Repository navigation
fix: forks overflow the model's window and then stay stuck on "Prompt is too long" - #381
Merged
Merged
Conversation
… is too long" A fork carries the parent's conversation into its first prompt, sized at 3.5 chars/token. Code and tool output run ~2.25, so a 2.36M-char seed came to ~1.09M tokens against a 1M window. Every later send resumed the backing session holding that prompt and failed the same way, and /rotate refused to run because the session had too few turns. - Size the seed at 2 chars/token, configurable via session.seedBudgetChars / seedCharsPerToken / seedWindowFraction (and CODEOID_SEED_* env vars). - When a seeded first turn overflows, retry it on a fresh backing session with a halved seed (up to 3 times) without re-posting the user's message. - Let /rotate skip its min-turns guard after an overflow or in error state, including after a daemon restart. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Collaborator
Author
|
pr-shepherd: CI is green at 4138401 (daemon-check, web-check, Socket Security x2 all SUCCESS). Branch is current with main. No auto-fixes were needed, nothing pushed. Ready for human review. |
rsharath
approved these changes
Oct 15, 2026
saucam
added a commit
that referenced
this pull request
Oct 17, 2026
…on Claude and Codex (#388) * feat: forks, switches and rewinds continue natively on Claude and Codex A fork, backend switch or rewind used to hand Claude, Codex and pi a text transcript in the first prompt (tool I/O cut to 2,000 chars, reasoning dropped), even when nothing changed backend. Now the session asks the backend to continue natively first (#387): - same backend: the backend forks its own conversation at the turn (Claude Agent SDK forkSession; Codex thread/fork), from a native point every turn now records (NormalizedTurnResult.nativeRef, stamped on the canonical turn) — nothing converted or cut, reasoning kept; - another backend: native history built from the canonical turns — a Claude session file with real tool_use/tool_result blocks, verified with the SDK's own reader before use, or Codex items via thread/inject_items. The transcript is the fallback (pi, the Gemini CLI, any failure), the session says which way it went, and session.nativeContinuation / CODEOID_NATIVE_CONTINUATION turns it off. Also fixes Codex never saving its thread id, which silently emptied every Codex conversation on a daemon restart, and marks a switched-to backend fresh until its first prompt so a restart re-seeds it. Closes #387 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: native continuation round 1 — never fork past a redaction, keep restored turns quoted, pin and validate fork points Security review: a native fork copies the backend's own log, which still holds what a tool_result hook redacted from codeoid's record, so a fork is not used once any carried tool call was redacted (codeoid's record is rebuilt natively instead). Turns rebuilt from a displayed transcript (an import) stay a quoted transcript rather than native history in the assistant's voice. Native refs are shape-checked, and Claude's fork lookup is pinned to the source session's project dir. The session file is written with wx into an owner-only dir and its temp file removed on failure. The Codex fallback notice no longer forwards backend error text to clients. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: native continuation round 1 — identity on native forks, no fork into an overflow, recovery reseeds Correctness review: - A fork that adopted a native backing counted as having queried, so its ZeroID agent identity was never registered and model calls fell back to the shared badge. Identity now registers on the first send after any native continuation. - No native fork from an errored, interrupted or budget-stopped turn (no nativeRef is stamped), nor when the source's last context was within 85% of the target's window: a fork stays the way out of an overflowed session (#381), with budgeted native history instead. - A Claude fork is read back before it is adopted, and Claude's 'No conversation found' recovery now reseeds the new session from the conversation instead of silently keeping only the latest prompt. - Stale turn anchors are cleared on a session reset or loop rebuild; a rewind to the start clears the native flag; long workdir paths are named exactly as the SDK names them; Codex anchors only completed turns, forks with excludeTurns, and can't adopt a thread after a teardown. - Settings UI exposes session.nativeContinuation; docs cover qwen and how this sits with CODEOID_CONTEXT_STRATEGY. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: native continuation round 2 — redaction sticks to the backing, durably; imported sessions may still fork - A tool_result redaction now marks the BACKING, not just the call: every later turn of it carries native.redacted, the set is written to the session meta the moment a redaction happens (so a crash before the turn ends can't forget it), restored on resume, and inherited by forks. A partial restart load, log compaction or a squeezed giant turn can no longer hide the redaction from the fork guard. - Turns rebuilt from a displayed transcript block only BUILDING native history (allowHistory: false); a fork of the backend's own record is still allowed. - With no usage on record, whether a fork fits is estimated from the history instead of assumed. - Claude recovery: a transcript seed it applies records the prompt it rides (so an overflow retries there), and adopting a backing doesn't re-arm recovery. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: native continuation round 3 — backing recovery can't loop, redaction flag can't leak into the next turn - Claude's 'No conversation found' recovery latch no longer clears when the backing is reset (recovery resets it itself, then may adopt a resumable native backing): it clears only after a turn on the new backing succeeds. If that backing is missing too, the turn now fails instead of recovering in a loop. (The round-2 guard in #adoptBacking was dead: the reset ran before it.) - The per-turn redaction flag is cleared when a new turn starts, so a redaction in a turn that never reached turn_done can't mark the next turn's backing. - A late tool result can't rewrite a destroyed session's meta. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: test that backing recovery re-arms after a successful turn, and keep the zero-turn docs on their tests Round-4 audit: clean. Folds its two cheap lows. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * docs: native continuation final-audit pass — docs and comments match the code providers-pi.md no longer says Claude can't take native history; FEATURES states the imported-turns rule as it is (never built into native history, a later real turn may still fork) and only claims a notice on fork/switch; COMPARISON and CHANGELOG name every transcript backend and the recovery fix; #seedProviderFromHistory and the Codex header describe the native path. On a switch the continuation notice now follows the switch line, and its transcript wording no longer says the backend can't (the feature may be off). Codex's thread config is one helper; small type and naming tidy-ups. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
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.
What happened
The
interop-testing (fork)session failed on its very first message with "Prompt is too long", and then on every message after that.When you fork a session, codeoid copies the parent's conversation into the new session's first prompt. It decides how much fits by assuming 3.5 characters per token. This conversation was mostly code and tool output, which comes out closer to 2.25. So a history sized to fit a 1M-token window (2.36M characters) was really about 1.09M tokens, and the request was rejected.
The session then couldn't recover. Each later message resumed the same backing Claude session, which still held the oversized prompt.
/rotate, the usual way out, refused to run because the session had "only 2 turns".What changes
/rotate./rotateworks on a stuck session. It skips its minimum-turn check when the last turn overflowed or the session is in an error state. This also holds after a daemon restart, so existing stuck sessions can be rotated once this is deployed.sessionsettings, also shown under advanced in the settings UI:seedBudgetCharsCODEOID_SEED_BUDGET_CHARS0(worked out from the window)seedCharsPerTokenCODEOID_SEED_CHARS_PER_TOKEN2seedWindowFractionCODEOID_SEED_WINDOW_FRACTION0.7CODEOID_SEED_BUDGET_CHARSalready existed as a.env-only setting. It is now a config key too, and the env var still takes precedence.Testing
src/tests/session-seed-overflow.test.ts(9 tests): the retry with halving, giving up after 3 attempts,/rotateafter an overflow (including after a restore),/rotatestill refused on a healthy young session, no retry when no history was carried over, and the config settings.main: 3127 passed, 0 failed.For agents
src/daemon/providers/context-windows.ts:SEED_CHARS_PER_TOKENchanged from 3.5 to 2.seedBudgetChars()takesSeedBudgetOptions(capChars,charsPerToken,windowFraction). Precedence:CODEOID_SEED_BUDGET_CHARSat call time, thenopts.capChars, then the computed value. AddsisPromptTooLongError().src/daemon/session.ts:#seedProviderFromHistory(retry?)records#pendingSeed(historyLength,budgetChars,attempts), but only for transcript seeds a warm backend actually stashed (outcome.truncationis set).#sendInnerstampsprompt,senderandturn(id/text/at) on the pending seed when the first fresh turn starts.turn_donehandler: an error that is prompt-too-long (fromerrorMessage, or this turn's committed assistant text) with a pending seed andattempts < SEED_OVERFLOW_MAX_RETRIESsets#seedOverflowRetryand skips the error message and#lastTurnError. Thefinallyin#consumeEventsthen calls#scheduleSeedOverflowRetry().#sendChain. It tears the provider down, callsresetToNewSessionand persists the new backing id, truncates the accumulator tohistoryLength, and re-seeds at half the budget. It then restores#currentTurnIdto the original turn (already indexed) and callsrunTurnwith the original prompt.#pendingSeed.manualRotatebypasses min-turns when#promptTooLongis set or status iserror.restoreScrollbacksets#promptTooLongwhen the last assistant row is the overflow.src/config.ts: addssession.seedBudgetChars/seedCharsPerToken/seedWindowFractionto the schema, the type andENV_OVERRIDES.src/daemon/settings/manifest.ts: the env-onlyCODEOID_SEED_BUDGET_CHARSfield is replaced by three config-backed fields.🤖 Generated with Claude Code