You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Version: but 0.21.0 (CLI), macOS (Apple Silicon) Repo shape: large private monorepo, ~9 applied single-branch stacks in the workspace, multiple agents/sessions committing via but concurrently
Summary
Two related incidents in one day, in the same workspace. The second one left the workspace in a state where but status, but unapply, and but oplog restore all fail, with no CLI recovery path — I had to repair it with hand-crafted git plumbing.
A but amend silently dropped an unrelated applied stack from the workspace — no UNAPPLY oplog entry, no message. The stack's files stayed on disk and reappeared as uncommitted changes.
A later but pull crashed partway through integrating one upstream commit. It had already rebased all nine applied branch refs onto the new base and deleted an integrated branch's ref + metadata, but died before rewriting the workspace merge commit — leaving branch refs and virtual_branches.toml in the "new world" while gitbutler/workspace (HEAD) still pointed at the old merge commit with the stale tips.
The main ask here is transactionality of upstream integration — #14831 reports a different assertion (no entrypoints in the rebase engine) with the same fact pattern on 0.21.0: integration crashes mid-flight and wedges the workspace. The downstream IdMap wedge is #14497 (partially fixed by #14700, merged after the 0.21.0 release).
Incident 1: amend silently evicts an unrelated stack
Workspace had ~10 applied stacks, including one stack with two heads (branch-x + branch-y, both at tip <commit-A>).
A but amend targeting a commit on a different stack ran (call it T+0).
The resulting workspace commit no longer had <commit-A> as a parent, and virtual_branches.toml (rewritten ~1s later) flipped that stack to in_workspace = false.
The oplog records only [AMEND] Amended commit — no UNAPPLY entry (every legitimate unapply in my oplog history has one). No warning was printed.
The evicted stack's files were left in the worktree (mtimes untouched), so they showed up as uncommitted/untracked changes, duplicating content committed on the now-unapplied branch.
Expected: an amend on stack A should never remove stack B from the workspace; if eviction is ever necessary it should be recorded in the oplog and reported to the user (related: #12355).
Incident 2: but pull crashes and leaves a three-way desync
After re-applying branch-x (clean), I ran but pull about a minute later:
but pull
Fetching newest data from remotes...
Checking: git@github.com:<org>/<repo>/origin/main
Found 1 upstream commits on origin/main
<upstream-sha> <commit subject> (#<pr>)
Checking integration statuses...
Error: BUG(opt-stack-id): should have gotten exactly one stack, got 8
The error is the bail! in crates/but-workspace/src/legacy/stacks.rs → stack_details_v3(): it was called with stack_id: None (via get_stack_status() in crates/gitbutler-branch-actions/src/upstream_integration.rs, which passes stack.id unguarded), fell into the "assume single-branch mode" arm, found 8 stacks in head_info(), and bailed. So some stack in the projection had no StackId — presumably because the operation was already partway through mutating state.
State after the crash (verified via reflogs and oplog snapshot trees):
All nine applied branch refs had been rebased onto the new base and moved (all reflog entries carry the same timestamp as the pull).
The integrated branch (squash-merged upstream as <upstream-sha>) had its ref deleted and its stanza removed from virtual_branches.toml.
virtual_branches.toml head CommitIds matched the new rebased tips.
But gitbutler/workspace still pointed at the pre-pull merge commit, wh — including a commit whose only remaining ref was branch-y (which has nostack metadata), and the deleted integrated branch's old tip.
The oplog has a single [UPDATE_BASE] Updated workspace base entry for this —ten, then the operation crashed mid-flight with no rollback.
Consequence: no CLI recovery path
With refs/metadata in the new world and HEAD in the old world, every relevant command failed:
but unapply <metadata-less-branch> → Branch '…' does not have an associated stack
but unapply <metadata-having-branch> → `Branch '…' not found in any applie metadata layer each see a different branch as applied — deadlock)
but oplog restore <any snapshot> → Error: Specified HEAD <tree-a> didn't match actual HEAD^{tree} <tree-b>
(the precondition in crates/but-core/src/worktree/checkout/utils.rs::merge_tion_or_keep_snapshot — with a dirty worktree it requires HEAD's tree to match
the snapshot's recorded source tree, which can't hold once the workspace is de unusable exactly when you need it most**). This resembles App unusable after failure to un-apply stack #11648.
Manual recovery that worked (for anyone else hitting this)
The crashed pull had actually done the rebases correctly, so I completed its f
but oplog restore <any snapshot> → Error: Specified HEAD <tree-a> didn't match actual HEAD^{tree} <tree-b>
(the precondition in crates/but-core/src/worktree/checkout/utils.rs::merge_tion_or_keep_snapshot — with a dirty worktree it requires HEAD's tree to match the snapshot's recorded source tree, which can't hold once the workspace is desynced, so oplog restore is unusable exactly when you need it most). This resembles App unusable after failure to un-apply stack #11648.
After that, but status was fully healthy again.
Suggested fixes
but pull / upstream integration should be transactional: don't move brancil the new workspace commit is ready; on error, roll back to the
before-snapshot automatically.
get_stack_status() should handle stack.id == None gracefully (skip/warnetails_v3(None)` bail on multi-stack workspaces.
but oplog restore needs to work from an inconsistent state — that's its p
Amend should never silently evict another stack, and any eviction must prodisible message. Repo shape: large private monorepo, ~9 applied single-branch stacks in theessions committing via but concurrently
Version: but 0.21.0 (CLI), macOS (Apple Silicon)
Repo shape: large private monorepo, ~9 applied single-branch stacks in the workspace, multiple agents/sessions committing via
butconcurrentlySummary
Two related incidents in one day, in the same workspace. The second one left the workspace in a state where
but status,but unapply, andbut oplog restoreall fail, with no CLI recovery path — I had to repair it with hand-crafted git plumbing.but amendsilently dropped an unrelated applied stack from the workspace — noUNAPPLYoplog entry, no message. The stack's files stayed on disk and reappeared as uncommitted changes.but pullcrashed partway through integrating one upstream commit. It had already rebased all nine applied branch refs onto the new base and deleted an integrated branch's ref + metadata, but died before rewriting the workspace merge commit — leaving branch refs andvirtual_branches.tomlin the "new world" whilegitbutler/workspace(HEAD) still pointed at the old merge commit with the stale tips.The main ask here is transactionality of upstream integration — #14831 reports a different assertion (
no entrypointsin the rebase engine) with the same fact pattern on 0.21.0: integration crashes mid-flight and wedges the workspace. The downstreamIdMapwedge is #14497 (partially fixed by #14700, merged after the 0.21.0 release).Incident 1: amend silently evicts an unrelated stack
branch-x+branch-y, both at tip<commit-A>).but amendtargeting a commit on a different stack ran (call it T+0).<commit-A>as a parent, andvirtual_branches.toml(rewritten ~1s later) flipped that stack toin_workspace = false.[AMEND] Amended commit— noUNAPPLYentry (every legitimate unapply in my oplog history has one). No warning was printed.Expected: an amend on stack A should never remove stack B from the workspace; if eviction is ever necessary it should be recorded in the oplog and reported to the user (related: #12355).
Incident 2:
but pullcrashes and leaves a three-way desyncAfter re-applying
branch-x(clean), I ranbut pullabout a minute later:The error is the
bail!incrates/but-workspace/src/legacy/stacks.rs→stack_details_v3(): it was called withstack_id: None(viaget_stack_status()incrates/gitbutler-branch-actions/src/upstream_integration.rs, which passesstack.idunguarded), fell into the "assume single-branch mode" arm, found 8 stacks inhead_info(), and bailed. So some stack in the projection had noStackId— presumably because the operation was already partway through mutating state.State after the crash (verified via reflogs and oplog snapshot trees):
<upstream-sha>) had its ref deleted and its stanza removed fromvirtual_branches.toml.virtual_branches.tomlhead CommitIds matched the new rebased tips.gitbutler/workspacestill pointed at the pre-pull merge commit, wh — including a commit whose only remaining ref wasbranch-y(which has nostack metadata), and the deleted integrated branch's old tip.The oplog has a single
[UPDATE_BASE] Updated workspace baseentry for this —ten, then the operation crashed mid-flight with no rollback.Consequence: no CLI recovery path
With refs/metadata in the new world and HEAD in the old world, every relevant command failed:
but statusprints a garbled tree (duplicated uncommitted sections, a phantom stack for the metadata-less ref) and ends with:Error: Could not find branch CLI id '' in IdMap(this symptom is Stale parent in workspace merge commit wedges CLI ("Could not find branch CLI id '' in IdMap") and shows "Unnamed segment" #14497; #but isn't in 0.21.0)but unapply <metadata-less-branch>→Branch '…' does not have an associated stackbut unapply <metadata-having-branch>→ `Branch '…' not found in any applie metadata layer each see a different branch as applied — deadlock)but oplog restore <any snapshot>→Error: Specified HEAD <tree-a> didn't match actual HEAD^{tree} <tree-b>(the precondition in
crates/but-core/src/worktree/checkout/utils.rs::merge_tion_or_keep_snapshot— with a dirty worktree it requires HEAD's tree to matchthe snapshot's recorded source tree, which can't hold once the workspace is de unusable exactly when you need it most**). This resembles App unusable after failure to un-apply stack #11648.
Manual recovery that worked (for anyone else hitting this)
The crashed pull had actually done the rebases correctly, so I completed its f
but oplog restore <any snapshot>→Error: Specified HEAD <tree-a> didn't match actual HEAD^{tree} <tree-b>(the precondition in
crates/but-core/src/worktree/checkout/utils.rs::merge_tion_or_keep_snapshot— with a dirty worktree it requires HEAD's tree to match the snapshot's recorded source tree, which can't hold once the workspace is desynced, so oplog restore is unusable exactly when you need it most). This resembles App unusable after failure to un-apply stack #11648.After that,
but statuswas fully healthy again.Suggested fixes
but pull/ upstream integration should be transactional: don't move brancil the new workspace commit is ready; on error, roll back to thebefore-snapshot automatically.
get_stack_status()should handlestack.id == Nonegracefully (skip/warnetails_v3(None)` bail on multi-stack workspaces.but oplog restoreneeds to work from an inconsistent state — that's its pRepo shape: large private monorepo, ~9 applied single-branch stacks in theessions committing via
butconcurrently