A tangled pull request is a patch, not a ref: each round is built with
git format-patch. Handed a range containing a merge, format-patch
omits that commit and still exits 0 with an empty stderr, so the round
reaches the appview as a series that does not apply -- which a human
reads as "this pull request has conflicts" against a branch that merges
as a fast-forward. Nothing downstream reports the omission.
The daemon steered agents straight into it: the tangled implementation
prompt told them to git merge "origin/$RADIAL_BASE_BRANCH", and then
discarded exactly those merges when building the round.
Three changes, and the first two are halves of one rule:
- The tangled prompt rebases onto the base instead of merging it in, and pushes with --force-with-lease because a rebase rewrites a branch a predecessor may already have pushed. GitHub's prompt still merges: it merges refs and no patch is ever built from that history.
- #formatPatch counts the range and refuses to publish a round that is not whole -- no merges present, one patch emitted per commit -- naming the rebase as the remedy, since the agent that caused it reads the error.
- format-patch is passed --binary, so a file git considers binary reaches the round with a body instead of a bodiless "Binary files ... differ" that git apply refuses for want of an index line.
That last case is not hypothetical: a text file picks up the binary
classification from a single NUL byte, which is invisible in an editor.
scripts/check-no-nul.mjs now runs first in pnpm lint and refuses one in
any tracked file that is not a known binary asset.