Branch on exit code. merge-pbi.sh resolves every exit to one
of 0/1/2/3 (contract documented in the wrapper header). The exit
code — not "zero vs non-zero" — selects the recovery: only exit 2
is a recorded merge failure that reads merge_failure.kind and
runs the 3-strike matrix.
- exit 0 → re-read
state.json, find merged_sha. Backlog status
is now awaiting_cross_review. SendMessage to Developer
(sprint.json.developers[].current_pbi == <pbi-id>):
[<pbi-id>] MERGED at <merged_sha>. Stand by for next assignment.
- exit 1 → preflight / infra failure. Nothing was recorded and
main is unchanged (
state.merge_failure was NOT written this
attempt and merge_failure_count did NOT advance). Do not
re-read merge_failure.kind and do not run the matrix below.
Report the wrapper's stderr verbatim, fix the named precondition
(wrong checked-out branch, status ≠ in_progress_merge, merge
lock contention, .scrum/ tracked, missing state/backlog, dirty
tree colliding with the merge set), and re-run merge-pbi.sh.
This does not count toward the 3-strike threshold.
- exit 2 → a merge failure was recorded THIS attempt and main
is back at its pre-merge HEAD. This is the only exit that
re-reads
state.json.merge_failure.kind and runs the per-kind
matrix + 3-strike rule. Status remains in_progress_merge while
merge_failure_count < 3. The wrapper's main-state cleanup
differs by kind: conflict aborts the merge via
git merge --abort so main stays exactly where it was;
artifact_missing and regression both have a partial merge
commit on main that is rolled back via
git reset --hard <pre-merge HEAD>. The SM does not need to
redo any git operation on main — only the per-kind SendMessage
below.
conflict → SM runs
bash .scrum/scripts/merge-main-into-pbi.sh <pbi-id> to merge
main HEAD into the PBI worktree. If that wrapper also exits
non-zero, the worktree is left in mid-merge state and SM
SendMessages the Developer:
[<pbi-id>] MERGE_CONFLICT paths=[<state.merge_failure.paths>]. Resolve conflicts in .scrum/worktrees/<pbi-id>, then run commit-pbi.sh and mark-pbi-ready-to-merge.sh. Do NOT use raw git rebase — it is blocked by pre-tool-use-no-branch-ops.
(If merge-main-into-pbi.sh succeeded cleanly, SM instructs the
Developer to re-run mark-pbi-ready-to-merge.sh to re-stamp
head_sha / paths_touched and re-notify.)
artifact_missing → SendMessage:
[<pbi-id>] ARTIFACT_MISSING paths=[<state.merge_failure.paths>]. Re-add files on pbi/<pbi-id> via commit-pbi.sh (files likely lost during conflict resolution or .gitignore drift), re-notify PBI_READY_TO_MERGE.
regression → main has been rolled back to pre-merge HEAD, so
merge-main-into-pbi.sh would only bring pre-merge main forward
and cannot reproduce the post-merge state the regression
command actually ran against. The Developer reproduces the
failure from the captured log instead. SendMessage:
[<pbi-id>] MERGE_REGRESSION log=.scrum/pbi/<pbi-id>/merge-regression.log. Reproduce/fix in .scrum/worktrees/<pbi-id> using the regression log (main was rolled back to pre-merge HEAD, so the post-merge state cannot be replayed locally), then commit-pbi.sh and mark-pbi-ready-to-merge.sh to re-notify.
- 3rd consecutive failure of any kind (status flips to
escalated,
merge_failure_count >= 3, escalation_reason ∈ {merge_conflict, merge_artifact_missing, merge_regression}) → invoke
pbi-escalation-handler skill with <pbi-id> (further Developer
iteration is unproductive).
- exit 3 → the merge commit landed on main but post-merge
bookkeeping/cleanup did not complete (or a rollback after a
recorded failure failed — main was mutated). The PBI is
effectively merged; do not route to the failure matrix and do
not count it toward the 3-strike threshold. Read the
wrapper's stderr: it names the exact recovery — re-run
mark-pbi-merged.sh <pbi-id> <sha> (backlog not yet flipped to
awaiting_cross_review), re-run cleanup-pbi-worktree.sh <pbi-id> (worktree/branch left behind), or a manual check
(verify main HEAD is at the intended merge commit when a rollback
failed). Repair, then confirm backlog status is
awaiting_cross_review and .scrum/worktrees/<pbi-id> +
pbi/<pbi-id> are gone before moving on.
Note: merge_failure.kind uses unprefixed values (conflict,
artifact_missing, regression) while escalation_reason uses the
merge_* prefix (merge_conflict, merge_artifact_missing,
merge_regression). The mapping is one-to-one;
mark-pbi-merge-failure.sh writes both.
Throughout the recovery loop the backlog status remains
in_progress_merge. The Developer fixes on pbi/<pbi-id> (in the
PBI worktree), runs commit-pbi.sh to record the fix, then
mark-pbi-ready-to-merge.sh to re-stamp head_sha / paths_touched
/ ready_at. SM retries merge-pbi.sh. The status only changes when
the merge succeeds (→ awaiting_cross_review) or when the 3rd
consecutive failure flips it to escalated.