Fix #203: tree-fetch failure behind healthy backend #204

Merged
crueber merged 1 commit from fix/issue-203 into main 2026-09-08 17:39:59 +00:00
Owner

Root cause (proven live, not cache poisoning): on a cold serving copy (fresh restart/recreate: refs synced, objects/pack empty), every sha-addressed render 404s. The tree handler re-resolves the full head sha via git rev-parse in the empty copy → miss → not found: <sha> (bind_wal.go Resolve fallback arm, via notFoundOr — the exact 51-byte body; the issue's 'no server code emits this shape' missed the resolve arm). The UI's resolve→sha flow never issues a ref-named request, so nothing ever triggers the serve sync that would materialize the packs — every visit re-toasts and the page sticks on 'loading tree…'.

CAPTURED failing request (real browser via CDP, fresh profile, own cold server built from origin/main):

  • GET /acme/demo/api/tree/8de1c9a5c9acb458a73a4fee8ac2fe81402d8ed3 → 404 not found: 8de1c9a5c9acb458a73a4fee8ac2fe81402d8ed3, served from NETWORK (fromDiskCache=false), response carries NO Cache-Control (Date only) — a poisoned immutable entry cannot replay this; every reload re-fetches live.
  • Prior GET /acme/demo/api/resolve → 200 with the same sha (refs healthy).
  • GET /acme/demo/api/tree/main → 200 and HEALED the copy (materialize fetched the packs — the objects were in the bucket all along).

Fix: walView.Resolve serve-syncs once and retries rev-parse on a full-sha (40/64 hex) miss before 404ing (docs/go/07_api.md §9.3 step 3 amended + Decisions entry). Hot path unchanged — the retry runs only on rev-parse miss; genuinely-missing shas keep the exact not found: <seg> 404. No frontend change, no new deps.

Tests: new TestWalResolveColdCopyRetriesServe (cold-copy miss shape + serve-sync attempted + heal-to-commit + cold GET …/tree/<sha> → 200); verified it FAILS pre-fix (calls = [refs]). go vet clean, go test ./internal/api/ -race green, coverage 95.4% (≥95 gate holds). Browser proof post-fix on a cold server: repo opens clean, tree renders, empty tray, zero console errors, both themes (dark default + toggled light).

Follow-up found (NOT in this PR, will file separately): the push path writes manifest PackRef checksums WITH the pack- infix (server newLocalPack keeps it via TrimSuffix-only; same in repoimport/task.go + cmd/walhub/ops.go), violating the wal/<bare-hex>.pack contract (docs/go/02 + Rust §5.1) — live bucket shows pack-<hex> and pack-pack-<hex> duplicates piling up in manifests.

Root cause (proven live, not cache poisoning): on a cold serving copy (fresh restart/recreate: refs synced, objects/pack empty), every sha-addressed render 404s. The tree handler re-resolves the full head sha via `git rev-parse` in the empty copy → miss → `not found: <sha>` (bind_wal.go Resolve fallback arm, via notFoundOr — the exact 51-byte body; the issue's 'no server code emits this shape' missed the resolve arm). The UI's resolve→sha flow never issues a ref-named request, so nothing ever triggers the serve sync that would materialize the packs — every visit re-toasts and the page sticks on 'loading tree…'. CAPTURED failing request (real browser via CDP, fresh profile, own cold server built from origin/main): - `GET /acme/demo/api/tree/8de1c9a5c9acb458a73a4fee8ac2fe81402d8ed3` → 404 `not found: 8de1c9a5c9acb458a73a4fee8ac2fe81402d8ed3`, served from NETWORK (fromDiskCache=false), response carries NO Cache-Control (Date only) — a poisoned immutable entry cannot replay this; every reload re-fetches live. - Prior `GET /acme/demo/api/resolve` → 200 with the same sha (refs healthy). - `GET /acme/demo/api/tree/main` → 200 and HEALED the copy (materialize fetched the packs — the objects were in the bucket all along). Fix: `walView.Resolve` serve-syncs once and retries `rev-parse` on a full-sha (40/64 hex) miss before 404ing (docs/go/07_api.md §9.3 step 3 amended + Decisions entry). Hot path unchanged — the retry runs only on rev-parse miss; genuinely-missing shas keep the exact `not found: <seg>` 404. No frontend change, no new deps. Tests: new `TestWalResolveColdCopyRetriesServe` (cold-copy miss shape + serve-sync attempted + heal-to-commit + cold `GET …/tree/<sha>` → 200); verified it FAILS pre-fix (`calls = [refs]`). `go vet` clean, `go test ./internal/api/ -race` green, coverage 95.4% (≥95 gate holds). Browser proof post-fix on a cold server: repo opens clean, tree renders, empty tray, zero console errors, both themes (dark default + toggled light). Follow-up found (NOT in this PR, will file separately): the push path writes manifest PackRef checksums WITH the `pack-` infix (`server newLocalPack` keeps it via TrimSuffix-only; same in repoimport/task.go + cmd/walhub/ops.go), violating the `wal/<bare-hex>.pack` contract (docs/go/02 + Rust §5.1) — live bucket shows `pack-<hex>` and `pack-pack-<hex>` duplicates piling up in manifests.
walView.Resolve serve-syncs once and retries rev-parse on a full-sha
miss before 404ing (docs/go/07_api.md §9.3 step 3): the UI's resolve→sha
flow never issues a ref-named request, so the first sha-addressed visit
after a restart/recreate 404'd 'not found: <sha>' on every tree/blob/commit
fetch even though refs resolved and the objects were in the bucket.

Proven live via CDP against a cold server: GET …/api/tree/<head-sha>
404'd from network (fresh profile, no Cache-Control on the 404) while
…/tree/main 200'd and healed the copy — ruling out the poisoned
immutable-cache suspect. Regression cover: TestWalResolveColdCopyRetriesServe
(resolve miss shape + serve-sync retry + cold tree/<sha> → 200).
Sign in to join this conversation.
No description provided.