windmill-labs/windmill · error · Error

Invalid JSON for ${field}: ${message}${hint}

Error message

Invalid JSON for ${field}: ${message}${hint}

What it means

parseOptionalJsonArg parses tool arguments that may arrive as a JSON string. When JSON.parse fails it wraps the parse error into `Invalid JSON for <field>: <message>`, and for fields that carry flow code (FLOW_CODE_BEARING_FIELDS, e.g. rawscript content) it appends a hint: multi-line code or quotes inside the JSON string break escaping, and large bodies should instead be created empty and filled via set_flow_module_code.

Source

Thrown at frontend/src/lib/components/copilot/chat/global/core.ts:528

})

// modules/preprocessor_module/failure_module can carry rawscript `content`, whose
// quotes and newlines are the usual reason the JSON string fails to parse.
const FLOW_CODE_BEARING_FIELDS = new Set(['modules', 'preprocessor_module', 'failure_module'])

function parseOptionalJsonArg(value: unknown, field: string): unknown {
	if (value === undefined || value === null) {
		return value
	}

	try {
		return typeof value === 'string' ? JSON.parse(value) : value
	} catch (error) {
		const message = error instanceof Error ? error.message : String(error)
		const hint = FLOW_CODE_BEARING_FIELDS.has(field)
			? ' A rawscript "content" string with multi-line code or quotes is the usual cause. Instead of inlining large code, create the rawscript module with empty content ("") and fill its body with set_flow_module_code afterwards.'
			: ''
		throw new Error(`Invalid JSON for ${field}: ${message}${hint}`)
	}
}

/**
 * Rawscript bodies are fragile to embed inside the `modules` JSON string: the
 * code's quotes and newlines have to survive three levels of escaping (tool-call
 * arguments -> modules string -> content string) and the model routinely mangles
 * them. So a module may be saved with empty (or `inline_script.` placeholder)
 * content; return the ids that still need a body filled out-of-band with
 * `set_flow_module_code`.
 */
function emptyInlineScriptModuleIds(editable: EditableFlowJson): string[] {
	const value: FlowValue = {
		modules: editable.modules,
		preprocessor_module: editable.preprocessor_module ?? undefined,
		failure_module: editable.failure_module ?? undefined
	}
	const session = createInlineScriptSession()

View on GitHub (pinned to e474e8803c)

Solutions

  1. For rawscript modules, create the module with content "" and fill the body via set_flow_module_code instead of inlining code.
  2. Escape embedded code correctly: replace newlines with \n and quotes with \" in the JSON string.
  3. Validate the JSON string with JSON.parse in a scratch call (or a linter) before sending it to the tool.
  4. If the value is already an object, pass it as an object instead of a string — the parser only parses strings.

Example fix

// before
modules: '[{ "id": "a", "value": { "content": "print(\"hi\")" } }]' // escaping broke
// after
modules: '[{ "id": "a", "value": { "content": "" } }]' // then set_flow_module_code(a, 'print("hi")')
Defensive patterns

Strategy: validation

Validate before calling

function assertParses(field: string, value: unknown) {
  if (typeof value === 'string') JSON.parse(value) // throws early with clear context
}
assertParses('modules', modulesArg)

Type guard

function isJsonString(s: string): boolean {
  try { JSON.parse(s); return true } catch { return false }
}

Try / catch

try {
  await callTool('modules', modulesArg)
} catch (e) {
  if (e.message.startsWith('Invalid JSON for modules')) {
    // fall back: create rawscript with empty content, fill body separately
    await callTool('modules', modulesArg.replace(/"content":\\?"[\s\S]*?"(?=,|})/g, '"content": ""'))
  }
}

Prevention

When it happens

Trigger: Calling the `modules` or `editable` tool argument with a JSON string whose content is truncated, has raw newlines inside string values (rawscript content), or has unescaped double quotes from the embedded code.

Common situations: An LLM inlines multi-line script code inside the modules JSON string so quotes/newlines break escaping; a template renderer mangles the JSON; a copy-pasted payload is truncated at a newline.

Understand the failure class

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/6713e8571e63fd38. Report an issue: GitHub.