fix(agent-runner): rotate oversized/old session transcripts before resume#2586
Conversation
…sume A long-lived hub session never rotates its continuation, so the on-disk .jsonl grows without bound — days of history plus base64 image blocks the agent Read (screenshots from QA lanes, etc.). The SDK reloads the whole transcript on every --resume, and past a threshold the first turn alone exceeds the host's 30-min idle ceiling: the container is SIGKILLed before it can reply, then the next message repeats the cycle forever. Symptom: a hub that was responsive for days suddenly goes silent on a heavy turn. Before resuming, the Claude provider now checks the transcript backing the stored continuation; if it exceeds a size cap (default 12MB) or age cap (default 14 days, from the first entry's timestamp) it archives a markdown summary to conversations/ and starts a fresh session. Both caps are operator-overridable via CLAUDE_TRANSCRIPT_ROTATE_BYTES / CLAUDE_TRANSCRIPT_ROTATE_AGE_DAYS. The PreCompact archiver is refactored into a shared archiveTranscriptFile() reused by the rotation path. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6686315a10
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const days = Number(process.env.CLAUDE_TRANSCRIPT_ROTATE_AGE_DAYS); | ||
| return Number.isFinite(days) && days > 0 ? days * 86_400_000 : 14 * 86_400_000; |
There was a problem hiding this comment.
Respect zero age override when computing rotation threshold
When CLAUDE_TRANSCRIPT_ROTATE_AGE_DAYS is set to 0 (or a negative value), the comment says age-based rotation should be disabled, but this implementation falls back to the 14-day default instead. In deployments that intentionally set 0 to keep long-lived sessions resumable, continuations will still be rotated once they pass 14 days, causing unexpected session resets and context loss.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch — fixed in #2595. transcriptRotateAgeMs() now distinguishes an unset/blank/non-numeric var (14-day default, unchanged) from an explicit 0/negative override, which returns Infinity so the age check is disabled and the byte-size cap alone governs — matching the documented behavior.
|
@IamAdamJowett Thanks for the contribution! |
…zed-transcripts fix(agent-runner): rotate oversized/old session transcripts before resume
Problem
A long-lived hub/parent session never rotates its continuation, so the on-disk SDK transcript (
projects/<cwd>/<session>.jsonl) grows without bound — days of history plus base64 image blocks the agentRead(e.g. screenshots routed back from sub-agents). The Claude SDK reloads the entire transcript on every--resume. Past a threshold the first turn alone exceeds the host's 30-minuteabsolute-ceiling(src/host-sweep.ts), so the container is SIGKILLed (exit 137) before it can produce any output — and the next inbound message wakes a fresh container that hits the same wall. The hub becomes permanently unresponsive.Observed in the wild: a hub session responsive for days went silent on an image-heavy turn. Its transcript had reached 28 MB over 9 days (55% base64 images); each wake wrote the continuation, then went silent until the ceiling killed it.
session_statehad acontinuationrow but nocurrent_tool— the SDK never reached a single tool call.Fix
Before resuming, the Claude provider inspects the transcript backing the stored continuation. If it exceeds a size cap (default 12 MB) or age cap (default 14 days, from the first entry's timestamp), it archives a markdown summary to
conversations/(reusing the existing PreCompact archiver) and starts a fresh session. Durable state already lives in the workspace/memory, so little is lost.AgentProvider.maybeRotateContinuation(continuation, cwd)— providers without an on-disk transcript simply omit it.poll-loopcalls it once at startup, right before resume.archiveTranscriptFile().CLAUDE_TRANSCRIPT_ROTATE_BYTES,CLAUDE_TRANSCRIPT_ROTATE_AGE_DAYS.Testing
claude.rotate.test.ts: keeps small/recent transcripts, rotates oversized and aged ones, no-ops on unknown session ids.🤖 Generated with Claude Code