typescript-git-remote-s3-duplicate-bundle-self-heal
Done. The deliverable is ~/duckbrain/OPS-012-solution.md, backed by working code and 17 passing hermetic tests.
src/git/s3-repair.ts — TS port of the bash repair:
- parseS3Remote (s3://bucket/prefix) → bucket + keyPrefix
- Lists <keyPrefix>/refs/heads/<branch>/*.bundle, excluding LOCK#, PROTECTED#, .zip/, /LOCKS/, .lock, with pagination
- Keeper = bundle matching local tip sha, else newest LastModified among bundles whose sha still exists locally (git cat-file -e <sha>^{commit}, injectable for tests)
- Server-side CopyObject to quarantine/git/<ns>/<branch>/<sha>.bundle at bucket root → HeadObject size verify before DeleteObject → re-list assertion
- Never throws; all failures become { ok: false, reason }
src/git/push-self-heal.ts — pushWithDuplicateRefHeal runs the repair only on the /matches more than one/i signature and retries the push exactly once (zero S3 calls happy-path).
test/git/s3-repair-ops012.test.ts — 17 hermetic tests: scripted { send: vi.fn() } fake client per command constructor, injected hasCommit, plus a scratch-git-repo case exercising the default execFile runner.
Two races combined: (1) concurrent full-history bundle writes leave two bundles under one ref, permanently breaking git push; (2) the repair lived only in the 24h script while the racer was the in-daemon autopush — and the running daemon predated the 2c31641 single-flight gate, so pushOnCommit=false in file config did not describe live behavior. Fixed by TS repair + failure-gated wiring + one-shot daily-script run to unfreeze.
npx vitest run → 17/17 passednpx tsc --noEmit --strict → exit 0The markdown includes diagnosis commands, full source, the unfreeze/acceptance commands, and the ops lesson (ActiveEnterTimestamp vs gate commit).
# Evidence - Problem class: typescript-git-remote-s3-duplicate-bundle-self-heal - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-20T06:27:39.046Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "git-remote-s3 remote ends up with TWO bundles under refs/heads/<branch>/ (two concurrent full-history bundle writes racing), every subsequent push dies with 'error: dst refspec refs/heads/<branch> matches more than one' and the git-history mirror freezes while the native sync layer keeps the namespace looking backed-up. Root cause here was layer asymmetry: the quarantine+prune repair existed only in a 24h script while the racer was the in-daemon autopush \u2014 and specifically an OLD daemon running pre-gate code (the pushOnCommit/evaluatePushGate single-flight gate shipped later; the running process predated it, so file config said pushOnCommit=false while pushes still fired). Fix that landed: (1) TS port of the bash repair in src/git/s3-repair.ts \u2014 parse s3:// remote URL to bucket+keyPrefix, list <keyPrefix>/refs/heads/<branch>/ *.bundle (exclude LOCK#/PROTECTED#/.zip//LOCKS//.lock), keeper=bundle matching local tip sha else newest LastModified, skip stale sha absent from local repo (git cat-file -e sha^{commit} via execFile), quarantine = server-side CopyObject to quarantine/git/<ns>/<branch>/<sha>.bundle AT BUCKET ROOT + HeadObject size verify BEFORE DeleteObject, re-list assert, never throws; (2) wire into the async push path ONLY on the failure signature (isDuplicateRefPushError on 'matches more than one') then retry the push exactly once \u2014 zero added S3 calls on the happy path; (3) unfreeze the live mirror by running the deployed daily push script once (repair runs before the push per namespace; the push then replaces the survivor bundle). Hermetic tests: scripted fake client {send:vi.fn()} per command constructor + injected hasCommit + one scratch-git-repo case for the default cat-file runner. Ops lesson: verify the RUNNING daemon's code vintage (Started timestamp vs the gate commit) before trusting file config to describe live behavior \u2014 getConfig reads the file fresh per call, but the process only loads source at start.", "environment": "duckbrain (Node 22, TS7, AWS SDK v3, git-remote-s3 on Hetzner S3)", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "typescript-git-remote-s3-duplicate-bundle-self-heal", "provider": "openrouter", "solved_at": "2026-09-20T06:27:39.046Z", "version": ""}Done. The deliverable is ~/duckbrain/OPS-012-solution.md, backed by working code and 17 passing hermetic tests.
src/git/s3-repair.ts — TS port of the bash repair:
- parseS3Remote (s3://bucket/prefix) → bucket + keyPrefix
- Lists <keyPrefix>/refs/heads/<branch>/*.bundle, excluding LOCK#, PROTECTED#, .zip/, /LOCKS/, .lock, with pagination
- Keeper = bundle matching local tip sha, else newest LastModified among bundles whose sha still exists locally (git cat-file -e <sha>^{commit}, injectable for tests)
- Server-side CopyObject to quarantine/git/<ns>/<branch>/<sha>.bundle at bucket root → HeadObject size verify before DeleteObject → re-list assertion
- Never throws; all failures become { ok: false, reason }
src/git/push-self-heal.ts — pushWithDuplicateRefHeal runs the repair only on the /matches more than one/i signature and retries the push exactly once (zero S3 calls happy-path).
test/git/s3-repair-ops012.test.ts — 17 hermetic tests: scripted { send: vi.fn() } fake client per command constructor, injected hasCommit, plus a scratch-git-repo case exercising the default execFile runner.
Two races combined: (1) concurrent full-history bundle writes leave two bundles under one ref, permanently breaking git push; (2) the repair lived only in the 24h script while the racer was the in-daemon autopush — and the running daemon predated the 2c31641 single-flight gate, so pushOnCommit=false in file config did not describe live behavior. Fixed by TS repair + failure-gated wiring + one-shot daily-script run to unfreeze.
npx vitest run → 17/17 passednpx tsc --noEmit --strict → exit 0The markdown includes diagnosis commands, full source, the unfreeze/acceptance commands, and the ops lesson (ActiveEnterTimestamp vs gate commit).
# Evidence - Problem class: typescript-git-remote-s3-duplicate-bundle-self-heal - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-20T06:27:39.046Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "git-remote-s3 remote ends up with TWO bundles under refs/heads/<branch>/ (two concurrent full-history bundle writes racing), every subsequent push dies with 'error: dst refspec refs/heads/<branch> matches more than one' and the git-history mirror freezes while the native sync layer keeps the namespace looking backed-up. Root cause here was layer asymmetry: the quarantine+prune repair existed only in a 24h script while the racer was the in-daemon autopush \u2014 and specifically an OLD daemon running pre-gate code (the pushOnCommit/evaluatePushGate single-flight gate shipped later; the running process predated it, so file config said pushOnCommit=false while pushes still fired). Fix that landed: (1) TS port of the bash repair in src/git/s3-repair.ts \u2014 parse s3:// remote URL to bucket+keyPrefix, list <keyPrefix>/refs/heads/<branch>/ *.bundle (exclude LOCK#/PROTECTED#/.zip//LOCKS//.lock), keeper=bundle matching local tip sha else newest LastModified, skip stale sha absent from local repo (git cat-file -e sha^{commit} via execFile), quarantine = server-side CopyObject to quarantine/git/<ns>/<branch>/<sha>.bundle AT BUCKET ROOT + HeadObject size verify BEFORE DeleteObject, re-list assert, never throws; (2) wire into the async push path ONLY on the failure signature (isDuplicateRefPushError on 'matches more than one') then retry the push exactly once \u2014 zero added S3 calls on the happy path; (3) unfreeze the live mirror by running the deployed daily push script once (repair runs before the push per namespace; the push then replaces the survivor bundle). Hermetic tests: scripted fake client {send:vi.fn()} per command constructor + injected hasCommit + one scratch-git-repo case for the default cat-file runner. Ops lesson: verify the RUNNING daemon's code vintage (Started timestamp vs the gate commit) before trusting file config to describe live behavior \u2014 getConfig reads the file fresh per call, but the process only loads source at start.", "environment": "duckbrain (Node 22, TS7, AWS SDK v3, git-remote-s3 on Hetzner S3)", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "typescript-git-remote-s3-duplicate-bundle-self-heal", "provider": "openrouter", "solved_at": "2026-09-20T06:27:39.046Z", "version": ""}