Hmbown/CodeWhale · error · anyhow::Error
Task not found: {task_id}
Error message
Task not found: {task_id} What it means
TaskManager::get_task_for_active_runtime looks up a task by its full durable id, stamped onto a trusted runtime thread rather than model input. The lookup fails closed in two cases: the id is absent from the task map, or the record has owner_session_id == None (a legacy ownerless task). So even a restored, active task without an owner is rejected.
Source
Thrown at crates/tui/src/task_manager.rs:1423
id_or_prefix: &str,
owner_session_id: &str,
) -> Result<TaskRecord> {
self.get_task_visible_to(id_or_prefix, Some(owner_session_id))
.await
}
/// Retrieve the exact owned task stamped onto a trusted runtime thread.
///
/// The runtime thread supplies a full durable id rather than model input.
/// Legacy ownerless tasks fail closed even when restored as active.
pub(crate) async fn get_task_for_active_runtime(&self, task_id: &str) -> Result<TaskRecord> {
let state = self.state.lock().await;
state
.tasks
.get(task_id)
.filter(|task| task.owner_session_id.is_some())
.cloned()
.ok_or_else(|| anyhow!("Task not found: {task_id}"))
}
async fn get_task_visible_to(
&self,
id_or_prefix: &str,
owner_session_id: Option<&str>,
) -> Result<TaskRecord> {
let state = self.state.lock().await;
let id = resolve_task_id_visible_to(&state.tasks, id_or_prefix, owner_session_id)?;
state
.tasks
.get(&id)
.cloned()
.ok_or_else(|| anyhow!("Task not found: {id_or_prefix}"))
}
/// Cancel a queued or running task by id/prefix.
pub async fn cancel_task(&self, id_or_prefix: &str) -> Result<TaskCancellation> {View on GitHub (pinned to 0c42157ee5)
Solutions
- Use the exact task id returned by the enqueue/creation call in the same session — not a prefix or an id from a prior session.
- If the record is legacy (no owner), re-create the task so it is stamped with the current session id.
- Check the task still exists via a list/counts call before the runtime thread dereferences it.
- Migrate old task records to backfill owner_session_id if legacy data must stay usable.
Defensive patterns
Strategy: validation
Validate before calling
// Validate before use: task must exist and carry an owner session id.
let state = task_manager.snapshot().await;
let ok = state.tasks.get(&task_id)
.is_some_and(|t| t.owner_session_id.is_some());
anyhow::ensure!(ok, "task {task_id} missing or legacy ownerless; re-create it");
let task = task_manager.get_task_for_active_runtime(&task_id).await?; Try / catch
match task_manager.get_task_for_active_runtime(&id).await {
Ok(task) => task,
Err(e) if e.to_string().contains("Task not found") => {
// fail closed: re-enqueue the work instead of retrying the stale id
reenqueue_work().await?
}
Err(e) => return Err(e.into()),
} Prevention
- Thread the exact durable id from creation into runtime threads — never reconstruct or truncate it.
- Re-create legacy ownerless tasks after upgrading instead of reusing them.
- Treat 'Task not found' on this path as terminal for that id; do not loop on it.
When it happens
Trigger: A runtime thread references a task id that was pruned, belongs to a rotated store, or predates owner tracking (owner_session_id is None). Passing a prefix is not supported here — only exact ids resolve.
Common situations: Upgrading from an older version whose persisted task records lack owner_session_id; session restore picking up legacy active tasks; referencing a task id after its record was garbage-collected.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- Task not found: {id}
- hunt_verdict task update must be a string
- No saved sessions found for workspace {}.
- No saved sessions found.
- MCP server '{name}' not found
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/7c8a101f0faa74c4.
Report an issue: GitHub.