tinyhumansai/openhuman · warning

missing required field `{key}`

Error message

missing required field `{key}`

What it means

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.

Source

Thrown at src/openhuman/agent/tools/todo.rs:233

        return BoardLocation::Thread {
            workspace_dir: parent.workspace_dir.clone(),
            thread_id: ops::ORCHESTRATOR_TASKS_THREAD_ID.to_string(),
        };
    }
    let Some(thread_id) = thread_context::current_thread_id() else {
        return BoardLocation::Scratch;
    };
    BoardLocation::Thread {
        workspace_dir: parent.workspace_dir.clone(),
        thread_id,
    }
}

fn required_string(args: &serde_json::Value, key: &str) -> anyhow::Result<String> {
    let value = args
        .get(key)
        .and_then(|v| v.as_str())
        .ok_or_else(|| anyhow::anyhow!("missing required field `{key}`"))?;
    let trimmed = value.trim();
    if trimmed.is_empty() {
        return Err(anyhow::anyhow!("missing required field `{key}`"));
    }
    Ok(trimmed.to_string())
}

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,
    };

View on GitHub (pinned to a221052e0d)

Solutions

  1. Include the field the {key} names, as a non-empty string
  2. Check exact key spelling per op (content, id, status)
  3. Validate op-specific required fields before dispatch in wrappers

Example fix

// before
{ "op": "update_status", "status": "done" }

// after
{ "op": "update_status", "id": "t1", "status": "done" }
Defensive patterns

Strategy: validation

Validate before calling

const REQUIRED_PER_OP: Record<string, string[]> = {
  add: ["content"], edit: ["id", "content"],
  update_status: ["id", "status"], remove: ["id"],
};
function missingFields(op: string, a: Record<string, unknown>): string[] {
  return (REQUIRED_PER_OP[op] ?? []).filter(k => typeof a[k] !== "string");
}

Type guard

function hasRequiredStrings(a: unknown, keys: string[]): boolean {
  const v = a as Record<string, unknown>;
  return keys.every(k => typeof v?.[k] === "string" && (v[k] as string).trim() !== "");
}

Try / catch

if e.to_string().contains("missing required field") {
    return Ok(ToolResult::error(format!("{e}; per-op required: add=content, edit=id+content, update_status=id+status, remove=id")));
}

Prevention

When it happens

Trigger: 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).

Common situations: Models assuming the id from context; argument builders that omit fields they consider implicit.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/35bd73f5f666d940. Report an issue: GitHub.