windmill-labs/windmill · error
Invalid JSON after replacement: ${message}
Error message
Invalid JSON after replacement: ${message} What it means
The patch_flow_json copilot tool applies a string find-and-replace to the serialized flow JSON, then re-parses it. If JSON.parse fails after the replacement, the tool throws this error wrapping the parser's message. It means the old_string/new_string pair produced syntactically invalid JSON — usually an unbalanced brace, quote, or comma introduced by the patch.
Source
Thrown at frontend/src/lib/components/copilot/chat/flow/core.ts:564
toolCallbacks.setToolStatus(toolId, {
content: 'Applying JSON patch...'
})
const updatedFlowJson = findAndReplace(
currentFlowJson,
oldString,
newString,
replaceAll,
'current flow JSON'
)
let patchedValue: unknown
try {
patchedValue = JSON.parse(updatedFlowJson)
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
throw new Error(`Invalid JSON after replacement: ${message}`)
}
const aiProviders = await getAiAgentProviderCatalogFor(
workspace,
(patchedValue as { modules?: unknown } | null)?.modules
)
const aiProviderWarnings: string[] = []
// Validation errors carry their own diagnosis (a bad module, a provider the workspace
// does not have); only a parse failure is about the replacement's JSON.
const parsedFlow: EditableFlowJson = validateEditableFlowJson(patchedValue, {
aiProviders,
aiProviderWarnings
})
for (const [moduleId, content] of Object.entries(inlineScriptSession.getAll())) {
helpers.inlineScriptSession.set(moduleId, content)
}
const updateResult = await helpers.setFlowJson({View on GitHub (pinned to e474e8803c)
Solutions
- Re-run patch_flow_json with old_string/new_string chosen so both sides are valid JSON fragments (keep quotes/braces balanced).
- Use smaller, structural replacements (a single field value) rather than large multi-object spans.
- Set replace_all when the fragment occurs more than once, or include surrounding context to make the match unique.
- Read the current flow JSON first (get flow state) and base the patch on its exact serialized text, including escaping.
Example fix
// before: breaks JSON — unquoted key in new_string
patchFlowJson({ old_string: '"timeout": 30', new_string: 'timeout: 60' })
// after: valid JSON fragment
patchFlowJson({ old_string: '"timeout": 30', new_string: '"timeout": 60' }) Defensive patterns
Strategy: validation
Validate before calling
// validate the patch result before handing it to the tool
const patched = findAndReplace(currentJson, oldString, newString, replaceAll, 'flow JSON')
try { JSON.parse(patched) } catch (e) { throw new Error(`Patch would produce invalid JSON: ${e.message}`) } Type guard
function isValidJson(s: string): boolean {
try { JSON.parse(s); return true } catch { return false }
} Try / catch
try {
await patchFlowJson({ old_string, new_string, replace_all })
} catch (e) {
if (e.message.startsWith('Invalid JSON after replacement')) {
// fall back to a targeted set_module_code / smaller patch
await applySmallerPatch()
} else throw e
} Prevention
- Keep old_string/new_string as valid JSON fragments — balance quotes and braces
- Copy fragments exactly from the serialized flow JSON, preserving escape sequences
- Prefer small single-field replacements over large multi-object spans
- Use replace_all when the fragment can occur more than once
When it happens
Trigger: patch_flow_json where replacing old_string with new_string breaks JSON syntax: quoting a fragment that includes JSON quotes, deleting a comma or brace, replacing only one of two occurrences without replace_all, or new_string containing unescaped control characters.
Common situations: The model patches a snippet containing escaped quotes (\") but supplies raw quotes in new_string; a replacement spans an object boundary; replace_all omitted when the target appears multiple times, leaving the JSON inconsistent.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- AI response contained empty code block
- AI response did not contain valid code. Please try rephrasin
- Invalid JSON for ${field}: ${errorMessage}
- Invalid JSON for ${field}: ${message}${hint}
- Invalid JSON after replacement: ${message}
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/099fa55d404c5ae1.
Report an issue: GitHub.