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

Session is already running: {}

Error message

Session is already running: {}

What it means

The resume_session_with_program function checks if a session is already in the Running state before attempting to resume it. Resuming a running session would spawn a duplicate process and corrupt the state machine. The check fires after the Completed-state guard and before any DB state mutation.

Source

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

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")?,
    };
    spawn_session_runner_for_program(
        &session.task,
        &session.id,

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Check session.state before calling resume — if Running, the session is already active
  2. Use a lock or compare-and-exchange on session state to prevent concurrent resume attempts
  3. Call stop_session first if you need to restart, then resume
  4. Track running sessions in the caller and deduplicate resume requests

Example fix

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

// after
let session = resolve_session(db, &id)?;
if session.state == SessionState::Running {
    tracing::info!("session {} is already running", id);
    return Ok(session.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::Running {
    tracing::info!("session {} is already running, skipping resume", id);
    return Ok(session.id);
}

Type guard

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

Prevention

When it happens

Trigger: Calling resume_session on a session whose state is SessionState::Running — the agent process is still active.

Common situations: Race condition where two callers try to resume the same session. Resume button clicked twice. Session process is alive but the UI lost track of its state.

Related errors


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