#3837·paseo

bug: Claude rewind breaks compacted sessions by leaving stale compactMetadata UUIDs

Author: onlyU-uaenaCreated Aug 25, 2026Updated Sep 18, 2026
Labelsbugp2triaged

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 maximum

This 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

  1. Create a Claude session large enough to produce a system/compact_boundary entry with compactMetadata.preservedSegment and compactMetadata.preservedMessages.
  2. Continue the conversation for several turns.
  3. Use Paseo rewind on a previous user message, which calls the standalone SDK forkSession(sessionId, { upToMessageId }) API.
  4. 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:

typescript
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

  1. After forkSession, validate the newest compaction boundary before rebinding/resuming: every UUID used by preservedMessages / preservedSegment must exist in the fork.
  2. Until the SDK fixes this, either:
    • repair those nested fields using the fork entries' forkedFrom.messageUuid -> uuid mapping, or
    • reject rewind with an actionable error instead of resuming a corrupt fork.
  3. 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.
  4. 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.