tinyhumansai/openhuman · warning
Missing 'plan' parameter
Error message
Missing 'plan' parameter
What it means
The `plan_exit` tool was invoked without a `plan` string (plan_exit.rs:69). The tool's only job is to emit PLAN_EXIT_MARKER followed by the trimmed plan text, so the argument is the entire payload. The empty-after-trim case is a separate soft ToolResult::error; this error means args["plan"] is absent or not a string.
Source
Thrown at src/openhuman/agent/tools/plan_exit.rs:69
"properties": {
"plan": {
"type": "string",
"description": "Markdown-formatted plan text to hand off."
}
},
"required": ["plan"]
})
}
fn permission_level(&self) -> PermissionLevel {
PermissionLevel::None
}
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
let plan = args
.get("plan")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing 'plan' parameter"))?;
let trimmed = plan.trim();
if trimmed.is_empty() {
return Ok(ToolResult::error("`plan` must not be empty"));
}
Ok(ToolResult::success(format!(
"{PLAN_EXIT_MARKER}\n{trimmed}"
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn plan_exit_emits_marker() {
let tool = PlanExitTool::new();
let result = toolView on GitHub (pinned to a221052e0d)
Solutions
- Pass "plan" as the full plan text string
- If the plan is naturally a list, join it into one string before calling
- Validate args against the schema (required: ["plan"]) in any wrapper
Example fix
// before
{}
// after
{ "plan": "1. Reproduce\n2. Fix null check in loader\n3. Add regression test" } Defensive patterns
Strategy: validation
Validate before calling
const a = args as Record<string, unknown>;
if (typeof a?.plan !== "string") {
throw new Error("plan_exit: 'plan' string is required");
} Type guard
function isPlanExitArgs(a: unknown): a is { plan: string } {
return typeof (a as Record<string, unknown>)?.plan === "string"
&& ((a as { plan: string }).plan.trim().length > 0);
} Try / catch
// plan_exit is terminal for the plan loop — feed a correctable error back
if e.to_string().contains("Missing 'plan'") {
return Ok(ToolResult::error("call plan_exit with the final plan as the 'plan' string"));
} Prevention
- Instruct the model that plan_exit carries the whole plan in one string
- Join multi-step plans into a single string before calling
- Keep required: ["plan"] aligned with the execute() read
When it happens
Trigger: Model calls plan_exit with no arguments or an empty object; plan passed as an array of steps instead of a string; wrapper dropping the field.
Common situations: Plan-review flows where the model assumes the plan is carried in context; argument-shape drift after prompt changes.
Related errors
- Missing 'agent' parameter
- Missing 'prompt' parameter
- missing required field `op`
- missing `cards` for op=replace
- missing required field `{key}`
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/9cce72d6fd7d0669.
Report an issue: GitHub.