{"record":{"id":"0105c4041466fce5","repo":"JuliusBrussee/caveman","slug":"cave-subagent-arguments-invalid","errorCode":"cave_subagent_arguments_invalid","errorMessage":"cave_subagent_arguments_invalid","messagePattern":"cave_subagent_arguments_invalid","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":3869,"sourceCode":"    parentSpanId: parent.spanId,\n  });\n}\n\nasync function executeSubagent(\n  toolDefinition: ToolDefinition,\n  params: unknown,\n  signal: AbortSignal | undefined,\n  parentOptions: InternalRunOptions,\n  usage: NestedUsage,\n  executionContext: InternalExecutionContext,\n  parentMeter: BudgetMeter | undefined,\n  parentDeadlineAt: number | undefined,\n): Promise<unknown> {\n  const runtime = toolDefinition.runtime;\n  if (runtime?.kind !== \"subagent\") throw new Error(\"cave_subagent_runtime_missing\");\n  if (params === null || typeof params !== \"object\" || Array.isArray(params) ||\n      typeof (params as { task?: unknown }).task !== \"string\") {\n    throw new Error(\"cave_subagent_arguments_invalid\");\n  }\n  const task = (params as { task: string }).task;\n  if (task.length > runtime.maxInputChars) throw new Error(\"cave_subagent_input_limit\");\n  const calls = usage.calls.get(toolDefinition.name) ?? 0;\n  if (calls >= runtime.maxCalls) throw new Error(\"cave_subagent_call_budget\");\n  // Reserve synchronously before any await so parallel Pi tool dispatch cannot\n  // pass the same maxCalls check twice.\n  usage.calls.set(toolDefinition.name, calls + 1);\n  const depth = executionContext.depth;\n  const depthLimit = Math.min(\n    parentOptions.maxSubagentDepth ?? DEFAULT_SUBAGENT_DEPTH_LIMIT,\n    ABSOLUTE_SUBAGENT_DEPTH_LIMIT,\n  );\n  if (depth + 1 > depthLimit) throw new Error(\"cave_subagent_depth_limit\");\n  // The wallet is carved here, still synchronously, for the same reason: two\n  // subagents dispatched in one turn must not both be funded out of the same\n  // remaining budget.\n  const walletAmount = parentMeter === undefined","sourceCodeStart":3851,"sourceCodeEnd":3887,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/runtime.ts#L3851-L3887","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\ndispatchSubagent(\"summarize the repo\");\n// after\ndispatchSubagent({ task: \"summarize the repo\" });","handlingStrategy":"validation","validationCode":"function isValidSubagentParams(params) {\n  return params !== null && typeof params === \"object\" && !Array.isArray(params)\n    && typeof params.task === \"string\";\n}","typeGuard":"function hasStringTask(p) {\n  return typeof p === \"object\" && p !== null && !Array.isArray(p)\n    && typeof p.task === \"string\";\n}","tryCatchPattern":"try {\n  await dispatchSubagent(params);\n} catch (e) {\n  if (e.message === \"cave_subagent_arguments_invalid\") {\n    // fall back to a wrapped form\n    await dispatchSubagent({ task: String(rawArg) });\n  } else throw e;\n}","preventionTips":["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"],"tags":["subagent","arguments","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}