affaan-m/ECC · error · anyhow::Error

Completed sessions cannot be resumed: {}

Error message

Completed sessions cannot be resumed: {}

What it means

The resume_session_with_program function guards against resuming a session whose state is Completed. A completed session has finished its lifecycle and cannot be resumed — its worktree may have been merged, its process has exited, and its state is final. The check fires before any state mutation or process spawning.

Source

Thrown at ecc2/src/session/manager.rs:1516

        .ok_or_else(|| anyhow::anyhow!("Session not found: {session_id}"))?;

    ToolLogger::new(db).query(&session.id, page, page_size)
}

pub async fn resume_session(db: &StateStore, cfg: &Config, id: &str) -> Result<String> {
    resume_session_with_program(db, cfg, id, None).await
}

async fn resume_session_with_program(
    db: &StateStore,
    _cfg: &Config,
    id: &str,
    runner_executable_override: Option<&Path>,
) -> Result<String> {
    let session = resolve_session(db, id)?;

    if session.state == SessionState::Completed {
        anyhow::bail!("Completed sessions cannot be resumed: {}", session.id);
    }

    if session.state == SessionState::Running {
        anyhow::bail!("Session is already running: {}", session.id);
    }

    db.update_state_and_pid(&session.id, &SessionState::Pending, None)?;
    if let Some(worktree) = session.worktree.as_ref() {
        if let Err(error) = worktree::sync_shared_dependency_dirs(worktree) {
            tracing::warn!(
                "Shared dependency cache sync warning for resumed session {}: {error}",
                session.id
            );
        }
    }
    let runner_executable = match runner_executable_override {
        Some(program) => program.to_path_buf(),
        None => std::env::current_exe().context("Failed to resolve ECC executable path")?,

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Create a new session instead of resuming a completed one
  2. Check session.state == SessionState::Completed before calling resume and show a user-facing message
  3. If you need to re-run the same task, clone the session configuration into a new session
  4. Track terminal state in the caller so resume is only offered for non-terminal sessions

Example fix

// before
resume_session(db, cfg, &id).await?;

// after
let session = resolve_session(db, &id)?;
if session.state == SessionState::Completed {
    return Err(anyhow!("Session {} is completed; create a new session to re-run", id));
}
resume_session(db, cfg, &id).await?;
Defensive patterns

Strategy: type-guard

Validate before calling

let session = resolve_session(db, id)?;
if session.state == SessionState::Completed {
    return Err(anyhow!("Session {} is already completed. Create a new session to re-run.", id));
}

Type guard

fn is_resumable(session: &Session) -> bool {
    !matches!(session.state, SessionState::Completed | SessionState::Running)
}

Prevention

When it happens

Trigger: Calling resume_session(db, cfg, id) or resume_session_with_program(...) for a session whose state is SessionState::Completed.

Common situations: Trying to resume a session that finished normally. Replaying a session ID from logs after it already completed. UI 'resume' button that does not check terminal state first.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/ca8fcb1e9bc518d8. Report an issue: GitHub.