What happens
porch done emits its own state commits after the PR it was tracking has already merged, so those commits can never be in that PR. The builder branch is then ahead of origin/main at the exact moment porch prints:
🎉 PROTOCOL COMPLETE
Project <id> has completed the <protocol> protocol.
Observed on a BUGFIX run (dvarr issue cluesmith#109, PR cluesmith#116). After the PR merged:
$ git log --oneline origin/main..HEAD
68e9e2d [Issue 109] docs: landing record for the seam fix
9bef385 chore(porch): bugfix-109 protocol complete
d93585d chore(porch): bugfix-109 PR #116 merged
Two of those three are porch's own, from packages/codev/src/commands/porch/index.ts:
- L487 —
chore(porch): ${state.id} PR #${options.merged} merged, written by done --merged N, which by definition runs after the merge.
- L656 —
chore(porch): ${state.id} protocol complete, written by advanceProtocolPhase on the terminal transition, immediately before the completion banner.
Why it matters
The completion banner is the builder's done signal, and it is wrong at the moment it prints. A builder that trusts it stops. A builder that doesn't trust it improvises a second, unmodelled "docs" PR to land the leftovers — which is what happened here (PR cluesmith#118).
That improvised PR is the real damage, because it exists outside the protocol:
- No phase owns it, so
porch status shows verified with nothing outstanding.
- No gate covers it.
- No notification step is attached to it, so the architect is never told the builder is waiting on a decision. The builder sits idle and the worktree is never cleaned up, with nothing anywhere reporting a problem.
The last point is the one that cost real time. Every other hand-off in the protocol has an afx send architect attached; this one has none, because the protocol does not know the PR exists.
Related
approve() already carries a note acknowledging half of this (L897-L900):
// NOTE: The 'verified' state is committed to the builder branch, which may not
// be merged back to main. The closed GitHub Issue serves as the canonical "done"
// signal on main. State alignment (making status.yaml on main authoritative) is
// tracked as future work per spec 653.
Spec 653 covers making status.yaml on main authoritative. This issue is the adjacent problem: not where state lives, but that porch reports success while work is still unlanded, and provides no path or notification for landing it. Fixing 653 might dissolve it; fixing it alone would not fix 653.
Suggested fixes
Roughly in order of cost.
1. Don't lie in the banner. Before printing PROTOCOL COMPLETE, check whether the branch is ahead of its base:
git rev-list --count origin/<base>..HEAD
If non-zero, print what is unlanded and the next action instead of a clean completion. Cheap, and it converts a silent wrong signal into an explicit one.
2. Model the landing step. Give protocols that produce post-merge state commits a terminal land step with its own phase prompt and its own afx send architect notification, so the follow-up PR is a first-class thing rather than something each builder improvises differently.
3. Remove the orphan commits. Have done --merged N and the terminal transition write state without committing to the builder branch — amend into the PR before it merges, or write where main already owns it. Nothing is left behind and neither of the above is needed. Largest change, and probably wants to be designed together with spec 653.
Reproduction
Any protocol run to completion. BUGFIX is the shortest:
- Run a BUGFIX project through to
porch done <id> --pr N --branch <branch>.
- Merge the PR.
porch done <id> --merged N
porch done <id> → prints PROTOCOL COMPLETE.
git log --oneline origin/main..HEAD → two commits, unlanded.
What happens
porch doneemits its own state commits after the PR it was tracking has already merged, so those commits can never be in that PR. The builder branch is then ahead oforigin/mainat the exact moment porch prints:Observed on a BUGFIX run (dvarr issue cluesmith#109, PR cluesmith#116). After the PR merged:
Two of those three are porch's own, from
packages/codev/src/commands/porch/index.ts:chore(porch): ${state.id} PR #${options.merged} merged, written bydone --merged N, which by definition runs after the merge.chore(porch): ${state.id} protocol complete, written byadvanceProtocolPhaseon the terminal transition, immediately before the completion banner.Why it matters
The completion banner is the builder's done signal, and it is wrong at the moment it prints. A builder that trusts it stops. A builder that doesn't trust it improvises a second, unmodelled "docs" PR to land the leftovers — which is what happened here (PR cluesmith#118).
That improvised PR is the real damage, because it exists outside the protocol:
porch statusshowsverifiedwith nothing outstanding.The last point is the one that cost real time. Every other hand-off in the protocol has an
afx send architectattached; this one has none, because the protocol does not know the PR exists.Related
approve()already carries a note acknowledging half of this (L897-L900):Spec 653 covers making
status.yamlon main authoritative. This issue is the adjacent problem: not where state lives, but that porch reports success while work is still unlanded, and provides no path or notification for landing it. Fixing 653 might dissolve it; fixing it alone would not fix 653.Suggested fixes
Roughly in order of cost.
1. Don't lie in the banner. Before printing
PROTOCOL COMPLETE, check whether the branch is ahead of its base:If non-zero, print what is unlanded and the next action instead of a clean completion. Cheap, and it converts a silent wrong signal into an explicit one.
2. Model the landing step. Give protocols that produce post-merge state commits a terminal
landstep with its own phase prompt and its ownafx send architectnotification, so the follow-up PR is a first-class thing rather than something each builder improvises differently.3. Remove the orphan commits. Have
done --merged Nand the terminal transition write state without committing to the builder branch — amend into the PR before it merges, or write where main already owns it. Nothing is left behind and neither of the above is needed. Largest change, and probably wants to be designed together with spec 653.Reproduction
Any protocol run to completion. BUGFIX is the shortest:
porch done <id> --pr N --branch <branch>.porch done <id> --merged Nporch done <id>→ printsPROTOCOL COMPLETE.git log --oneline origin/main..HEAD→ two commits, unlanded.