windmill-labs/windmill · error
Invalid group
Error message
Invalid group
What it means
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.
Source
Thrown at frontend/src/lib/components/copilot/chat/monaco-adapter.ts:127
async rejectAll(opts?: { disableReviewCallback?: boolean }) {
this.finish(opts)
}
// Keep all changes, used in revert mode
async keepAll(opts?: { disableReviewCallback?: boolean }) {
this.finish(opts)
}
// Revert all changes, used in revert mode
async revertAll(opts?: { disableReviewCallback?: boolean }) {
this.acceptAll(opts)
}
applyGroup(group: { changes: VisualChangeWithDiffIndex[]; groupIndex: number }) {
// maximum of 2 changes per group with the deletion first
if (group.changes.length > 2) {
throw new Error('Invalid group')
} else if (group.changes.length === 2) {
const deletedChange = group.changes[0]
const addedChange = group.changes[1]
if (deletedChange.type === 'deleted' && addedChange.type === 'added_block') {
this.editor.executeEdits('chat', [
{
range: {
startLineNumber: deletedChange.range.startLine,
startColumn: 1,
endLineNumber: deletedChange.range.endLine + 1,
endColumn: 0
},
text: addedChange.value + '\n'
}
])
} else {
throw new Error('Invalid group')
}View on GitHub (pinned to e474e8803c)
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.
Example fix
// before
adapter.applyGroup({ changes: [c1, c2, c3], groupIndex: 0 })
// after
for (const c of [c1, c2, c3]) adapter.applyGroup({ changes: [c], groupIndex: 0 }) Defensive patterns
Strategy: validation
Validate before calling
function isValidGroup(g: { changes: VisualChangeWithDiffIndex[] }): boolean {
return g.changes.length >= 1 && g.changes.length <= 2
}
if (!isValidGroup(group)) {
// split into per-change groups instead of calling applyGroup
} Type guard
function isApplicableGroup(g: { changes: VisualChangeWithDiffIndex[] }): boolean {
return g.changes.length === 1 ||
(g.changes.length === 2 && g.changes[0].type === 'deleted' && g.changes[1].type === 'added_block')
} Try / catch
try {
adapter.applyGroup(group)
} catch (e) {
if (e instanceof Error && e.message === 'Invalid group') {
for (const c of group.changes) adapter.applyGroup({ changes: [c], groupIndex: group.groupIndex })
} else throw e
} Prevention
- 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
When it happens
Trigger: 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'.
Common situations: 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.
Related errors
- SQL_PREPARATION_ERROR
- No flow available to test. Please ensure you have a flow ope
- Could not synthesize ${type} draft "${path}".
- [SqlTypeService] Worker initialization timeout for ${uri.toS
- [SqlTypeService] Worker initialization failed for ${uri.toSt
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/2153890853719a3f.
Report an issue: GitHub.