Hmbown/CodeWhale · error · anyhow::Error

unsupported hunt_verdict task update '{other}'. Expected one

Error message

unsupported hunt_verdict task update '{other}'. Expected one of: hunting, hunted, wounded, escaped

What it means

normalize_hunt_verdict accepts exactly four lowercase verdict strings for hunt-style task updates: hunting, hunted, wounded, escaped. The input is trimmed before matching, but any other value (typos, uppercase forms like 'Hunted', or future verdicts) is rejected with this bail before any task mutation is applied.

Source

Thrown at crates/tui/src/task_manager.rs:2230

            &QueueFile {
                queue: queue.iter().cloned().collect(),
            },
        )
    }

    fn persist_task_locked(&self, task: &TaskRecord) -> Result<()> {
        let path = self.tasks_dir.join(format!("{}.json", task.id));
        write_json_atomic(&path, task)
    }
}

fn normalize_hunt_verdict(raw: &str) -> Result<&'static str> {
    match raw.trim() {
        "hunting" => Ok("hunting"),
        "hunted" => Ok("hunted"),
        "wounded" => Ok("wounded"),
        "escaped" => Ok("escaped"),
        other => bail!(
            "unsupported hunt_verdict task update '{other}'. Expected one of: hunting, hunted, wounded, escaped"
        ),
    }
}

/// Outcome of loading the persisted task store at boot: the reconciled task
/// map + queue, plus the ids whose status was flipped running->failed by
/// crash recovery (the only records boot needs to re-persist).
struct LoadedTaskState {
    tasks: HashMap<String, TaskRecord>,
    queue: VecDeque<String>,
    recovered: Vec<String>,
}

fn load_state(tasks_dir: &Path, queue_path: &Path) -> Result<LoadedTaskState> {
    let mut tasks = HashMap::new();
    let mut recovered = Vec::new();
    if tasks_dir.exists() {

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Map your statuses to the literal set: hunting, hunted, wounded, escaped (lowercase, surrounding whitespace is tolerated).
  2. Normalize input to trim + lowercase before validation so 'Hunted ' passes.
  3. If you believe a new verdict is genuinely needed, add it to normalize_hunt_verdict in the product code and its tests rather than string-matching client-side.

Example fix

// before
update_task(id, json!({"hunt_verdict": verdict.as_str()})).await?; // 'HUNTED' fails

// after
let verdict = verdict.trim().to_ascii_lowercase();
if !matches!(verdict.as_str(), "hunting" | "hunted" | "wounded" | "escaped") {
    return Err(anyhow!("unknown hunt verdict: {verdict}"));
}
update_task(id, json!({"hunt_verdict": verdict})).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn normalize_verdict_or_err(raw: &str) -> Result<&'static str> {
    match raw.trim() {
        "hunting" | "hunted" | "wounded" | "escaped" => Ok(raw.trim()),
        other => Err(anyhow!("unknown hunt verdict: {other}")),
    }
}

Type guard

fn is_hunt_verdict(s: &str) -> bool {
    matches!(s.trim(), "hunting" | "hunted" | "wounded" | "escaped")
}

Prevention

When it happens

Trigger: Sending a task update with hunt_verdict set to a value outside the four-element set, e.g. 'hunts', 'HUNTED', 'in-progress', or 'killed'; forwarding verdicts from an external tool whose vocabulary differs.

Common situations: Integrating a custom status enum that maps 1:1 except for one or two names; schema drift after a rename; hand-crafted JSON payloads in scripts or tests using informal statuses.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/e6325035f6876a11. Report an issue: GitHub.