Hmbown/CodeWhale · error

sub-agent transcript history shrank from {} to {} messages

Error message

sub-agent transcript history shrank from {} to {} messages

What it means

The sub-agent transcript persister enforces an append-only invariant: `persisted_messages` counts the JSONL lines already on disk, and `sync_messages` errors when the incoming message slice is shorter than that count. Hitting it means the message history handed to the persister dropped messages that were already written — an integrity violation, not normal compaction. Treat it as a bug in history management or in a fork/replay path.

Source

Thrown at crates/tui/src/tools/subagent/mod.rs:7518

        let relative_path = subagent_transcript_artifact_relative_path(agent_id);
        let path = checked_subagent_transcript_artifact_path(&state_root, agent_id)?;
        let header = json!({
            "kind": "subagent_transcript_header",
            "schema_version": SUBAGENT_TRANSCRIPT_ARTIFACT_SCHEMA_VERSION,
            "agent_id": agent_id,
        });
        create_private_subagent_transcript(&state_root, &path, &json_line(&header)?)?;
        Ok(Self {
            state_root,
            path,
            relative_path,
            persisted_messages: 0,
        })
    }

    fn sync_messages(&mut self, messages: &[Message], durable: bool) -> Result<()> {
        if messages.len() < self.persisted_messages {
            return Err(anyhow!(
                "sub-agent transcript history shrank from {} to {} messages",
                self.persisted_messages,
                messages.len()
            ));
        }

        let mut encoded = Vec::new();
        for (index, message) in messages.iter().enumerate().skip(self.persisted_messages) {
            encoded.extend(json_line(&json!({
                "kind": "message",
                "index": index,
                "message": message,
            }))?);
        }

        if !encoded.is_empty() || durable {
            append_private_subagent_transcript(&self.state_root, &self.path, &encoded, durable)?;
        }

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Never remove or reorder messages passed to sync_messages — append only.
  2. If on-disk history must shrink, create a fresh transcript artifact (new header, persisted_messages reset to 0) instead of syncing a shorter slice.
  3. Audit compaction/fork paths: either preserve the full message count or fork before the first sync.
  4. After an upgrade, confirm persisted_messages is initialized from the existing artifact's line count, not from an empty default.

Example fix

// before: compaction trims history in place, then syncs
let compacted = messages[len/2..].to_vec();
persister.sync_messages(&compacted, true)?; // Err 1202

// after: shrinking on disk requires a new artifact
let fresh = TranscriptArtifact::create(state_root, agent_id, &header)?;
fresh.sync_messages(&full_history, true)?; // full slice, append-only
Defensive patterns

Strategy: try-catch

Validate before calling

// Before syncing, assert the append-only invariant yourself:
debug_assert!(messages.len() >= persister.persisted_messages(),
    "history would shrink: {} -> {}", persister.persisted_messages(), messages.len());

Type guard

fn history_is_append_only(persisted: usize, incoming: &[Message]) -> bool {
    incoming.len() >= persisted
}

Try / catch

if let Err(e) = persister.sync_messages(&messages, durable) {
    if e.to_string().contains("history shrank") {
        // integrity bug: do NOT truncate the file to match; log, keep the artifact,
        // and rebuild the in-memory history from the artifact instead.
    }
}

Prevention

When it happens

Trigger: Calling sync_messages with a Message vec shorter than persisted_messages: in-place history compaction that trims old messages, a fork/checkpoint restore that rebuilds a truncated context, or a refactor that filters messages before syncing.

Common situations: Wiring context-window compaction into the sub-agent loop; resuming from checkpoints that reconstruct fewer messages than the transcript contains; changing message filtering between syncs.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/ab36915dde830a61. Report an issue: GitHub.