{"record":{"id":"35bd73f5f666d940","repo":"tinyhumansai/openhuman","slug":"missing-required-field-key-35bd73","errorCode":null,"errorMessage":"missing required field `{key}`","messagePattern":"missing required field `(.+?)`","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/openhuman/agent/tools/todo.rs","lineNumber":233,"sourceCode":"        return BoardLocation::Thread {\n            workspace_dir: parent.workspace_dir.clone(),\n            thread_id: ops::ORCHESTRATOR_TASKS_THREAD_ID.to_string(),\n        };\n    }\n    let Some(thread_id) = thread_context::current_thread_id() else {\n        return BoardLocation::Scratch;\n    };\n    BoardLocation::Thread {\n        workspace_dir: parent.workspace_dir.clone(),\n        thread_id,\n    }\n}\n\nfn required_string(args: &serde_json::Value, key: &str) -> anyhow::Result<String> {\n    let value = args\n        .get(key)\n        .and_then(|v| v.as_str())\n        .ok_or_else(|| anyhow::anyhow!(\"missing required field `{key}`\"))?;\n    let trimmed = value.trim();\n    if trimmed.is_empty() {\n        return Err(anyhow::anyhow!(\"missing required field `{key}`\"));\n    }\n    Ok(trimmed.to_string())\n}\n\nfn optional_string(args: &serde_json::Value, key: &str) -> Option<String> {\n    args.get(key)\n        .and_then(|v| v.as_str())\n        .map(|s| s.to_string())\n}\n\nfn patch_from_args(args: &serde_json::Value) -> anyhow::Result<CardPatch> {\n    let status: Option<TaskCardStatus> = match args.get(\"status\").and_then(|v| v.as_str()) {\n        Some(s) => Some(ops::parse_status(s).map_err(anyhow::Error::msg)?),\n        None => None,\n    };","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/src/openhuman/agent/tools/todo.rs#L215-L251","documentation":"The required_string helper (todo.rs:233) could not extract a mandatory field: args[key] is absent or not a JSON string. It backs the per-op mandatory fields of the `todo` tool — add→content, edit→id and content, update_status→id and status, remove→id. The whitespace-only case produces the same message via a separate branch.","triggerScenarios":"add without content; edit without id or content; update_status without id or status; remove without id; any of these passed as a non-string JSON value; key-name drift (text instead of content, task_id instead of id).","commonSituations":"Models assuming the id from context; argument builders that omit fields they consider implicit.","solutions":["Include the field the {key} names, as a non-empty string","Check exact key spelling per op (content, id, status)","Validate op-specific required fields before dispatch in wrappers"],"exampleFix":"// before\n{ \"op\": \"update_status\", \"status\": \"done\" }\n\n// after\n{ \"op\": \"update_status\", \"id\": \"t1\", \"status\": \"done\" }","handlingStrategy":"validation","validationCode":"const REQUIRED_PER_OP: Record<string, string[]> = {\n  add: [\"content\"], edit: [\"id\", \"content\"],\n  update_status: [\"id\", \"status\"], remove: [\"id\"],\n};\nfunction missingFields(op: string, a: Record<string, unknown>): string[] {\n  return (REQUIRED_PER_OP[op] ?? []).filter(k => typeof a[k] !== \"string\");\n}","typeGuard":"function hasRequiredStrings(a: unknown, keys: string[]): boolean {\n  const v = a as Record<string, unknown>;\n  return keys.every(k => typeof v?.[k] === \"string\" && (v[k] as string).trim() !== \"\");\n}","tryCatchPattern":"if e.to_string().contains(\"missing required field\") {\n    return Ok(ToolResult::error(format!(\"{e}; per-op required: add=content, edit=id+content, update_status=id+status, remove=id\")));\n}","preventionTips":["Check per-op required fields before dispatch — the helper enforces both presence and non-blank content","Use exact key spellings (content, id, status)","Never assume context carries the id — pass it explicitly"],"tags":["rust","tool","arguments","llm","todo"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}