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

Session is already running

Error message

Session is already running: {}

What it means

resume_session_with_program refuses to resume a session that is already in the Running state, since resuming would start a second concurrent execution of the same session. The guard prevents duplicate processes and pid/state corruption.

Solutions

  1. Check the session state and skip if already Running
  2. Wait for the running session to finish before resuming
  3. If the state is stuck as Running due to a crash, reset state (update_state_and_pid) after verifying no live process holds the pid
  4. Serialize resume calls with a lock so only one worker resumes a given session

Example fix

// before
resume_session(db, id)?;
// after
let session = resolve_session(db, id)?;
if session.state != SessionState::Running {
    resume_session(db, id)?;
} else {
    eprintln!("session {} already running; skipping", id);
}
Defensive patterns

Strategy: validation

Validate before calling

let session = resolve_session(db, id)?;
if session.state == SessionState::Running {
    return Err(format!("session {} is already running", id));
}

Type guard

fn is_stopped(s: &Session) -> bool { !matches!(s.state, SessionState::Running) }

Try / catch

match resume_session(db, id) {
    Err(e) if e.to_string().contains("already running") => { /* poll until finished or skip */ }
    other => other?,
}

Prevention

When it happens

Trigger: Calling resume_session(id) (or via resume_session_requeues_failed_session) while the session's stored state is SessionState::Running.

Common situations: Double-clicking a resume button; a scheduler re-triggering resume while the first process is still alive; a crashed process left the pid registered and the state Running; concurrent automation workers both trying to resume the same session.

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 affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/0fe1ee7c1525cbcb. Report an issue: GitHub.

Appendix: 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 8321021c54)