{"record":{"id":"09965c834ab136b7","repo":"Hmbown/CodeWhale","slug":"thread-not-found","errorCode":null,"errorMessage":"thread {} not found","messagePattern":"thread (.+?) not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/state/src/lib.rs","lineNumber":851,"sourceCode":"            goal.last_gap_pass,\n            u32::try_from(goal.continuation_count.max(0)).unwrap_or(u32::MAX),\n        )\n        .map_err(anyhow::Error::msg)?;\n        let pause_reason = goal\n            .pause_reason\n            .map(|reason| serde_json::to_string(&reason))\n            .transpose()?;\n        let conn = self.conn()?;\n        let exists: Option<i64> = conn\n            .query_row(\n                \"SELECT 1 FROM threads WHERE id = ?1\",\n                params![goal.thread_id],\n                |row| row.get(0),\n            )\n            .optional()\n            .context(\"failed to verify thread before saving goal\")?;\n        if exists.is_none() {\n            anyhow::bail!(\"thread {} not found\", goal.thread_id);\n        }\n\n        conn.execute(\n            r#\"\n            INSERT INTO thread_goals (\n                thread_id, goal_id, objective, status, token_budget, tokens_used,\n                time_used_seconds, continuation_count, created_at, updated_at,\n                last_gap_fingerprint, repeated_gap_count, last_gap_pass, pause_reason\n            ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)\n            ON CONFLICT(thread_id) DO UPDATE SET\n                goal_id=excluded.goal_id,\n                objective=excluded.objective,\n                status=excluded.status,\n                token_budget=excluded.token_budget,\n                tokens_used=excluded.tokens_used,\n                time_used_seconds=excluded.time_used_seconds,\n                continuation_count=excluded.continuation_count,\n                created_at=excluded.created_at,","sourceCodeStart":833,"sourceCodeEnd":869,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/state/src/lib.rs#L833-L869","documentation":"upsert_thread_goal saves a goal attached to a thread, but first verifies the referenced thread row exists. If the SELECT returns no row, it bails with 'thread {id} not found' rather than creating an orphaned goal. This is a foreign-key-style guard implemented in application code.","triggerScenarios":"Calling upsert_thread_goal(conn, goal) with a goal.thread_id that has no corresponding row in the threads table (typo'd id, thread deleted before the goal write, id from a different database).","commonSituations":"UI kept a stale thread id after the thread was deleted; goal persisted from a different session/workspace db; race where another process removed the thread between listing and saving; test fixtures referencing ids never inserted.","solutions":["Check the thread id is valid: SELECT id FROM threads WHERE id = ? before the call.","Create the thread first (or restore it) before saving the goal.","If the thread was deleted intentionally, drop or re-parent the goal instead of saving it.","Confirm you're connected to the same database the thread was written to.","For races, wrap the exists-check and insert in one transaction and handle the constraint failure."],"exampleFix":"// before: saving with a possibly stale id\nstate.upsert_thread_goal(&goal)?;\n// after: verify or create the thread first\nif !state.thread_exists(&goal.thread_id)? {\n    state.create_thread(&goal.thread_id)?;\n}\nstate.upsert_thread_goal(&goal)?;","handlingStrategy":"validation","validationCode":"fn thread_exists(conn: &Connection, id: &str) -> rusqlite::Result<bool> {\n    conn.query_row(\"SELECT 1 FROM threads WHERE id = ?1\", params![id], |_| Ok(true))\n        .optional().map(|o| o.unwrap_or(false))\n}\n// call: ensure thread_exists(conn, &goal.thread_id)? before upserting","typeGuard":"fn goal_has_live_thread(conn: &Connection, goal: &Goal) -> bool {\n    thread_exists(conn, &goal.thread_id).unwrap_or(false)\n}","tryCatchPattern":"match state.upsert_thread_goal(&goal) {\n    Err(e) if e.to_string().contains(\"not found\") => {\n        state.create_thread(&goal.thread_id)?;\n        state.upsert_thread_goal(&goal)?;\n    }\n    r => r?,\n}","preventionTips":["Create the thread row before any goal referencing it","Purge goals when their thread is deleted (cascade)","Never cache thread ids across database files","Wrap exists-check + insert in a transaction to avoid races"],"tags":["database","foreign-key","sqlite"],"backgroundTag":"entity-not-found","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}