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
- Ensure the drag source always sets data.stackId when selectionId.type is 'commit'
- Keep acceptsChangeDropData() in the pipeline so drops with undefined stackId are rejected before ondropChangeData
- When building drop data in tests or programmatically, include stackId (and selectionId.commitId)
- 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
- Always set stackId on change drop data with selectionId.type 'commit'
- Keep acceptsChangeDropData() as the gate before ondropChangeData
- Populate complete payloads in tests and programmatic drops
- Refresh drag sources after stack deletions or reorders
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
- Can't receive a change without it's source or commit
- Could not find selected project
- Could not find selected project
- Could not find selected project
- Failed to create a new commit
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)