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

Session not found

Error message

Session not found: {session_id}

What it means

SessionStore::update_state_and_pid updates a session's state and OS pid in the SQLite sessions table. After executing the UPDATE it checks the affected row count; zero rows means no session with that id exists, so it bails with 'Session not found: {session_id}'. The store refuses to silently no-op writes to nonexistent sessions so callers never operate on stale data.

Solutions

  1. Verify the session id exists (query the sessions table or a get/list API) before calling update_state_and_pid.
  2. Re-fetch the current session list if the id came from cached or earlier-read state; the session was likely deleted concurrently.
  3. Check the id string for typos, trimming, or wrong-source ids (e.g. external task id vs session id).
  4. If 'not found' is an expected outcome in your flow, catch the error and treat it as a no-op rather than retrying.

Example fix

// before
store.update_state_and_pid(&session_id, state, pid)?; // bails if deleted

// after
if store.get_session(&session_id)?.is_some() {
    store.update_state_and_pid(&session_id, state, pid)?;
} else {
    tracing::warn!(session_id, "session already gone; skipping state/pid update");
}
Defensive patterns

Strategy: validation

Validate before calling

fn session_exists(store: &SessionStore, id: &str) -> anyhow::Result<bool> {
    Ok(store.get_session(id)?.is_some())
}
// call site: if !session_exists(&store, &session_id)? { skip/log; return }

Type guard

fn valid_session_id(id: &str) -> bool {
    !id.trim().is_empty() && id.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
}

Try / catch

match store.update_state_and_pid(id, state, pid) {
    Err(e) if e.to_string().contains("Session not found") => warn_and_skip(id),
    other => other?,
}

Prevention

When it happens

Trigger: Calling update_state_and_pid(session_id, ...) with an id that is not in the sessions table — typically a session that was already deleted, a typo'd id, or a session removed by another process (e.g. stop_session_recorded, delete) between listing and updating.

Common situations: A reaper/enforcer loop (enforce_session_heartbeats_with) resumed crashed sessions using ids read earlier while another worker concurrently deleted them; resume flows targeting a session id from a stale CLI arg or config; racing resume_session_with_program against cleanup.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/c7f9537fdf6372f0. Report an issue: GitHub.

Appendix: source

Thrown at ecc2/src/session/store.rs:1305

        pid: Option<u32>,
    ) -> Result<()> {
        let updated = self.conn.execute(
            "UPDATE sessions
             SET state = ?1,
                 pid = ?2,
                 updated_at = ?3,
                 last_heartbeat_at = ?3
             WHERE id = ?4",
            rusqlite::params![
                state.to_string(),
                pid.map(i64::from),
                chrono::Utc::now().to_rfc3339(),
                session_id,
            ],
        )?;

        if updated == 0 {
            anyhow::bail!("Session not found: {session_id}");
        }

        self.refresh_session_board_meta()?;
        Ok(())
    }

    pub fn update_state(&self, session_id: &str, state: &SessionState) -> Result<()> {
        let current_state = self
            .conn
            .query_row(
                "SELECT state FROM sessions WHERE id = ?1",
                [session_id],
                |row| row.get::<_, String>(0),
            )
            .optional()?
            .map(|raw| SessionState::from_db_value(&raw))
            .ok_or_else(|| anyhow::anyhow!("Session not found: {session_id}"))?;

View on GitHub (pinned to 8321021c54)