bug: Claude rewind breaks compacted sessions by leaving stale compactMetadata UUIDs
Summary
Rewinding a long Claude session that has already been compacted can invalidate the compaction boundary in the forked transcript. On the next turn, Claude reconstructs the pre-compaction history and may fail with:
prompt is too long: 1058964 tokens > 1000000 maximumThis was reproduced from a real Paseo session and traced through both Paseo and @anthropic-ai/claude-agent-sdk source.
Environment
- Paseo daemon at incident time: 0.5.1
- Confirmed unchanged in Paseo 0.5.2 and current
main @anthropic-ai/claude-agent-sdk: 0.3.220- Claude Code: 2.1.235
- macOS arm64
Reproduction
- Create a Claude session large enough to produce a
system/compact_boundaryentry withcompactMetadata.preservedSegmentandcompactMetadata.preservedMessages. - Continue the conversation for several turns.
- Use Paseo rewind on a previous user message, which calls the standalone SDK
forkSession(sessionId, { upToMessageId })API. - Send a message in the forked session.
Expected behavior
The fork preserves the effective post-compaction context and remains within the model context limit.
Actual behavior
The compacted-away history becomes active again. In the observed session:
- Last compaction: 840,905 pre-compaction tokens -> 10,352 post-compaction tokens
- First request after rewind: 1,058,964 tokens
- Retry: 1,058,990 tokens
Root cause
Paseo delegates conversation rewind directly to the SDK:
const fork = await forkSession(sessionId, { upToMessageId: messageId });
setSessionId(fork.sessionId);The SDK fork implementation creates an old-to-new UUID map and rewrites top-level uuid, parentUuid, and logicalParentUuid. However, each entry is shallow-copied, so UUID references nested inside compactMetadata remain unchanged:
compactMetadata.preservedSegment.headUuid
compactMetadata.preservedSegment.anchorUuid
compactMetadata.preservedSegment.tailUuid
compactMetadata.preservedMessages.anchorUuid
compactMetadata.preservedMessages.uuids[]
compactMetadata.preservedMessages.allUuids[]In the observed fork, every critical reference in the newest compaction boundary pointed to a UUID that no longer existed in the fork. Claude Code then aborted compaction relinking because the listed preserved UUIDs were missing, leaving the pre-compaction chain active.
Replaying the transcript with Claude's chain-selection logic produced:
- Broken fork as written: 3,053 active chain entries
- Same fork with nested compaction UUIDs remapped: 794 active chain entries
The latest published SDK checked during investigation (0.3.245) still appears to use the same shallow-copy fork logic, so an SDK version bump alone does not currently address this.
Secondary symptom: orphaned background task notification
If a background shell command started before the rewind target and is killed while Paseo retires the old Claude process, the fork contains the start record but not the later terminal record from the original session. Claude then emits:
No completion record was found for this background shell command from the previous session.This notification is not the cause of the context overflow, but it is another observable rewind artifact.
Suggested fix
- After
forkSession, validate the newest compaction boundary before rebinding/resuming: every UUID used bypreservedMessages/preservedSegmentmust exist in the fork. - Until the SDK fixes this, either:
- repair those nested fields using the fork entries'
forkedFrom.messageUuid -> uuidmapping, or - reject rewind with an actionable error instead of resuming a corrupt fork.
- repair those nested fields using the fork entries'
- Add a regression fixture containing a real compact boundary, rewind it, and assert that the effective chain starts at the boundary rather than restoring pre-compaction messages.
- Separately, terminalize or explicitly discard in-flight background-task records when switching to the fork.
Existing test gap
The current Claude rewind unit test only verifies that Paseo calls forkSession with the expected message ID. The real-provider rewind matrix covers only one to three ordinary turns; neither path exercises a compacted transcript.
Related but not identical: #439.
Source: getpaseo/paseo