Hmbown/CodeWhale · error

Agent has no continuable checkpoint to resume from…

Error message

Agent {agent_id} has no continuable checkpoint to resume from (continuable={continuable}, messages={messages})

What it means

Resuming an interrupted sub-agent requires a stored checkpoint marked continuable. This error means the agent exists but its checkpoint is missing, non-continuable, or has no messages to replay, so the resume path fails closed rather than fabricating a fresh run.

Solutions

  1. Inspect the agent's checkpoint state (`continuable`, `messages.len()`) in the error text and confirm whether it should be continuable; if not, spawn a new sub-agent instead of resuming.
  2. Re-run the interrupted work as a fresh sub-agent spawn, carrying the original task prompt.
  3. If resuming mid-workflow, cancel or clean up the stale agent entry (agents/cancel or agents/remove) and re-dispatch.
  4. If checkpoints should persist across restarts, verify the session store is loading saved checkpoints and that no version change dropped them.

Example fix

// before
let out = agents.resume(agent_id)?;

// after
if let Some(agent) = agents.get(&agent_id) {
    let continuable = agent.checkpoint.as_ref().is_some_and(|cp| cp.continuable && !cp.messages.is_empty());
    if !continuable {
        // respawn fresh instead of resuming
        return spawn_new_agent(task);
    }
}
let out = agents.resume(agent_id)?;
Defensive patterns

Strategy: validation

Validate before calling

fn resumable(agent: &SubAgentHandle) -> bool {
    agent.checkpoint.as_ref().is_some_and(|cp| cp.continuable && !cp.messages.is_empty())
}
// call only if resumable(&agent)

Type guard

fn continuable_checkpoint(cp: &Option<Checkpoint>) -> Option<&Checkpoint> {
    cp.as_ref().filter(|c| c.continuable && !c.messages.is_empty())
}

Try / catch

match agents.resume(agent_id) {
    Err(e) if e.to_string().contains("no continuable checkpoint") => spawn_fresh_agent(task),
    other => other?,
}

Prevention

When it happens

Trigger: Calling the agent resume path (agents/resume) for an agent_id whose `agent.checkpoint` is None, or whose checkpoint has `continuable == false`, or whose checkpoint contains zero messages. The check runs before restoring the child's write claim.

Common situations: Resuming after the checkpoint was cleared by a completed/cancelled run; resuming a sub-agent that failed before producing any messages; double-resuming the same checkpoint; upgrading from an older session format that did not persist checkpoints.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/eeef62884d0a4dde. Report an issue: GitHub.

Appendix: source

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

            if !resumable {
                return Err(anyhow!(
                    "Cannot resume agent {agent_id}: status is {} ({})",
                    subagent_status_name(&agent.status),
                    policy.describe()
                ));
            }
            let checkpoint = agent
                .checkpoint
                .as_ref()
                .filter(|cp| cp.continuable && !cp.messages.is_empty())
                .ok_or_else(|| {
                    let continuable = agent.checkpoint.as_ref().is_some_and(|cp| cp.continuable);
                    let messages = agent
                        .checkpoint
                        .as_ref()
                        .map(|cp| cp.messages.len())
                        .unwrap_or(0);
                    anyhow!(
                        "Agent {agent_id} has no continuable checkpoint to resume from (continuable={continuable}, messages={messages})"
                    )
                })?;
            // Restore the interrupted child's write claim so the resumed loop
            // stays inside the coordination ledger with the original bounded
            // scope instead of inheriting the caller's unchecked write surface.
            // The ledger claim is already namespaced and carries the isolation
            // flag; both are passed through to the spawn seam.
            let claim = self
                .coordination
                .write_claims
                .iter()
                .find(|record| record.claim.owner == agent_id)
                .map(|record| (record.claim.clone(), record.isolated_worktree));
            // Preserve the interrupted child's runtime posture (read_only /
            // denied tools / shell) instead of rebuilding from the caller's
            // role, which could widen the resumed child's authority.
            let preserved_profile = self

View on GitHub (pinned to 73e0f67d83)