fix(core): never filter a definitive leak out of the verdict - #8
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
checkLeaks()accumulatedcollectedUnclosedinside thetypesloop, so thedefault filter — the three frame types — decided which definitive leaks
existed. A census with 47 live
VideoDecoders and ten collected withoutclose()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
collectedUnclosedis folded across every tracked type, whatevertypessays. GC'd-while-open is the definitive leak; a filter aimed at live frames
must not hide a decoder dropped on the floor.
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.types: 'all', so enforcing the codecs does not mean spelling out sevennames.
LeakReportgainsunenforcedLiveandenforced(additive).webcodecs_leak_sitesnow attributes every type unless one is named — itanswers "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:
VERSIONand the MCP server's announced versionwere both hardcoded
'0.1.0'andscripts/version.mjsrewrote neither, soevery 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
VideoFramecan 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.mjspins the verdict layer against synthetic censuses,including a
VideoDecodercollected withoutclose()— no browser test canforce 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:
VideoDecoders constructedclose()Both versions of the API reading one identical census (live
VideoDecoder=119,collectedUnclosed=48):Full suite: 36 pass, 0 fail, against the pinned Chrome 151.0.7922.71.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is enabled.