{"record":{"id":"2153890853719a3f","repo":"windmill-labs/windmill","slug":"invalid-group","errorCode":null,"errorMessage":"Invalid group","messagePattern":"Invalid group","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/monaco-adapter.ts","lineNumber":127,"sourceCode":"\n\tasync rejectAll(opts?: { disableReviewCallback?: boolean }) {\n\t\tthis.finish(opts)\n\t}\n\n\t// Keep all changes, used in revert mode\n\tasync keepAll(opts?: { disableReviewCallback?: boolean }) {\n\t\tthis.finish(opts)\n\t}\n\n\t// Revert all changes, used in revert mode\n\tasync revertAll(opts?: { disableReviewCallback?: boolean }) {\n\t\tthis.acceptAll(opts)\n\t}\n\n\tapplyGroup(group: { changes: VisualChangeWithDiffIndex[]; groupIndex: number }) {\n\t\t// maximum of 2 changes per group with the deletion first\n\t\tif (group.changes.length > 2) {\n\t\t\tthrow new Error('Invalid group')\n\t\t} else if (group.changes.length === 2) {\n\t\t\tconst deletedChange = group.changes[0]\n\t\t\tconst addedChange = group.changes[1]\n\t\t\tif (deletedChange.type === 'deleted' && addedChange.type === 'added_block') {\n\t\t\t\tthis.editor.executeEdits('chat', [\n\t\t\t\t\t{\n\t\t\t\t\t\trange: {\n\t\t\t\t\t\t\tstartLineNumber: deletedChange.range.startLine,\n\t\t\t\t\t\t\tstartColumn: 1,\n\t\t\t\t\t\t\tendLineNumber: deletedChange.range.endLine + 1,\n\t\t\t\t\t\t\tendColumn: 0\n\t\t\t\t\t\t},\n\t\t\t\t\t\ttext: addedChange.value + '\\n'\n\t\t\t\t\t}\n\t\t\t\t])\n\t\t\t} else {\n\t\t\t\tthrow new Error('Invalid group')\n\t\t\t}","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/monaco-adapter.ts#L109-L145","documentation":"MonacoAdapter.applyGroup() applies a group of visual changes suggested by the chat to the editor. The adapter only knows how to execute two shapes atomically: a single change, or a two-change group representing a delete-followed-by-add (a replacement). Any group with more than 2 changes violates this contract and throws 'Invalid group' as an internal invariant so bad tool output fails loudly instead of corrupting the buffer.","triggerScenarios":"applyGroup (via acceptAll or onApply) called with group.changes.length > 2 — i.e. the chat's suggestion grouped more than two VisualChangeWithDiffIndex entries into one group, contradicting the adapter's documented 'maximum of 2 changes per group with the deletion first'.","commonSituations":"A model/tool emitting multiple edits for one location and grouping them together; a change in how diff chunks are computed producing bigger groups; version drift between the grouping code and the adapter.","solutions":["Split the suggestion into groups of at most 2 changes (deletion first, then addition) before calling applyGroup.","Call applyChange/applyGroup once per change if atomicity is not required.","Fix the grouping logic upstream (where VisualChangeWithDiffIndex groups are built) to enforce the ≤2 invariant.","Verify the model output post-processing doesn't merge adjacent hunks into one group."],"exampleFix":"// before\nadapter.applyGroup({ changes: [c1, c2, c3], groupIndex: 0 })\n// after\nfor (const c of [c1, c2, c3]) adapter.applyGroup({ changes: [c], groupIndex: 0 })","handlingStrategy":"validation","validationCode":"function isValidGroup(g: { changes: VisualChangeWithDiffIndex[] }): boolean {\n  return g.changes.length >= 1 && g.changes.length <= 2\n}\nif (!isValidGroup(group)) {\n  // split into per-change groups instead of calling applyGroup\n}","typeGuard":"function isApplicableGroup(g: { changes: VisualChangeWithDiffIndex[] }): boolean {\n  return g.changes.length === 1 ||\n    (g.changes.length === 2 && g.changes[0].type === 'deleted' && g.changes[1].type === 'added_block')\n}","tryCatchPattern":"try {\n  adapter.applyGroup(group)\n} catch (e) {\n  if (e instanceof Error && e.message === 'Invalid group') {\n    for (const c of group.changes) adapter.applyGroup({ changes: [c], groupIndex: group.groupIndex })\n  } else throw e\n}","preventionTips":["Enforce the ≤2-changes-per-group invariant where groups are built","Keep deletions before additions inside a group","Add a unit test asserting groupers never emit >2 changes"],"tags":["monaco","editor","internal-invariant"],"backgroundTag":"invalid-diff-group","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}