zeroclaw-labs/zeroclaw · error · anyhow::Error

goal task {task_id} has no goal extension row

Error message

goal task {task_id} has no goal extension row

What it means

update_goal_objective runs an UPDATE against the goal extension table and checks rows affected. Zero affected rows means no goal extension row exists for that task id, so the objective change is refused instead of silently no-opping — the caller learns the goal is structurally incomplete.

Source

Thrown at crates/zeroclaw-runtime/src/control_plane/task_store_sqlite/goal.rs:492

            params![task_id],
            row_to_goal_task,
        )
        .optional()
        .context("get goal task")
    }

    async fn update_goal_objective(&self, task_id: &str, objective: &str) -> Result<()> {
        let conn = self.conn.lock();
        let updated = conn
            .execute(
                "UPDATE goal_tasks
                    SET objective = ?1
                  WHERE task_id = ?2",
                params![objective, task_id],
            )
            .context("update goal objective")?;
        if updated == 0 {
            anyhow::bail!("goal task {task_id} has no goal extension row");
        }
        Ok(())
    }

    async fn update_goal_limits(
        &self,
        task_id: &str,
        token_limit: Option<u64>,
        cost_limit_usd: Option<f64>,
    ) -> Result<()> {
        let (effective_token_limit, effective_cost_limit_usd) =
            goal_limits_to_db(token_limit, cost_limit_usd)?;
        let conn = self.conn.lock();
        let updated = conn
            .execute(
                "UPDATE goal_tasks
                SET effective_token_limit = ?1,
                    effective_cost_limit_usd = ?2

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Confirm the goal was created via the goal creation API (it writes both rows atomically)
  2. Recreate the goal if the extension row is missing
  3. Fix fixtures/scripts that insert task rows without the paired extension
Defensive patterns

Strategy: validation

Validate before calling

if !goal_extension_exists(&store, id).await? {
    anyhow::bail!("goal {id} lacks an extension row; recreate it before updating");
}
store.update_goal_objective(id, objective).await?;

Try / catch

match store.update_goal_objective(id, obj).await {
    Err(ref e) if e.to_string().contains("has no goal extension row") => {
        // treat as structurally-missing entity: recreate the goal, then reapply the objective
    }
    other => other,
}

Prevention

When it happens

Trigger: update_goal_objective on a task whose canonical row exists but whose goal extension row does not: partial creation, manual INSERT of only the canonical row, corrupted store, or concurrent extension deletion.

Common situations: Stores upgraded across schema versions where extension inserts were missed; test fixtures inserting canonical rows directly; admin scripts mutating the SQLite file.

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


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/28129b4a86845900. Report an issue: GitHub.