windmill-labs/windmill · error · Error

Invalid JSON after replacement: ${message}

Error message

Invalid JSON after replacement: ${message}

What it means

After applying textual replacements to a flow's JSON representation, the code re-parses the result with JSON.parse. If parsing fails, the replacement produced syntactically invalid JSON (e.g. broke braces/quotes), so the operation is aborted with the parser's message appended. This protects the flow draft from being overwritten with unparseable content.

Source

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

	// content is preserved through the patch via the InlineScriptSession; the
	// model uses set_flow_module_code to change inline script bodies.
	const base = await loadFlowDraftValue(path, ctx.workspace)
	const session = createInlineScriptSession()
	const editable = buildEditableFlowJson(flowDraftAsEditableInput(base.flow), session)
	const currentJson = JSON.stringify(editable)
	const updatedJson = findAndReplace(
		currentJson,
		oldString,
		newString,
		replaceAll,
		'compact flow JSON'
	)
	let parsedValue: unknown
	try {
		parsedValue = JSON.parse(updatedJson)
	} catch (error) {
		const message = error instanceof Error ? error.message : String(error)
		throw new Error(`Invalid JSON after replacement: ${message}`)
	}

	const aiProviders = await getAiAgentProviderCatalogFor(
		ctx.workspace,
		(parsedValue as { modules?: unknown } | null)?.modules
	)
	const aiProviderWarnings: string[] = []
	const patchedEditable = validateEditableFlowJson(parsedValue, { aiProviders, aiProviderWarnings })
	const newFlowValue = applyEditableFlowJsonToFlow(base.flow.value, patchedEditable, session)
	finalizeUnresolvedInlineScripts(newFlowValue)

	const result = await writeFlowDraft(
		{
			path,
			summary: base.summary,
			flow: {
				...base.flow,
				value: newFlowValue,

View on GitHub (pinned to e474e8803c)

Solutions

  1. Make the replacement text JSON-escaped (escape quotes/backslashes/newlines) and repeat the patch.
  2. Choose a search snippet that lies entirely within a single JSON string value, not across structural tokens.
  3. Use the structured write_flow tool instead of textual replacement for structural changes.

Example fix

// before
replace('"code": "old"', '"code": "say "hi""') // unescaped quotes break JSON
// after
replace('"code": "old"', '"code": "say \\"hi\\""')
Defensive patterns

Strategy: validation

Validate before calling

function canPatchSafely(json: string, search: string, replace: string): boolean {
  const updated = json.replace(search, replace)
  try { JSON.parse(updated); return true } catch { return false }
}

Try / catch

try {
  await patchFlowJson(args)
} catch (e) {
  if (String(e.message).startsWith('Invalid JSON after replacement:')) {
    // re-escape replacement text or switch to write_flow
  } else throw e
}

Prevention

When it happens

Trigger: A patch-style replace operation over a flow JSON string yields text that JSON.parse rejects — mismatched quotes, trailing commas, truncated structure after the replaced snippet.

Common situations: The replacement text itself contains unescaped quotes or newline characters; the searched snippet spanned a structural boundary (comma, brace) and its replacement removed or duplicated structural tokens; the model replaced JSON with pseudo-JSON prose.

Understand the failure class

Related errors


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