can1357/oh-my-pi · error · AIError.ValidationError

Validation failed for tool "${toolCall.name}": Tool call arg

Error message

Validation failed for tool "${toolCall.name}": Tool call arguments are not valid JSON.\nParse Error: ${parseError}\nRaw JSON:\n${truncatedRawJson}

What it means

When a tool call's arguments carry a __rawJson field (raw unparsed JSON from the provider), validateToolCall attempts JSON.parse; on failure it throws AIError.ValidationError embedding the tool name, the parser's error, and up to 512 chars of the raw JSON (truncated with an ellipsis marker). This means the model produced malformed JSON arguments that could not be handed to the tool.

Source

Thrown at packages/ai/src/utils/validation.ts:1940

}

/**
 * Validates tool call arguments against an ArkType or plain JSON Schema schema.
 * Applies conservative LLM-quirk normalization before declaring failure.
 *
 * @throws Error with a formatted message when validation cannot be reconciled.
 */
export function validateToolArguments(tool: Tool, toolCall: ToolCall): ToolCall["arguments"] {
	const originalArgs = toolCall.arguments;
	if (originalArgs && typeof originalArgs === "object" && "__parseError" in originalArgs) {
		const parseError = originalArgs.__parseError;
		const rawJson = String(originalArgs.__rawJson ?? "");
		const maxLen = 512;
		const truncatedRawJson =
			rawJson.length <= maxLen
				? rawJson
				: `${rawJson.slice(0, maxLen)}… [truncated ${rawJson.length - maxLen} chars]`;
		throw new AIError.ValidationError(
			`Validation failed for tool "${toolCall.name}": Tool call arguments are not valid JSON.\nParse Error: ${parseError}\nRaw JSON:\n${truncatedRawJson}`,
		);
	}
	const ctx = getValidationContext(tool);
	const { json } = ctx;

	// Always normalize first — strip null/string "null" from optional fields,
	// strip optional empty strings only when their property schema rejects the
	// explicit value, and substitute defaults. Handles LLM outputting
	// placeholders for "no value" even when validation would otherwise pass.
	let normalizedArgs: unknown = originalArgs;
	let changed = false;

	// Unwrap accidentally double-JSON-encoded object keys before any schema
	// pass. LLMs sometimes emit `{ "\"op\"": "done" }`, so the property name
	// arrives quote-wrapped; left alone it reads as an unrecognized key, gets
	// dropped by the coercion repair, and re-surfaces as a missing-required
	// error. Running first means every later pass sees the corrected names.

View on GitHub (pinned to 9690622007)

Solutions

  1. Inspect the Raw JSON section of the error to identify the malformed portion (truncation vs syntax)
  2. Increase max output tokens / fix stream interruption so arguments complete
  3. Retry the request — malformed generation is often transient
  4. If using a lenient model, add a repair pass (e.g. jsonrepair) on __rawJson before validation, or catch the error and ask the model to re-emit the arguments
  5. Catch AIError.ValidationError in the tool-execution loop and surface it back to the model as a corrective message

Example fix

// before — crash on malformed args
const args = validateToolCall(tools, toolCall);
// after — recover by feeding the error back to the model
try {
  const args = validateToolCall(tools, toolCall);
} catch (err) {
  if (err instanceof AIError.ValidationError) {
    messages.push({ role: "user", content: `Your arguments for ${toolCall.name} were invalid: ${err.message}. Please re-emit valid JSON.` });
  } else throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const raw = (toolCall.arguments as any)?.__rawJson;
if (typeof raw === "string") {
  try { JSON.parse(raw); } catch (e) {
    // malformed upstream JSON — repair or fail fast before validation
    toolCall.arguments = JSON.parse(jsonRepair(raw));
  }
}

Try / catch

try {
  const args = validateToolCall(tools, toolCall);
} catch (err) {
  if (err instanceof AIError.ValidationError && /not valid JSON/.test(err.message)) {
    // retry the generation or feed the parse error back to the model
  } else throw err;
}

Prevention

When it happens

Trigger: Provider streams tool-call arguments as raw JSON fragments that fail to parse (truncated stream, invalid JSON syntax like single quotes/trailing commas, non-UTF8 artifacts); validateToolCall/validateToolArguments receives originalArgs containing __rawJson whose parse throws.

Common situations: Model output cut off by max_tokens leaving truncated JSON; a weak/quantized local model emitting invalid JSON; reasoning models wrapping JSON in prose or code fences; streaming interruptions mid tool-call.

Understand the failure class

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/4bd9037eb54216b4. Report an issue: GitHub.