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.

Solutions

  1. Split the suggestion into groups of at most 2 changes (deletion first, then addition) before calling applyGroup.
  2. Call applyChange/applyGroup once per change if atomicity is not required.
  3. Fix the grouping logic upstream (where VisualChangeWithDiffIndex groups are built) to enforce the ≤2 invariant.
  4. 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

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


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

Appendix: 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)