JuliusBrussee/caveman · error
cave_subagent_arguments_invalid
cave_subagent_arguments_invalid
Error message
cave_subagent_arguments_invalid
What it means
Before running a subagent, the runtime validates that params is a non-null, non-array object whose task property is a string. Any malformed arguments — null params, an array, a non-object, or a missing/non-string task — throw cave_subagent_arguments_invalid. This is strict input validation at the subagent tool boundary.
Solutions
- Pass arguments as a plain object with a string task field: { task: "..." }
- Validate/parse tool arguments against the tool's input schema before dispatch
- Coerce non-string task values to string upstream if the producer cannot be fixed
- Inspect the raw tool_call arguments JSON for wrapping/shape mistakes
Example fix
// before
dispatchSubagent("summarize the repo");
// after
dispatchSubagent({ task: "summarize the repo" }); Defensive patterns
Strategy: validation
Validate before calling
function isValidSubagentParams(params) {
return params !== null && typeof params === "object" && !Array.isArray(params)
&& typeof params.task === "string";
} Type guard
function hasStringTask(p) {
return typeof p === "object" && p !== null && !Array.isArray(p)
&& typeof p.task === "string";
} Try / catch
try {
await dispatchSubagent(params);
} catch (e) {
if (e.message === "cave_subagent_arguments_invalid") {
// fall back to a wrapped form
await dispatchSubagent({ task: String(rawArg) });
} else throw e;
} Prevention
- Wrap every task as { task: "..." }, never pass bare strings or arrays
- Validate tool arguments against the declared input schema before dispatch
- Add a unit test for argument shape at the dispatch boundary
- Reject malformed model tool_calls before they reach the runtime
When it happens
Trigger: Calling the subagent tool with params = null, params = [], params = "task text" (string not object), or params = {} / { task: 42 } where task is absent or not a string.
Common situations: Model emitting malformed tool arguments that were not schema-validated; caller passing the task text directly instead of wrapping it in { task: ... }; JSON deserialization producing an array; optional task field dropped by an upstream serializer.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- at least one report path and the canonical span fixture…
- cave_budget_release_reason_required
- cave_sandbox_subagent_definition_invalid
- cave_sandbox_unknown_subagent
- cave_tool_input_schema_mismatch
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/0105c4041466fce5.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/runtime.ts:3869
parentSpanId: parent.spanId,
});
}
async function executeSubagent(
toolDefinition: ToolDefinition,
params: unknown,
signal: AbortSignal | undefined,
parentOptions: InternalRunOptions,
usage: NestedUsage,
executionContext: InternalExecutionContext,
parentMeter: BudgetMeter | undefined,
parentDeadlineAt: number | undefined,
): Promise<unknown> {
const runtime = toolDefinition.runtime;
if (runtime?.kind !== "subagent") throw new Error("cave_subagent_runtime_missing");
if (params === null || typeof params !== "object" || Array.isArray(params) ||
typeof (params as { task?: unknown }).task !== "string") {
throw new Error("cave_subagent_arguments_invalid");
}
const task = (params as { task: string }).task;
if (task.length > runtime.maxInputChars) throw new Error("cave_subagent_input_limit");
const calls = usage.calls.get(toolDefinition.name) ?? 0;
if (calls >= runtime.maxCalls) throw new Error("cave_subagent_call_budget");
// Reserve synchronously before any await so parallel Pi tool dispatch cannot
// pass the same maxCalls check twice.
usage.calls.set(toolDefinition.name, calls + 1);
const depth = executionContext.depth;
const depthLimit = Math.min(
parentOptions.maxSubagentDepth ?? DEFAULT_SUBAGENT_DEPTH_LIMIT,
ABSOLUTE_SUBAGENT_DEPTH_LIMIT,
);
if (depth + 1 > depthLimit) throw new Error("cave_subagent_depth_limit");
// The wallet is carved here, still synchronously, for the same reason: two
// subagents dispatched in one turn must not both be funded out of the same
// remaining budget.
const walletAmount = parentMeter === undefinedView on GitHub (pinned to 3ee70a1026)