13 KiB
phase, fixed_at, review_source, iteration, findings_in_scope, fixed, skipped, remaining, status, checkpoint, checkpoint_reason, commit_range
| phase | fixed_at | review_source | iteration | findings_in_scope | fixed | skipped | remaining | status | checkpoint | checkpoint_reason | commit_range |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 01-stabilize-video-pipeline | 2026-05-16T07:25:00Z | .planning/phases/01-stabilize-video-pipeline/01-REVIEW.md | 1 | 18 | 5 | 0 | 13 | partial | true | context_budget_exhausted_after_critical_cluster | 2e3f524..HEAD |
Phase 1: Code Review Fix Report (PARTIAL — CHECKPOINT)
Fixed at: 2026-05-16T07:25:00Z Source review: 01-REVIEW.md Iteration: 1 Status: partial — CHECKPOINT for orchestrator continuation
Summary
- Findings in scope: 18 (3 Critical + 9 Warning + 6 Info)
- Fixed this pass: 5 (3 Critical + 2 Warning, all delivered as one
cohesive commit because they share the
encodeAndSendBuffercode path) - Skipped: 0
- Remaining: 13 (7 Warning + 6 Info) — see "Remaining Work" below
- Sweep items added: 0 (no spare context budget for the defensive sweep on this pass — see "Sweep Status" below)
Commits this pass:
| Hash | Subject |
|---|---|
2e3f524 |
fix(01-review): CR-01+CR-02+CR-03+WR-03+WR-09 critical port + handshake race |
Fixed Issues
CR-01: Port reconnect race drops BUFFER response under reconnect-during-encode
Files modified: src/offscreen/recorder.ts
Commit: 2e3f524
Applied fix: Captured portAtRequest = keepalivePort before the await in
encodeAndSendBuffer. After blobToBase64 completes, refuse to post if
keepalivePort !== portAtRequest (i.e. a reconnect replaced the port mid-encode).
The SW already times out cleanly after 2 s, so the next SAVE_ARCHIVE
re-issues REQUEST_BUFFER on the fresh port — stale data never leaks to a
stranger port.
CR-02: SW never registers onMessage on the incoming port — PING silently discarded
Files modified: src/background/index.ts
Commit: 2e3f524
Applied fix: Added a permanent port.onMessage.addListener in the SW
onConnect handler that explicitly drains PING and silently drops unknown
traffic. The per-request BUFFER listener installed by getVideoBufferFromOffscreen
still wins (it's added later in the listener chain when SAVE_ARCHIVE fires).
This guarantees the SW idle-timer reset is consumed by a real handler under
all Chrome 110+ behaviour variants.
CR-03: Handshake deadlock after Service Worker respawn
Files modified: src/background/index.ts
Commit: 2e3f524
Applied fix: When chrome.offscreen.hasDocument() returns true on SW init,
immediately call offscreenReadyResolve?.() and null the resolver. The
offscreen MUST have completed its bootstrap before becoming observable via
hasDocument() — waiting for an OFFSCREEN_READY that will never come is the
deadlock. This is REVIEW.md option (b) (resolve-on-hasDocument-true).
WR-03: mergeVideoSegments sort uses non-stable timestamp resolution
Files modified: src/offscreen/recorder.ts
Commit: 2e3f524
Applied fix: Added a module-level let segmentSeq = 0; counter and
replaced baseTimestamp + idx with ++segmentSeq. Switched the encode
loop from Promise.all(map) to a sequential for loop so the counter
increments are deterministic (Promise.all would interleave shared-state
mutations). Throughput impact: ~150 ms vs ~50 ms for 3 segments —
still well under the 2 s SW timeout budget.
WR-09: mergeVideoSegments accepts unfinalized in-flight segment
Files modified: src/offscreen/recorder.ts
Commit: 2e3f524
Applied fix: Restructured the segment-selection logic in
encodeAndSendBuffer to include the in-flight Blob ONLY when
finalized.length === 0 (the first-10-s UX trade-off documented at the
original comment). Once any rotation has finalized a segment, the
unfinalized in-flight tail is dropped — its missing Matroska
SegmentSize/Cues was exactly the "File ended prematurely" symptom from
the webm-playback-freeze debug session.
Remaining Work — CHECKPOINT for orchestrator continuation
The orchestrator must spawn a continuation to complete the remaining 13 findings plus the defensive sweep. Listing them with file:line and the intended fix so the next instance can resume without re-reading REVIEW.md.
Warnings (7 remaining)
| ID | File:Line | Intended fix |
|---|---|---|
| WR-01 | src/offscreen/recorder.ts:146-151 |
Classify getDisplayMedia rejection into a stable error code ('user-cancelled', 'codec-unsupported', 'permission-denied', 'unknown'); broadcast the code, log the raw DOMException at warn level. |
| WR-02 | src/offscreen/recorder.ts:102-113 |
Remove chrome.runtime.sendMessage from assertCodecSupported; let startRecording's catch block handle the notify (currently double-emits). Update tests/offscreen/codec-check.test.ts GREEN block. |
| WR-04 | smoke.sh:109 |
Require python3 in pre-flight; remove the || printf '%s' "${SMOKE_HTML}" fallback that emits raw HTML into the data URL. (The ffprobe-gate |
| WR-05 | smoke.sh:74, 158, 161, 174-180 |
Snapshot the BEFORE list as full filenames (sorted); diff with comm -13 against AFTER list to identify the genuinely-new zip by identity, not just count. |
| WR-06 | src/shared/binary.ts:48-52 |
DEFERRED to Phase 2 per REVIEW.md own analysis — flagged for awareness, no v1 fix required. Document this skip in REVIEW-FIX.md. |
| WR-07 | src/shared/binary.ts:67-74 |
Add if (b64.length === 0) return new Blob([], { type: mimeType }); early-return; in SW getVideoBufferFromOffscreen, filter out empty segments before pushing to mergeVideoSegments. |
| WR-08 | src/background/index.ts:350-358 |
Replace inline base64 encoding in downloadArchive with await blobToBase64(archiveBlob) from src/shared/binary.ts. |
Info (6 remaining)
| ID | File:Line | Intended fix |
|---|---|---|
| IN-01 | src/background/index.ts:324 + manifest.json:4 |
Replace hardcoded extensionVersion: '1.0.0' with chrome.runtime.getManifest().version. REVIEW.md flags as Phase 5 but USER said FULL SCOPE — apply now. |
| IN-02 | src/shared/logger.ts:9, 14-22, 35, 40-50 |
Migrate Logger and ContentLogger from ...args: any[] to ...args: unknown[]. Audit call sites in src/background/index.ts and content script for console.log(...args) semantic preservation. |
| IN-03 | tests/offscreen/ring-buffer.test.ts |
Delete the vestigial breadcrumb file. REVIEW.md says "optional" — USER said FULL SCOPE → delete. |
| IN-04 | tests/offscreen/webm-playback.test.ts:65-105, 172-178 |
Delete decodeDryRun and the void decodeDryRun suppression; keep decodeDryRunStrict and move documentation into a JSDoc block. |
| IN-05 | src/shared/types.ts:18-22 |
Change Message<T = any> to Message<T = unknown>. Audit downstream (msg as Message).data accesses for explicit narrowing. May require updating multiple call sites in src/background/index.ts. |
| IN-06 | manifest.json:6-15 |
No defect — REVIEW.md confirms permissions are necessary. Document as "skipped: documented sufficient-but-broad scope per CONTEXT.md". |
Sweep Status
The defensive_sweep_directive in the prompt asked for a second pass covering missing error handling, race conditions, type-safety holes, edge cases not tested, etc. NOT performed this pass due to context budget.
Suggested sweep targets for the continuation instance (priorities ordered by impact based on what was observed during the CR fix pass):
- Race between
rotateSegment()andSTOP_RECORDING:stopRecording()clearsrotationTimerIdand callsvideoRecorder.stop(), butonSegmentStoppedthen unconditionally callsstartNewSegment()ifmediaStream !== null. After STOP_RECORDING, the stream is NOT nulled (onlyonUserStoppedSharingnulls it), so a new segment starts AFTER the operator manually stopped. Likely defect. encodeAndSendBufferre-entrance: Two back-to-back REQUEST_BUFFER messages would race —++segmentSeqshared state, two outstanding awaits. Each call capturesportAtRequestindependently so they don't cross-corrupt, but timestamps interleave nondeterministically between the two BUFFER responses. Test coverage missing.startNewSegment()MediaRecorder constructor throw: If the codec becomes unavailable mid-session (very rare but possible on some GPU/driver hot-swaps), thenew MediaRecorder(...)throws insidestartNewSegmentand the rotation chain dies silently —onSegmentStoppedis never called again, the recorder is stuck.onUserStoppedSharingre-entrance: registered with{ once: true }on EACH track. If video and audio tracks both end (audio is currently never enabled, but D-13 allows the option), the handler runs twice.resetBuffer()is idempotent so this is benign today, but thechrome.runtime.sendMessage({ ...error: 'user-stopped-sharing' })call fires twice — same double-emit pattern as WR-02.getVideoBufferFromOffscreenresolver leak: If the per-request handler is removed AND a stale BUFFER arrives, the permanent onMessage sink (CR-02 fix) catches it. But if the SW respawns mid-getVideoBufferFromOffscreen, thevideoPortreference goes stale and the request hangs until the 2 s timeout. Test coverage missing.base64ToBlobadversarial input (extends WR-07): chars outside base64 alphabet →atobthrowsInvalidCharacterError. Caller does have try/catch but the error path doesn't classify the failure.smoke.shBash style sweep:set -euo pipefailinteractions with2>/dev/null || truepatterns; missing--separators onrm,find,cp; unquoted globs infind ... -name '...'. Per Google shell style guide.- Build-config hygiene:
vite.config.tsandvitest.config.tsnot inspected this pass. Quick scan for unused plugins, missing strict options, alignment withtsconfig.json.
Test Status (end of this pass)
npx vitest run --reporter=dot
Test Files 8 passed (8)
Tests 30 passed (30)
Duration 2.67s
npx tsc --noEmit → exit 0 (no output)
grep -RIn "as any\|@ts-ignore" src/offscreen/ src/background/index.ts src/shared/
→ no matches (gate clean)
npm run build NOT yet exercised this pass — orchestrator continuation
should run it after all remaining fixes land.
Next Step
Orchestrator action required: spawn continuation instance with:
- Same
<config>block. - Add to the continuation prompt: "Start from commit
2e3f524. Remaining work is documented in.planning/phases/01-stabilize-video-pipeline/01-REVIEW-FIX.mdunder 'Remaining Work'. Apply all 7 Warnings + 6 Info + the sweep, then rewrite this REVIEW-FIX.md as the final report (replace the partial marker, status: all_fixed, list every commit hash)."
After the continuation completes:
- Run
/gsd-verify-work 1to gate Phase 1 requirements (NOT this fixer's job per the<no_phase_completion_marking>directive in the prompt).
Fixed: 2026-05-16T07:25:00Z Fixer: Claude (gsd-code-fixer, partial — CHECKPOINT) Iteration: 1