{"record":{"id":"099fa55d404c5ae1","repo":"windmill-labs/windmill","slug":"invalid-json-after-replacement-message","errorCode":null,"errorMessage":"Invalid JSON after replacement: ${message}","messagePattern":"Invalid JSON after replacement: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/flow/core.ts","lineNumber":564,"sourceCode":"\n\t\t\ttoolCallbacks.setToolStatus(toolId, {\n\t\t\t\tcontent: 'Applying JSON patch...'\n\t\t\t})\n\n\t\t\tconst updatedFlowJson = findAndReplace(\n\t\t\t\tcurrentFlowJson,\n\t\t\t\toldString,\n\t\t\t\tnewString,\n\t\t\t\treplaceAll,\n\t\t\t\t'current flow JSON'\n\t\t\t)\n\n\t\t\tlet patchedValue: unknown\n\t\t\ttry {\n\t\t\t\tpatchedValue = JSON.parse(updatedFlowJson)\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error)\n\t\t\t\tthrow new Error(`Invalid JSON after replacement: ${message}`)\n\t\t\t}\n\t\t\tconst aiProviders = await getAiAgentProviderCatalogFor(\n\t\t\t\tworkspace,\n\t\t\t\t(patchedValue as { modules?: unknown } | null)?.modules\n\t\t\t)\n\t\t\tconst aiProviderWarnings: string[] = []\n\t\t\t// Validation errors carry their own diagnosis (a bad module, a provider the workspace\n\t\t\t// does not have); only a parse failure is about the replacement's JSON.\n\t\t\tconst parsedFlow: EditableFlowJson = validateEditableFlowJson(patchedValue, {\n\t\t\t\taiProviders,\n\t\t\t\taiProviderWarnings\n\t\t\t})\n\n\t\t\tfor (const [moduleId, content] of Object.entries(inlineScriptSession.getAll())) {\n\t\t\t\thelpers.inlineScriptSession.set(moduleId, content)\n\t\t\t}\n\n\t\t\tconst updateResult = await helpers.setFlowJson({","sourceCodeStart":546,"sourceCodeEnd":582,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/flow/core.ts#L546-L582","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: breaks JSON — unquoted key in new_string\npatchFlowJson({ old_string: '\"timeout\": 30', new_string: 'timeout: 60' })\n// after: valid JSON fragment\npatchFlowJson({ old_string: '\"timeout\": 30', new_string: '\"timeout\": 60' })","handlingStrategy":"validation","validationCode":"// validate the patch result before handing it to the tool\nconst patched = findAndReplace(currentJson, oldString, newString, replaceAll, 'flow JSON')\ntry { JSON.parse(patched) } catch (e) { throw new Error(`Patch would produce invalid JSON: ${e.message}`) }","typeGuard":"function isValidJson(s: string): boolean {\n  try { JSON.parse(s); return true } catch { return false }\n}","tryCatchPattern":"try {\n  await patchFlowJson({ old_string, new_string, replace_all })\n} catch (e) {\n  if (e.message.startsWith('Invalid JSON after replacement')) {\n    // fall back to a targeted set_module_code / smaller patch\n    await applySmallerPatch()\n  } else throw e\n}","preventionTips":["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"],"tags":["json","copilot","patch","parsing"],"backgroundTag":"invalid-json-after-patch","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}