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
- Verify the session id exists (query the sessions table or a get/list API) before calling update_state_and_pid.
- Re-fetch the current session list if the id came from cached or earlier-read state; the session was likely deleted concurrently.
- Check the id string for typos, trimming, or wrong-source ids (e.g. external task id vs session id).
- 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
- Always resolve ids from a fresh store query, never from cached snapshots.
- Check existence before mutating in enforcer/resume loops.
- Treat not-found during cleanup as a benign race and skip, not retry.
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
- dispatch transition lost
- unknown claim token
- a current bound approved draft is required
- a top-level committed transaction is required
- Another ECC process is updating Claude settings
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)