gitbutlerapp/gitbutler · error

Change drop data must specify the source stackId

Error message

Change drop data must specify the source stackId

What it means

Thrown by StackDzHandler.ondropChangeData in stackDropHandler.ts when a change with selectionId.type === "commit" is dropped onto a stack dropzone (creating a new stack/commit) but data.stackId is undefined, so moveChangesToNewCommit cannot know which stack the changes come from. acceptsChangeDropData normally rejects this case, making it a defensive 'should not happen' throw.

Solutions

  1. Ensure the drag source always sets data.stackId when selectionId.type is 'commit'
  2. Keep acceptsChangeDropData() in the pipeline so drops with undefined stackId are rejected before ondropChangeData
  3. When building drop data in tests or programmatically, include stackId (and selectionId.commitId)
  4. Refresh the UI so drop data references existing stacks

Example fix

// before: payload missing source stack
const data = new FileChangeDropData({ selectionId: { type: 'commit', commitId }, stackId: undefined, ... });
// after
const data = new FileChangeDropData({ selectionId: { type: 'commit', commitId }, stackId: sourceStack.id, ... });
Defensive patterns

Strategy: validation

Validate before calling

function canDropOnStack(data: unknown): data is ChangeDropData {
  return (data instanceof FileChangeDropData || data instanceof FolderChangeDropData) &&
    data.selectionId.type === 'commit' &&
    typeof data.stackId === 'string';
}

Type guard

function hasSourceStack(d: ChangeDropData): d is ChangeDropData & { stackId: string } {
  return typeof d.stackId === 'string';
}

Try / catch

try {
  await handler.ondropChangeData(data);
} catch (e) {
  if (e instanceof Error && e.message.includes('must specify the source stackId')) {
    toast.error('Drop data is missing its source stack; refresh and retry');
  } else throw e;
}

Prevention

When it happens

Trigger: Dropping FileChangeDropData/FolderChangeDropData with selectionId.type 'commit' and undefined stackId onto a stack header; drop data constructed incompletely or the accepts() filter bypassed (programmatic drop, stale payload).

Common situations: Drag started from a UI element that never populated stackId; payload predating a stack deletion/reorder; tests or code dispatching drop events with hand-built ChangeDropData lacking stackId; refactor changing how stackId is set on drop data.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/f7b26d9a2f7b2ba8. Report an issue: GitHub.

Appendix: source

Thrown at apps/desktop/src/lib/dragging/dropHandlers/stackDropHandler.ts:102

				if (!outcome.newCommit) {
					throw new Error("Failed to create a new commit");
				}

				const sourceStackId = data.stackId;
				const sourceCommitId = data.selectionId.commitId;
				if (sourceStackId) {
					const diffSpec = changesToDiffSpec(await data.treeChanges());
					await this.macros.moveChangesToNewCommit(
						stack.id,
						outcome.newCommit,
						sourceStackId,
						sourceCommitId,
						branchName,
						diffSpec,
					);
				} else {
					// Should not happen, but just in case
					throw new Error("Change drop data must specify the source stackId");
				}
				break;
			}
			case "worktree": {
				const stack = await this.stackService.newStackMutation({
					projectId: this.projectId,
					branch: { name: undefined },
				});

				const changes = await data.treeChanges();
				const assignments = changes
					.flatMap((c) =>
						this.uncommittedService.getAssignmentsByPath(data.stackId ?? null, c.path),
					)
					.map((h) => ({
						hunkHeader: h.hunkHeader,
						pathBytes: h.pathBytes,
						target: this.stackTarget(stack.id),

View on GitHub (pinned to 58e5313667)