zeroclaw-labs/zeroclaw · error · anyhow::Error
goal task {task_id} has no canonical TaskKind::Goal row and
Error message
goal task {task_id} has no canonical TaskKind::Goal row and goal extension What it means
ensure_goal_task_row is the fail-fast guard at the start of goal lifecycle transactions (pause_goal_task, resume_goal_task, set/get_continuation_context). It verifies the canonical TaskKind::Goal row AND its goal extension row exist; if either is missing the operation is refused before any status mutation, so no partial state is written.
Source
Thrown at crates/zeroclaw-runtime/src/control_plane/task_store_sqlite/goal.rs:340
}
fn ensure_goal_task_row(conn: &Connection, task_id: &str) -> Result<()> {
let exists = conn
.query_row(
"SELECT EXISTS(
SELECT 1
FROM tasks
JOIN goal_tasks ON goal_tasks.task_id = tasks.id
WHERE tasks.id = ?1
AND tasks.kind = 'goal'
)",
params![task_id],
|row| row.get::<_, i64>(0),
)
.context("verify goal task row")?
!= 0;
if !exists {
anyhow::bail!("goal task {task_id} has no canonical TaskKind::Goal row and goal extension");
}
Ok(())
}
fn upsert_continuation_context(
conn: &Connection,
task_id: &str,
context: &TaskContinuationContext,
) -> Result<()> {
conn.execute(
"INSERT INTO task_continuation_contexts (task_id, context_json)
VALUES (?1, ?2)
ON CONFLICT(task_id) DO UPDATE
SET context_json = excluded.context_json",
params![task_id, continuation_context_to_db(context)?],
)
.context("upsert task continuation context")?;
Ok(())View on GitHub (pinned to 88bb9c8533)
Solutions
- Verify the id exists and is TaskKind::Goal via the task list/get API before calling lifecycle methods
- If the extension row is missing, recreate the goal through the proper creation API rather than hand-INSERTing
- Check for concurrent deletion of the task
- Add a pre-flight check in operator tooling that lists goals and their extension status
Defensive patterns
Strategy: validation
Validate before calling
// pre-flight before lifecycle calls
if !matches!(store.get_task(id).await, Ok(Some(ref t)) if t.kind == TaskKind::Goal) {
anyhow::bail!("{id} is not a goal task; nothing to pause/resume");
}
store.pause_goal_task(id, pause).await?; Type guard
async fn is_goal_task(store: &SqliteTaskStore, id: &str) -> bool {
matches!(store.get_task(id).await, Ok(Some(t)) if t.kind == TaskKind::Goal)
} Try / catch
match store.pause_goal_task(id, pause).await {
Err(ref e) if e.to_string().contains("has no canonical TaskKind::Goal row") => {
// wrong id or structurally incomplete goal: verify and recreate via the creation API
}
other => other,
} Prevention
- Select goal ids from the listing API, never hand-type them
- Create goals only through the creation API so canonical + extension rows are written atomically
- Never hand-edit the SQLite store
- Fail lifecycle tooling fast on unknown ids
When it happens
Trigger: Calling a goal lifecycle method with a task id that is not a goal task (wrong id, regular task, cross-environment paste) or whose goal extension row is missing (partial insert, manual DB edit, schema drift after migration).
Common situations: Task ids copied between environments; databases migrated or hand-edited; goals created through a path that skipped the extension insert; races with concurrent deletion.
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
- effective cost limit must be a non-negative finite value
- goal task {task_id} has no goal extension row
- goal task {task_id} is terminal or missing
- purge_agent not supported by this memory backend
- rename_agent not supported by this memory backend
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/8dff8ecc7d46e411.
Report an issue: GitHub.