tinyhumansai/openhuman · error · anyhow::Error

missing required string argument `{key}`

Error message

missing required string argument `{key}`

What it means

Shared arg reader for the agent artifact tools: `read_required_str` (src/openhuman/agent/artifacts/tools.rs:40) errors when `args[key]` is absent, not a JSON string, or trims to empty. It guards `artifact_get`/`artifact_delete`'s `artifact_id` (and any other required string arg) before any store I/O happens.

Source

Thrown at src/openhuman/agent/artifacts/tools.rs:40

use crate::openhuman::tools::traits::{PermissionLevel, Tool, ToolResult};

/// Read `offset` / `limit` as optional `usize` from tool args.
fn read_opt_usize(args: &serde_json::Value, key: &str) -> Option<usize> {
    args.get(key)
        .and_then(serde_json::Value::as_u64)
        .map(|v| v as usize)
}

/// Read a required, non-empty string arg.
fn read_required_str(args: &serde_json::Value, key: &str) -> anyhow::Result<String> {
    let raw = args
        .get(key)
        .and_then(serde_json::Value::as_str)
        .map(str::trim)
        .filter(|s| !s.is_empty());
    match raw {
        Some(s) => Ok(s.to_string()),
        None => Err(anyhow::anyhow!("missing required string argument `{key}`")),
    }
}

/// List artifacts the agent has produced, newest first.
pub struct ArtifactListTool {
    config: Arc<Config>,
}

impl ArtifactListTool {
    pub fn new(config: Arc<Config>) -> Self {
        Self { config }
    }
}

#[async_trait]
impl Tool for ArtifactListTool {
    fn name(&self) -> &str {
        "artifact_list"

View on GitHub (pinned to a221052e0d)

Solutions

  1. Include `"artifact_id": "<uuid>"` as a non-empty JSON string in the tool call arguments.
  2. Verify the exact key spelling against the tool's parameters_schema (`required: ["artifact_id"]`).
  3. If orchestrating programmatically, assert the field exists and is a non-empty trimmed string before dispatching the tool.
  4. Get a valid id first from artifact_list rather than guessing/hallucinating one.

Example fix

// before
{ "id": "550e8400-..." }
// after
{ "artifact_id": "550e8400-e29b-41d4-a716-446655440000" }
Defensive patterns

Strategy: validation

Validate before calling

fn required_str_arg(args: &serde_json::Value, key: &str) -> Option<&str> {
    args.get(key)?.as_str().map(str::trim).filter(|s| !s.is_empty())
}

let id = required_str_arg(&tool_args, "artifact_id")
    .ok_or_else(|| format!("call must include non-empty string `{key}`", key = "artifact_id"))?;

Type guard

fn has_required_string(args: &serde_json::Value, key: &str) -> bool {
    args.get(key)
        .and_then(serde_json::Value::as_str)
        .map(|s| !s.trim().is_empty())
        .unwrap_or(false)
}

Prevention

When it happens

Trigger: An LLM tool call to artifact_get/artifact_delete whose arguments JSON omits `artifact_id`, passes a number/null/object instead of a string, or passes whitespace-only text; programmatic callers building args from unvalidated input.

Common situations: Model hallucinating the arg name (`id` instead of `artifact_id`); the id arriving as a JSON number or null from upstream serialization; copy-paste tool schemas drifting from the implemented required list.

Related errors


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