finalizeAllTurnCheckpoints has no total deadline: a slow remote can outlast the agent's hook timeout
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 5.1k
- Forks
- 475
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 178
Description
Split out of #2146, where the acute dead-network case is fixed by memoizing an exhausted blob fetch. This is the case the memos cannot catch.
The gap
finalizeAllTurnCheckpoints (strategy/manual_commit_hooks.go) loops over state.TurnCheckpointIDs against a single store, logging and continuing past each failure. Each iteration can make bounded network calls — a ref fetch and, since #2146, a blob fetch — each capped at remote.WriteProbeFetchBudget (15s).
Two memos bound the dead network: gitRefsStore.fetchFailure for refs, and hookBlobFetcher's exhaustion memo for blobs (#2146). Both trigger only when a fetch burns its whole budget.
A slow-but-alive remote trips neither. Every fetch returns just under budget, nothing is memoized, and the cost accumulates per checkpoint. 5 × 14s is as fatal to a hook as 5 × 15s. Worse, FetchingTree.File fetches one hash at a time, so a read touching several missing blobs multiplies within a single iteration.
Nothing caps the total:
newGitHookContext(hooks_git_cmd.go:41) wraps logging and a perf span only — no deadline.lifecycle.go'scondenseDeadlineis a different path and does not enclose this loop.
So the effective ceiling is the agent killing the hook — roughly 60s for Claude Code — after which everything downstream of the loop is skipped, silently.
Why this is more than a latency nit
The stop hook is what the user's agent waits on. Being killed mid-loop means some checkpoints keep their provisional mid-turn transcript instead of the full session transcript, with only Warn-level breadcrumbs. It is also the failure mode most likely in the field: flaky wifi, a VPN, a throttled or loaded remote are all commoner than a cleanly dead network.
Direction, not a decision
An enclosing context.WithTimeout over the loop is the obvious shape, but two things need deciding rather than assuming:
- The budget. It has to fit inside the tightest host deadline, and those differ — Claude Code's ~60s hook timeout versus Codex's
agent.SessionEndBudgeter3s cap, where the process tree is killed on expiry. Existing reference points:StatusWalkBudget20s,WriteProbeFetchBudget15s,ReadChainBudget3min. - What happens to checkpoints past the deadline. Almost certainly fine to leave unfinalized: finalize is already best-effort (
errCount++; continue), and PostCommit retries an incomplete condense. But that should be confirmed rather than assumed, and whatever it is should be visible —entire statusalready surfaces capture degradation viaSessionState.CaptureDegradedAt, which may be the right precedent.
A cheaper partial mitigation worth considering either way: have FetchingTree.File fall back to PreFetch-style batching on a miss, so one read costs one round-trip instead of one per blob.
Not a regression
This predates #2146 — the ref path had the same unbounded-loop shape with only a dead-network memo. #2146 adds a second fetch per iteration, which raises the constant but is not the cause.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with finalizeAllTurnCheckpoints in strategy/manual_commit_hooks.go, then read newGitHookContext in hooks_git_cmd.go and the FetchingTree.File path alongside the existing fetch budgets. Determine the host-compatible total budget, the behavior for checkpoints beyond it, and how incomplete finalization should be surfaced; done means the loop cannot outlast the chosen deadline and the outcome is visible.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- cli, performance
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100