Skip to content

fix(core): never filter a definitive leak out of the verdict - #8

Merged
unfoundbox merged 2 commits into
mainfrom
claude/priceless-haslett-30cd49
Aug 17, 2026
Merged

fix(core): never filter a definitive leak out of the verdict#8
unfoundbox merged 2 commits into
mainfrom
claude/priceless-haslett-30cd49

Conversation

@unfoundbox

@unfoundbox unfoundbox commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

The bug

checkLeaks() accumulated collectedUnclosed inside the types loop, so the
default filter — the three frame types — decided which definitive leaks
existed. A census with 47 live VideoDecoders and ten collected without
close() produced an empty report and printed "No leaked WebCodecs objects."

The shim had recorded every one of them and warned on the console. Only the
verdict layer lost them. This is the failure mode CONTRIBUTING.md calls the one
rule that matters: the tool reported clean on the exact leak it was pointed at.

Found while dogfooding this against motionvector studio's decoder-lifecycle fix.

What changed

  1. collectedUnclosed is folded across every tracked type, whatever types
    says. GC'd-while-open is the definitive leak; a filter aimed at live frames
    must not hide a decoder dropped on the floor.
  2. No unqualified all-clear. When an unenforced type holds live objects the
    message names them: No leaks in VideoFrame, AudioData, ImageBitmap — but VideoDecoder=47 still live and not enforced. Pass types: 'all' to check those too. summarize() inherits it.
  3. types: 'all', so enforcing the codecs does not mean spelling out seven
    names. LeakReport gains unenforcedLive and enforced (additive).

webcodecs_leak_sites now attributes every type unless one is named — it
answers "which line is leaking", which is attribution, not a verdict, and
returning nothing for a codec leak was the same blind spot.

Separately, the version stamp: VERSION and the MCP server's announced version
were both hardcoded '0.1.0' and scripts/version.mjs rewrote neither, so
every payload from 0.2.0 and 0.2.1 carried a stamp two releases stale. The bump
script carries them now and fails loudly if either pattern stops matching.

Behaviour change

A suite that passed only because it enforced VideoFrame can now go red
correctly, if something was collected unclosed. This is why the recommendation
is 0.3.0, not a patch.

Verification

test/assert.test.mjs pins the verdict layer against synthetic censuses,
including a VideoDecoder collected without close() — no browser test can
force a finalizer on demand. Each of the three fixes was reverted in turn and
watched to fail (2, 1, 2 failures respectively).

End to end against studio's real preview pipeline — mediabunnyVideoPipeline,
two 12s 1080p h264 clips, an 8s hard scrub, driven over CDP:

pre-fix studio studio today
VideoDecoders constructed 780 239
live at end 119–223 0
GC'd without close() 48–63 0

Both versions of the API reading one identical census (live VideoDecoder=119,
collectedUnclosed=48):

published 0.2.1 → ok=true   "No leaked WebCodecs objects."
this branch     → ok=false  "48 VideoDecoder garbage collected without close() — definitively leaked."

Full suite: 36 pass, 0 fail, against the pinned Chrome 151.0.7922.71.

🤖 Generated with Claude Code


View with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is enabled.

unfoundbox and others added 2 commits August 17, 2026 13:30
Dogfooding this tool on a decoder-lifecycle fix, a run that ended with 47
live VideoDecoders and ten collected without close() printed "No leaked
WebCodecs objects." The shim saw every one of them and warned on the
console. checkLeaks() threw the evidence away: the default `types` is the
frame-like types, and the accumulation loop visited only those, so both
the live decoders and the GC'd-unclosed ones fell outside the report.

Three changes:

- collectedUnclosed is folded across every tracked type, whatever `types`
  says. The README calls it the most definitive leak there is; a filter
  aimed at live frames must not hide a decoder dropped on the floor.
  This can turn a previously green suite red, correctly.
- The all-clear is qualified when an unenforced type holds live objects,
  so a report can no longer say "no leaks" beside VideoDecoder=47.
- `types: 'all'` enforces all seven types without naming them.

webcodecs_leak_sites now attributes every type unless one is named. It
answers "which line is leaking", which is attribution, not a verdict —
returning nothing for a codec leak was the same blind spot.

test/assert.test.mjs pins all of it against synthetic censuses, including
the GC'd-unclosed case no browser test can produce on demand. Each fix
was reverted in turn and watched to fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`VERSION` in the core and the version the MCP server announces were both
hardcoded '0.1.0', and scripts/version.mjs rewrote neither. Every census
payload from 0.2.0 and 0.2.1 therefore carried a version that had not been
true since the first release — including the payloads an agent reads, and
the handshake an MCP client sees.

The bump script now carries both, and exits with an explanation if either
literal stops matching its pattern, because a silent no-op is exactly how
they went stale. test/version.test.mjs holds them to their package.json.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@unfoundbox
unfoundbox merged commit 575cd96 into main Aug 17, 2026
3 checks passed
@unfoundbox
unfoundbox deleted the claude/priceless-haslett-30cd49 branch August 17, 2026 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant