Hmbown/CodeWhale · error · anyhow::Error
hunt_verdict task update must be a string
Error message
hunt_verdict task update must be a string
What it means
While applying task updates, if the updates object contains a 'hunt_verdict' key, its value must be a JSON string (value.as_str()). Any other JSON type — boolean, number, null, array, object — is rejected before normalize_hunt_verdict runs. The verdict string itself is then normalized; invalid strings fail in normalize_hunt_verdict with a separate error.
Source
Thrown at crates/tui/src/task_manager.rs:2126
.context("Failed to parse gate task update")?;
let summary = format!("Gate {} {}: {}", gate.gate, gate.status, gate.summary);
task.gates.retain(|existing| existing.id != gate.id);
task.gates.push(gate.clone());
push_timeline_entry(
task,
TaskTimelineEntry {
timestamp: now,
kind: "gate".to_string(),
summary: summarize_text(&summary, TIMELINE_SUMMARY_LIMIT),
detail_path: gate.log_path,
},
);
}
if let Some(value) = updates.get("hunt_verdict") {
let raw = value
.as_str()
.ok_or_else(|| anyhow!("hunt_verdict task update must be a string"))?;
let verdict = normalize_hunt_verdict(raw)?;
if task.hunt_verdict.as_deref() != Some(verdict) {
task.hunt_verdict = Some(verdict.to_string());
push_timeline_entry(
task,
TaskTimelineEntry {
timestamp: now,
kind: "hunt_verdict".to_string(),
summary: format!("Hunt verdict updated: {verdict}"),
detail_path: None,
},
);
}
}
if let Some(value) = updates.get("attempt") {
let attempt: TaskAttemptRecord = serde_json::from_value(value.clone())
.context("Failed to parse attempt task update")?;View on GitHub (pinned to 0c42157ee5)
Solutions
- Pass the verdict as a JSON string: {"hunt_verdict": "clean"} (use whatever verdict spelling normalize_hunt_verdict accepts).
- Omit the key entirely if no verdict update is needed.
- Validate the updates payload shape before calling the update API.
Example fix
// before
updates = { "hunt_verdict": true }
updates = { "hunt_verdict": { "result": "clean" } }
// after
updates = { "hunt_verdict": "clean" } Defensive patterns
Strategy: type-guard
Type guard
function isValidHuntVerdictUpdate(updates: Record<string, unknown>): boolean {
const v = updates['hunt_verdict'];
return v === undefined || typeof v === 'string';
} Try / catch
if (updates.hunt_verdict !== undefined && typeof updates.hunt_verdict !== 'string') {
updates.hunt_verdict = String(updates.hunt_verdict);
}
try { await applyTaskUpdates(id, updates); }
catch (e) {
if (e.message.includes('hunt_verdict task update must be a string')) { /* fix payload to string, retry once */ }
else throw e;
} Prevention
- Define hunt_verdict in the schema as a plain string enum; reject objects/booleans at the boundary.
- When copying verdicts from structured output, unwrap to the string field before applying updates.
When it happens
Trigger: Applying task updates like {"hunt_verdict": true}, {"hunt_verdict": 1}, or {"hunt_verdict": {"verdict": "clean"}} — the value is present but not a string.
Common situations: A model or integration emits a boolean verdict (true/false) or a nested object instead of the expected string literal such as 'clean' or 'suspicious'.
Related errors
- Task not found: {task_id}
- Task not found: {id}
- agy OAuth token JSON carries no access token member
- fleet task spec must include at least one task
- Model response incomplete: provider stop reason `{}`; the JS
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/a337556a4df986cb.
Report an issue: GitHub.