tinyhumansai/openhuman · warning
invalid approvalMode '{other}' (expected required|not_requir
Error message
invalid approvalMode '{other}' (expected required|not_required|null) What it means
CardPatch parsing: args["approvalMode"] is a string but not one of the three accepted literals (todo.rs:258). Matching is case-sensitive: exactly "required", "not_required", or JSON null (null clears the override). Any other spelling — including camelCase variants — is rejected.
Source
Thrown at src/openhuman/agent/tools/todo.rs:258
fn optional_string(args: &serde_json::Value, key: &str) -> Option<String> {
args.get(key)
.and_then(|v| v.as_str())
.map(|s| s.to_string())
}
fn patch_from_args(args: &serde_json::Value) -> anyhow::Result<CardPatch> {
let status: Option<TaskCardStatus> = match args.get("status").and_then(|v| v.as_str()) {
Some(s) => Some(ops::parse_status(s).map_err(anyhow::Error::msg)?),
None => None,
};
let approval_mode = match args.get("approvalMode") {
Some(value) if value.is_null() => Some(None),
Some(value) => match value.as_str() {
Some("required") => Some(Some(TaskApprovalMode::Required)),
Some("not_required") => Some(Some(TaskApprovalMode::NotRequired)),
Some(other) => {
return Err(anyhow::anyhow!(
"invalid approvalMode '{other}' (expected required|not_required|null)"
))
}
None => {
return Err(anyhow::anyhow!(
"invalid approvalMode type (expected required|not_required|null)"
))
}
},
None => None,
};
Ok(CardPatch {
content: None,
status,
objective: optional_string(args, "objective"),
plan: optional_string_array(args, "plan")?,
assigned_agent: optional_string(args, "assignedAgent"),
allowed_tools: optional_string_array(args, "allowedTools")?,View on GitHub (pinned to a221052e0d)
Solutions
- Use exactly "required" or "not_required" (lowercase, snake_case), or omit the field to leave it unchanged
- Use JSON null to explicitly clear an existing approval-mode override
- Map UI/enum names to the wire literals in wrappers before invoking
Example fix
// before
{ "op": "edit", "id": "t1", "approvalMode": "notRequired" }
// after
{ "op": "edit", "id": "t1", "approvalMode": "not_required" } Defensive patterns
Strategy: type-guard
Validate before calling
const OK = new Set(["required", "not_required"]);
const a = args as Record<string, unknown>;
if ("approvalMode" in a && a.approvalMode !== null
&& (typeof a.approvalMode !== "string" || !OK.has(a.approvalMode))) {
throw new Error("approvalMode must be 'required' | 'not_required' | null");
} Type guard
function isApprovalMode(v: unknown): v is "required" | "not_required" | null {
return v === null || v === "required" || v === "not_required";
} Try / catch
if e.to_string().contains("invalid approvalMode") {
return Ok(ToolResult::error("approvalMode accepts exactly 'required', 'not_required', or null (case-sensitive)"));
} Prevention
- Use the exact lowercase snake_case literals — case-sensitive matching
- Map UI enum names to wire literals in wrappers
- Use null to clear an override, omit the field to leave it unchanged
When it happens
Trigger: Passing "ApprovalMode", "notRequired", "none", "auto", or "REQUIRED"; model-generated camelCase mirroring the JSON key name instead of the value literals.
Common situations: Models echoing the key casing for the value; wrappers forwarding UI enum names that differ from the wire literals.
Related errors
- missing required field `op`
- missing `cards` for op=replace
- missing required field `{key}`
- 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/b8e8383e518fc89b.
Report an issue: GitHub.