Repository navigation
fix(strategy): bound turn checkpoint finalization - #2188
Open
MuskanPaliwal wants to merge 19 commits into
Open
MuskanPaliwal wants to merge 19 commits into
MuskanPaliwal wants to merge 19 commits into
Conversation
Entire-Checkpoint: 01M14QBVM11XQ1ZD7QCVZXMBST
Entire-Checkpoint: 01M1BDT4T6N6HDTEJCXM5WM0QW
MuskanPaliwal
marked this pull request as ready for review
August 31, 2026 08:45
Entire-Checkpoint: 01M1BMQMME14ZGDQZN54EFA5RY
…-finalize-deadline-2148 # Conflicts: # cmd/entire/cli/strategy/manual_commit_hooks.go
…-finalize-deadline-2148
Entire-Checkpoint: 01M4X26AHSD70GXQT6VXNAZT3N
…ast the deadline Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4X70BFCP0FFQTJ20Y7V0DVX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4X789CH3XGK8PYK7ZSH7FNH
The deadline can pass while checkpoint.Open runs, and precompute would then hash the whole transcript for writes that never start. Also log a write error that coincides with the deadline instead of hiding it behind the interruption, and widen the mid-loop deadline test so a slow runner still reaches the first fetch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4X8H5085N6527PBH3HV9JK2
…-finalize-deadline-2148
…eouts An interrupted turn-end finalize now keeps the unfinalized TurnCheckpointIDs, as the late-transcript and degraded-scanner exits already do, so a repeated Stop in the same turn retries them with a fresh budget. The five copied interruption checks collapse into finalizeInterrupted. The finalize deadline now comes from the agent running the hook rather than state.AgentType, which can be empty. Antigravity installs from before bd1e2ff have no Stop timeout, so agy's 30s default applies while the budget assumes 300s. RepairHookTimeouts lets `entire doctor` add the missing timeout, editing raw JSON so unmodeled keys survive and set timeouts are kept. The doctor message now covers a default that is too short as well as too long. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4XZ6B1C1SQFVWK7TVHE6KST
…dline-2148' into fix-turn-checkpoint-finalize-deadline-2148
… repair Finalize now checks the turn-end deadline before it starts. When the lock wait and SaveStep have already spent the budget, the transcript is no longer read and redacted for writes that can never start, so that time stays available for saving session state. Local work that has started is still never interrupted. The deadline carries its own cause, so logs tell an exhausted budget apart from the host cancelling the hook. The Antigravity hook-timeout repair that doctor runs without asking now touches only Entire's own Stop handler, matched by command. It treats a timeout key in any case as already set, steps over null entries instead of panicking on them, and leaves a disabled entry alone. Agents now declare their host's turn-end hook timeout through agent.TurnEndHookTimeouter, and Entire derives the finalize budget from it in one place. A non-positive value falls back to the default instead of meaning "skip", which matched neither the sibling SessionEndBudgeter nor any agent's intent. The per-agent tests that restated the implementation are replaced by one check over every declaring agent. entire status names both causes of capture degradation again, and the checkpoint scenario docs show that interrupted IDs are kept for retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4ZBSJDK749JX37VPRSE974Q
Entire-Checkpoint: 01M4ZTEZZ6MXES3ZD7VQQRM78Z
Entire-Checkpoint: 01M4ZTF1A676FYG752VWCV83M0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2148
At turn end, finalize rewrites every checkpoint the turn created with the full transcript. Each rewrite can fetch refs and blobs from the remote. Every fetch has its own limit, and the dead-network memos keep a remote that is down from costing that limit once per checkpoint. Nothing limited the pass as a whole, though. A slow remote that still answers finishes each fetch just under its limit, trips neither memo, and the total outlasts the host's hook timeout. The host kills the hook before Entire saves session state. The checkpoints keep their provisional transcripts, and the only trace is a few warning logs.
The fix
The network phase of
finalizeAllTurnCheckpoints, meaning the checkpoint store open and the write loop, now runs under a deadline. The deadline comes from the real limit the agent's host puts on the turn-end hook.Finalize checks the deadline at entry and in the network phase, never in the middle of local work. If the deadline has already passed at entry, finalize doesn't start. Nothing it prepared could be written by then, and the hook needs that time to save state. Reading, sanitizing, externalizing and redacting the transcript is local work that never looks at the context, so nothing cuts it off halfway. Its time still counts against the budget. If it runs long, finalize stops at the first network step instead of starting fetches it can't finish. Finalize checks again after the store opens, so precompute doesn't hash the transcript for writes that will never start. A write that fails as the deadline passes logs its error, and finalize then counts it as an interruption.
The deadline is
processStartplus a per-agent budget, the same patternsessionEndCondenseDeadlineuses. The host starts its clock when it spawns the hook, so Entire starts its clock at process start too. The budget comes from the agent running the hook, not fromstate.AgentType, which can be empty.Each agent declares its host's turn-end hook timeout through a new
agent.TurnEndHookTimeoutercapability. Entire derives the budget from it in one place, so changing the timeout changes the budget too. #1237 just changed Claude Code's.agent.TurnEndBudgetWithinkeeps a fifth of the timeout, between 2s and 10s, for saving state after finalize. A timeout of zero or less falls back to the default.checkpointHookTimeoutSecstopHookTimeoutSecondsdefaultHookTimeoutSecexecFiletimeout, pinned by a testdefaultTurnEndHookTimeout(30s)TestTurnEndHookTimeoutLedgermakes every built-in agent either declare a timeout or appear inturnEndHookTimeoutFallbackswith a reason, and it fails on a stale entry. A new agent can't pick up the default by accident. External agents always get the default, because the protocol has no field for a hook timeout.When finalize is cut short
TurnCheckpointIDs, as they already do for the late-transcript and degraded-scanner exits. A second Stop in the same turn retries them with a fresh budget. Otherwise the next turn start clears them and the provisional transcripts stay for good. I expect the retry to be rare. Claude Code, Codex, Copilot CLI and most other hosts fire Stop once per turn, so for them a cut-off checkpoint keeps its provisional transcript. That's the price of finishing inside the host's limit, and I think it's the right trade.CaptureDegradedAt, soentire statusreports the problem. This now covers the host cancelling the hook too (SIGTERM ends the root context), which used to leave no trace.Older Antigravity installs
The Antigravity budget assumes the 300s Stop timeout Entire installs. Installs from before bd1e2ff have no timeout, so agy's 30s default applies and kills the hook long before the 290s budget runs out.
AntigravityAgent.RepairHookTimeoutsletsentire doctoradd the missing timeout without asking, the same repair Claude Code already has. It edits the raw JSON, so keys Entire doesn't model survive. It only touches Entire's own Stop handler, which it finds by command. It keeps a timeout that's already set under any key case, skips null entries, and leaves a disabled entry alone. The doctor message now covers an agent default that's too short as well as one that's too long.Doctor only helps people who run it. So Antigravity now also reports a missing Stop timeout through
CheckHookConfig, andentire statusshows "Antigravity hooks out of date · run 'entire doctor'".--jsonlists the agent underhooks_outdated. The check and the repair share one function, so status flags an install exactly when doctor has a timeout to add. Running doctor always clears the warning. A user's own handlers, a timeout they set, or a disabled entry never trigger it.Decision for reviewers
The 30s fallback is a judgment call. Cursor documents only a "platform default". OpenCode has no limit at all. Entire's plugin runs the hook through
spawnSync, which blocks OpenCode's UI untilentireexits, so the fallback is the only limit OpenCode gets. 30s matches what Codex and Copilot enforce. It's one constant inlifecycle.go.Verification
Finalize deadline tests:
Agent and budget tests:
.tsextension.Antigravity tests:
"Timeout"in another case, and keeps unmodeled keys, foreign entries, a user's handler in the Stop list,nullentries and a disabled entry. It leaves the file alone when there's nothing to add.How the timing tests stay deterministic:
I checked each new test against a planted regression, and each one failed. The regressions were the deadline check moved to the top, the checks around the store open removed, the repository-open interruption check removed, writes ignoring the deadline, cancellation not flagged, a write error flagged as degradation, interrupted IDs cleared or kept off by one, Pi's constant drifting, and a missing or stale ledger entry.
I also ran it end to end with a built binary. An Antigravity install with the Stop timeout removed shows the
entire statuswarning.entire doctoradds the 300s timeout and the warning goes away. A fresh install shows no warning.On the final commit, format, lint (0 issues), and the unit, integration and canary tests all pass.
Not included
sqlite3Timeout(30s) against the assumed 30s hook limit. Pi has 2s of headroom to redact what may be a large transcript. The cheap fix is to cap the sidecar read at whatever deadline is left. Follow-up.TurnCheckpointIDsset and doesn't record capture degradation. That predates this PR. Follow-up.