fix(checkpoint): fetch a locally-missing ref before declaring a backfill target absent#1824
fix(checkpoint): fetch a locally-missing ref before declaring a backfill target absent#1824peyton-alt wants to merge 5 commits into
Conversation
…ill target absent The git-refs store's backfill writes (summary, transcript finalize, attribution) probed for the checkpoint ref with a local-only lookup, while reads on-demand fetch a missing ref when a fetcher is configured. For a checkpoint written or migrated on another machine that means the read path sees it but a backfill declares it absent — under kind routing the write then lands on a stale fallback copy (or errors), and refs-first reads never surface it. Backfills now resolve their base via resolveRefMaybeFetch: a ref missing locally is fetched once before deciding. Absent after the fetch still reports ErrCheckpointNotFound (legitimate fallthrough); a fetch FAILURE surfaces as-is — transient unavailability must not read as absence, or the router would fork the write. Creates keep the local-only probe: their ref never exists yet, and fetch-on-create would add a doomed network round-trip to every condensation and break offline writes. Common-case cost is zero: the ref exists locally and no fetch fires. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a consistency gap in the git-refs checkpoint store: backfill writes (summary, transcript finalize, attribution) now resolve a checkpoint ref the same way reads do—by attempting a one-time on-demand fetch when the ref is missing locally—preventing kind-routing fallthrough from writing backfills to a stale backend copy.
Changes:
- Introduces
refBaseForBackfill(fetch-aware) and refactors shared tip/tree loading intorefTip. - Switches backfill write paths to use
refBaseForBackfillwhile keeping creates on the local-onlyrefBase. - Adds unit tests covering fetch-on-backfill, fetch-failure behavior, genuine absence after fetch, and “creates never fetch”.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| cmd/entire/cli/checkpoint/refs_store.go | Adds fetch-aware base resolution for backfills and updates backfill writers to avoid false “not found” when a ref is missing locally. |
| cmd/entire/cli/checkpoint/refs_store_test.go | Adds targeted tests to pin backfill fetch behavior and failure/absence semantics. |
| func (s *gitRefsStore) backfillTranscript(ctx context.Context, opts UpdateOptions) error { | ||
| if opts.CheckpointID.IsEmpty() { | ||
| return errors.New("invalid update options: checkpoint ID is required") | ||
| } | ||
|
|
||
| parentHash, existing, err := s.refBase(opts.CheckpointID) | ||
| parentHash, existing, err := s.refBaseForBackfill(ctx, opts.CheckpointID) | ||
| if err != nil { |
There was a problem hiding this comment.
Fixed in 96013ab — backfillTranscript now has the same ctx.Err() short-circuit as its two siblings, so a cancelled context never reaches the (potentially fetching) base resolution.
…ore layer Review findings from the PR #1824 multi-agent pass: - Tests: pin the zero-cost claim (backfill with a locally-present ref never fetches), verify the fetched-then-backfilled data reads back for all three request types, and assert the backfill commit parents on the fetched tip rather than orphaning over the checkpoint's history. - Comments rephrased order-agnostically: the write-fallthrough they cited is PR #1811's unmerged behavior; they now state the absence-vs-failure contract instead of a phantom mechanism. refBase's doc names its other deliberate callers (migrate.go local import; attach's externally-guarded writeSession). - FetchCheckpointRef's stale 'caller treats a fetch failure as not found' comment inverted the actual contract this PR depends on; it now documents failure-surfaces-never-absence, including the remote-lacks-ref caveat. - ref-checkpoint-backend.md: the write shape and on-demand-fetch sections now describe the create/backfill probe split. Scope note (review CRITICAL): no production write path currently wires a RefFetcher, so this PR is store-layer semantics + plumbing; the wiring is a follow-up gated on (1) FetchCheckpointRef distinguishing remote-lacks-ref (absence) from transport failure — today wiring would break the pre-migration-hex fallthrough — and (2) a hook-appropriate fetch timeout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Applied the review fix set in 2b4b736. Highlights:
Field test (fresh local clone, ref absent locally): the ref was fetched and restored end-to-end — via the read store's fetcher, which is exactly the masking the review identified; the write-path wiring remains the named follow-up. Full suite green (8173 tests). |
Completes the #1824 fix — previously the fetch-probing backfill base was store-layer plumbing with no production caller wiring a RefFetcher. The two prerequisites the review named are now in place: - remote.FetchCheckpointRef (moved from the cli package so strategy hooks can wire it; cli keeps thin aliases) probes with ls-remote before fetching: a ref the remote genuinely lacks returns an error wrapping plumbing.ErrReferenceNotFound (absence — safe fallthrough under kind routing), while transport failures on either leg surface as-is. git fetch of a missing refspec fails indistinguishably from a network error, which is why wiring was unsafe before this discrimination existed. - Write probes are budget-bounded (remote.WriteProbeFetchBudget, 15s via remote.BoundedCheckpointRefFetcher) and the git-refs store memoizes the first transport-level fetch failure per store, so a stop hook finalizing N checkpoints on a dead network pays the outage once, briefly. Remote absence is per-ref and never memoized. Wired at the three backfill write sites: explain --generate's write store, the stop-hook transcript finalize, and post-commit attribution. The carry-forward and other creates stay unwired — creates never probe. Tests: remote-level absence/present/transport-failure fixtures against a local bare origin; store-level absence classification chain-through and failure-memo behavior (transport memoized once, absence never). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Wiring follow-through in b33b3c2 — the review's CRITICAL ("fix is inert: no production write path wires a RefFetcher") is now resolved rather than rescoped around. Both named prerequisites were implemented: absence-vs-failure discrimination via an ls-remote probe in the relocated |
Copilot review finding on #1824: backfillTranscript lacked the ctx.Err() guard its two backfill siblings have, so a cancelled context could still reach the (now potentially fetching) base resolution. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Findings from the wiring commit's multi-agent re-review: - False absence on a fallback target (HIGH): when a checkpoint_remote is configured but unresolvable (unknown provider + unmappable origin protocol), FetchURL falls back to origin — a remote that never hosts the configured checkpoint refs. The ls-remote probe treated its emptiness as absence, silently dropping backfills for checkpoints that exist on the real checkpoint remote. fetchURLAuthoritative now reports whether the resolved URL is authoritative for checkpoint refs; probe-emptiness on a non-authoritative fallback is a failure, never absence. - SSH hygiene in hooks (HIGH): the two hook wirings now use remote.HookCheckpointRefFetcher, which marks the context non-interactive (BatchMode SSH) like the pre-push path — a passphrase-protected key can no longer prompt or hang inside post-commit/stop hooks. - Actionable errors (HIGH): lsRemote folds git's redacted stderr into its error, and FetchCheckpointRef folds the fetch output — a bare 'exit status 128' left auth, DNS, and missing-repo failures indistinguishable in hook Warn logs. - Caller cancellation is no longer memoized as a network failure (a Ctrl-C mid-operation said nothing about the remote); the memoized-skip message notes its cause may name a different ref; mirrors never receive a RefFetcher (best-effort write-only copies must not pay network probes); the git-branch backfillTranscript gains the ctx.Err() guard its siblings have (pre-existing gap surfaced by the same review). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Re-review of the wiring commit complete (code-reviewer + silent-failure agents, scoped to b33b3c2) — fixes applied in 96013ab and 4fdb8aa: Code-reviewer: clean — probe semantics verified (same auth path as fetch, no tail-match over-matching, no prompt hang), memo concurrency correct, 15s budget correctly clamps the internal 2-minute timeout, layering sound. Silent-failure agent found three HIGHs, all fixed:
Also: caller cancellation is no longer memoized as a network verdict (tested), the memoized-skip message clarifies its cause may name a different ref, mirrors never receive a RefFetcher, and the git-branch Field-tested in a fresh clone: present-on-remote ref resolves end-to-end in ~3s; absent-everywhere ULID returns a clean not-found in under a second. Full suite green (8182 tests), fmt + lint clean. |
Follow-up to the #1811 review (addressed directly per discussion, no issue filed). Complete fix: store-layer semantics and production wiring.
Problem
The git-refs store's reads on-demand fetch a checkpoint ref that's missing locally; its backfill writes (summary, stop-time transcript finalize, attribution) probed with a local-only lookup. For a checkpoint written or migrated on another machine, a backfill declared it absent — landing on a stale fallback copy under kind routing (#1811), or failing — while reads served the refs copy, leaving the backfilled data permanently invisible.
Fix (three layers)
1. Store: backfills fetch-probe —
refBaseForBackfill→resolveRefMaybeFetchfetches a locally-missing ref once when a fetcher is wired. Creates keep the local-only probe (their ref never exists yet). Zero cost when the ref exists locally (pinned).2. Absence ≠ failure at the fetch layer —
remote.FetchCheckpointRef(moved from the cli package so strategy hooks can import it; thin aliases keep old call sites) now probes withls-remotebefore fetching: a ref the remote genuinely lacks returns an error wrappingplumbing.ErrReferenceNotFound→ classified as absence → safe fallthrough under kind routing. Transport failures on either leg surface as-is — never absence, so a dead network can't fork a write onto a stale copy. This was the blocker that made naive wiring unsafe (git fetchof a missing refspec fails like a network error).3. Wiring with a hook-safe budget —
remote.BoundedCheckpointRefFetcher(remote.WriteProbeFetchBudget)(15s per probe) wired at the three backfill write sites: explain--generate's write store, the stop-hook transcript finalize, and post-commit attribution. The git-refs store memoizes the first transport-level fetch failure per store instance, so a stop hook finalizing N checkpoints on a dead network pays the outage once, briefly. Remote absence is per-ref and never memoized.Verification
NotErrorIs ErrCheckpointNotFound), absent-after-fetch, local-ref-never-fetches, creates-never-fetch, absence-classification chain-through, and failure-memo behavior (transport memoized once; absence never)FetchCheckpointRef's inverted contract comment fixed;ref-checkpoint-backend.mdupdated)mise run fmt+mise run lintcleanRelationship to #1811
Independent; merges in either order (comments phrased order-agnostically). Reconciliation: whichever lands second drops #1811's "backfill absence probes are local-only" sentence from the kindRoutingStore
Writedoc comment.🤖 Generated with Claude Code