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

  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.