tinyhumansai/openhuman · warning
missing `cards` for op=replace
Error message
missing `cards` for op=replace
What it means
The `todo` tool dispatched op=replace but args has no `cards` key (todo.rs:156). replace swaps the entire board for the provided card list, so cards is mandatory for that op — clearing the board is a separate op=clear, not an empty replace.
Source
Thrown at src/openhuman/agent/tools/todo.rs:156
let id = required_string(&args, "id")?;
let mut patch = patch_from_args(&args)?;
patch.content = optional_string(&args, "content");
ops::edit(&location, &id, patch).await
}
"update_status" => {
let id = required_string(&args, "id")?;
let status = required_string(&args, "status")?;
let status = ops::parse_status(&status).map_err(anyhow::Error::msg)?;
ops::update_status(&location, &id, status).await
}
"remove" => {
let id = required_string(&args, "id")?;
ops::remove(&location, &id).await
}
"replace" => {
let cards = args
.get("cards")
.ok_or_else(|| anyhow::anyhow!("missing `cards` for op=replace"))?;
let cards: Vec<TaskBoardCard> = serde_json::from_value(cards.clone())
.map_err(|e| anyhow::anyhow!("invalid `cards`: {e}"))?;
ops::replace(&location, cards).await
}
"clear" => ops::clear(&location).await,
"list" => ops::list(&location).await,
other => {
return Ok(ToolResult::error(format!(
"unknown op '{other}' (expected add|edit|update_status|remove|replace|clear|list)"
)))
}
};
match result {
Ok(snap) => {
let payload = json!({
"threadId": snap.thread_id,
"cards": snap.cards,View on GitHub (pinned to a221052e0d)
Solutions
- Include "cards" as a JSON array of card objects for op=replace
- Use op=clear when the intent is an empty board
- Validate that op=replace calls carry an array cards field before dispatch
Example fix
// before
{ "op": "replace" }
// after
{ "op": "replace", "cards": [{ "id": "t1", "content": "Ship the fix" }] } Defensive patterns
Strategy: validation
Validate before calling
const a = args as Record<string, unknown>;
if (a?.op === "replace" && !Array.isArray(a.cards)) {
throw new Error("todo replace: 'cards' array is required (use op=clear to empty the board)");
} Type guard
function isReplaceArgs(a: unknown): a is { op: "replace"; cards: unknown[] } {
const v = a as Record<string, unknown>;
return v?.op === "replace" && Array.isArray(v.cards);
} Try / catch
if e.to_string().contains("missing `cards` for op=replace") {
return Ok(ToolResult::error("replace needs the full 'cards' array; use op=clear for an empty board"));
} Prevention
- Remember replace carries the complete board, clear empties it
- Validate replace calls carry an array cards field in wrappers
- When syncing board state, build the cards array before choosing replace
When it happens
Trigger: Model sends { "op": "replace" } with no cards; wrapper mapping a board update to replace without including the new card array; intending to clear but choosing the wrong op.
Common situations: Board-sync logic that falls back to replace without a payload; models conflating replace and clear.
Related errors
- missing required field `op`
- missing required field `{key}`
- invalid approvalMode '{other}' (expected required|not_requir
- invalid approvalMode type (expected required|not_required|nu
- Missing 'agent' parameter
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/9ad79b00ba779ccc.
Report an issue: GitHub.