Repository navigation
AGENTS.md step 7 tells an agent that a non-zero adopt-project.py execute exit is to fix the plan and re-run into fresh legs, but in GitHub mode execute exits 2 with the split pushed when gh pr create is refused, and exits 2 after adoption verified when the topic will not set, so following step 7 abandons a pushed adoption whose own exit is a command to run by hand #304
Description
(lane openRepoShape-1, 2026-10-12T00:14:28Z)
Found by this lane's re-verification of the #188 branch (fix/adopt-late-failure-notice at cdfb308), its R5, and left out of #188 as main's own gap. Pre-existing on main b30595c.
What is wrong. AGENTS.md step 7 is what an agent reads after execute: "If it exits non-zero, STOP and relay it: the source is untouched — nothing is deleted, main is where it was, the split is on a branch — so the exit is to fix the plan and re-run into fresh legs." That is the way out of a failed verification, exit 1, and its footer says the same. In GitHub mode execute has two more non-zero exits. Both come after both legs and the split branch are pushed, and the tool's own way out of each is a command to finish by hand:
gh pr createrefused: exit 2, then "The branch IS pushed. Open the pull request by hand:" and the command.executereturns before it verifies, so no verification table is printed.- The topic will not set: exit 2 after
adoption verified: every source path is in exactly one place, then "The split IS pushed and the pull request IS open. Set the topic by hand:" and three commands.
On either exit, step 7 sends an agent to re-run into a second pair of legs, beside the pair already pushed.
SHAPE=<openRepoShape checkout>
export GIT_AUTHOR_NAME=T GIT_AUTHOR_EMAIL=t@example.invalid GIT_COMMITTER_NAME=T GIT_COMMITTER_EMAIL=t@example.invalid
for fails in "pr create" "repo edit"; do (
TMP=$(mktemp -d); mkdir -p "$TMP/gh/testorg" "$TMP/bin"
# GitHub with no network: every https://github.com/ url is a bare repository under $TMP/gh,
# and a stub `gh` makes them for `gh repo create` and refuses only `gh $fails`
printf '[url "%s/gh/"]\n\tinsteadOf = https://github.com/\n' "$TMP" > "$TMP/gitconfig"
export GIT_CONFIG_GLOBAL="$TMP/gitconfig" GIT_ALLOW_PROTOCOL=file PATH="$TMP/bin:$PATH"
cat > "$TMP/bin/gh" <<EOF
#!/bin/sh
case "\$1 \$2" in
"$fails") echo "HTTP 403: Resource not accessible by integration" >&2; exit 1;;
"repo create") git init -q --bare -b main "$TMP/gh/\$3.git" && echo "https://github.com/\$3";;
"pr create") echo "https://github.com/testorg/Northwind/pull/1";;
"repo edit") ;;
*) exit 1;;
esac
EOF
chmod +x "$TMP/bin/gh"
git init -q -b main "$TMP/Northwind" && cd "$TMP/Northwind" || exit 1
mkdir src docs && printf 'print(1)\n' > src/app.py && printf '# Spec\n' > docs/spec.md
printf '# Northwind\n' > README.md && git add -A && git commit -qm init
git clone -q --bare . "$TMP/gh/testorg/Northwind.git" && git remote add origin https://github.com/testorg/Northwind.git
git fetch -q origin && git remote set-head origin main
python3 "$SHAPE/adopt-project.py" plan --source "$TMP/Northwind" --project Northwind --org testorg \
--elected-by 'T H' --elected-on 2026-09-02 --out "$TMP/plan.yaml" > /dev/null
python3 "$SHAPE/adopt-project.py" execute --plan "$TMP/plan.yaml" --yes --work-dir "$TMP/work" > "$TMP/out" 2> "$TMP/err"
echo "== gh $fails refused: execute exit $?"
grep -E '^adoption verified|^ pull request' "$TMP/out"
grep -E '^REFUSED|IS pushed|^ gh ' "$TMP/err"
echo "repositories on GitHub: $(ls "$TMP/gh/testorg" | tr '\n' ' ')"
echo "branches of the source on GitHub: $(git -C "$TMP/gh/testorg/Northwind.git" for-each-ref --format='%(refname:short)' refs/heads | tr '\n' ' ')"
) doneObserved on main b30595c (git 2.43.0), and byte-identical on #188's branch at e507c42:
== gh pr create refused: execute exit 2
REFUSED opening the pull request. THE EXACT COMMAND WAS:
gh pr create --repo testorg/Northwind --base main --head adopt/three-repo-shape --title Adopt the three-repository shape: Northwind-spec and Northwind-code --body Adopt the three-repository shape: Northwind-spec and Northwind-code
The branch IS pushed. Open the pull request by hand:
gh pr create --repo testorg/Northwind --base main --head adopt/three-repo-shape
repositories on GitHub: Northwind-code.git Northwind-spec.git Northwind.git
branches of the source on GitHub: adopt/three-repo-shape main
== gh repo edit refused: execute exit 2
pull request https://github.com/testorg/Northwind/pull/1
adoption verified: every source path is in exactly one place
REFUSED setting the project topic. THE EXACT COMMAND WAS:
gh repo edit testorg/Northwind --add-topic xf-project-northwind
The split IS pushed and the pull request IS open. Set the topic by hand:
gh repo edit testorg/Northwind --add-topic xf-project-northwind
gh repo edit testorg/Northwind-spec --add-topic xf-project-northwind
gh repo edit testorg/Northwind-code --add-topic xf-project-northwind
repositories on GitHub: Northwind-code.git Northwind-spec.git Northwind.git
branches of the source on GitHub: adopt/three-repo-shape main
In the first run, stdout ends at the split <sha> on adopt/three-repo-shape -> https://github.com/testorg/Northwind.git line: no VERIFICATION table follows.
Why it matters.
- Step 7 is the instruction an agent follows after a non-zero exit. For these two exits it points away from the exit the tool prints, which is one command to run by hand.
- After the topic exit, the pushed legs, the split and the open pull request are a finished, verified adoption. Following step 7 abandons it for a second adoption into fresh leg repositories.
- After the
gh pr createexit there is no table for step 7's "Read the verification table back to them", and the pull request opened by hand is for a split nothing verified._set_the_topic's docstring keeps the table because "losing it to a permission or a rate limit would be the more expensive failure", and a refusedgh pr createloses it to exactly those. - Both exits are GitHub mode's alone, and no test on main reaches either function, so the
--local-remote-dirpath every test takes never shows the gap.
Where (main b30595c):
AGENTS.md:135-139: step 7.adopt-project.py:2973-2995,_open_the_pull_request. Its docstring says "the branch IS pushed by the time this runs, so the exit is never "start again"" (:2978-2980), and the by-hand command is at:2991-2993.cmd_executereturns 2 at:3191, before_verify(:3195).adopt-project.py:2998-3027,_set_the_topic. Its docstring cites AGENTS.md step 7 (:3008), and the by-hand commands are at:3020-3025.cmd_executereturns2 if topics_failed else 0at:3199, afteradoption verified(:3356). Both calls sit underif not local:(:3188).adopt-project.py:3350-3355: a failed verification's footer and its exit 1, the one exit step 7 describes.adopt-project.py:75-76: the EXIT CODES line gives exit 2 one meaning, "a refusal — the question could not be asked", and does not mention the two exit-2s that come after the push.
What would fix it.
- Step 7 keys its advice to the exit the tool prints, not to "non-zero":
- exit 1, a failed verification: fix the plan and re-run, as its footer says;
- an exit 2 whose message says
IS pushed: relay it and finish by hand with the printed command, and never re-run; - any other refusal: relay it with its own remediation.
executeverifies before it returns on a refusedgh pr create, as it already does when the topic will not set. The pull request opened by hand is then for a verified split, and step 7 has its table.- The EXIT CODES line names the exit-2s that come after the push.
- A test holds step 7 to the two printed exits. AGENTS.md is at its 464-line cap on main (
tests/test_repo_hygiene.py:564), so the new wording fits in the same lines or raises the cap with a dated line, as earlier raises did.
After #188 lands. The #188 branch rewrites the same sentence: "If it exits non-zero, STOP and relay it: the source lost nothing, and the exit it prints is to fix the plan, delete the split branch, and re-run into fresh legs and a fresh --work-dir, or none." That is exact for exit 1, whose footer on the branch prints the command that deletes the split branch, and the branch's test_agents_md_step_7_names_the_exit_the_tool_prints holds those three phrases. The two exits above are unchanged there: the script prints byte-identical output on the branch at e507c42. So the gap stays, and it reads more sharply. For these exits "the exit it prints" is a by-hand command, not the one step 7 names. "Delete the split branch" also names the head of the pull request that is open or about to be opened by hand. The fix above still applies; it keeps those three phrases for exit 1 and adds the IS pushed case beside them.
Related. #188: R5 of this lane's re-verification of its fix round. #21: the topic. #138: _open_the_pull_request and _set_the_topic split out of cmd_execute. #204, closed by #227: in update-shape.py apply --pr, a gh pr create that fails after the push is now named as that step, with a NEXT line and the commit kept.
Not claimed.
🤖 Generated with Claude Code
Activity
brettheap commentedon Oct 12, 2026
CLAIMED — lane openRepoShape-5, session e62c2fd7-176d-429d-89fb-0653dd08ad5f@Eagle, 2026-10-12T11:04:12Z, for #304
Logged in
lanes/log/openRepoShape-5.mdat26b512e6d30e5ec311d70f42eb9a992b9fb4d06f.The three reads (lane-collision-protocol Rule 1):
adopt-project.py: `execute` verifies the split before it exits on a r…
adopt-project.py: a closed stdout no longer takes the by-hand `gh pr …
tests/test_adopt_github_exits.py: the stub `gh` records the token and…
tests/test_adopt_github_exits.py: the printed-command test holds the …
Merge origin/main (1f90a0f) into fix/adopt-late-failure-notice
tests: the AGENTS.md cap is 482 for #188's exit 1 in step 7, and the …
Merge origin/main (1f90a0f: #304 via #371, #276 via #354, #323 via #380…
Merge origin/main 92e29d9 into fix/adopt-root-kept-symlinks: #190's r…
Merge origin/main (75052cb: #188 via #445; decff87, dba5fb0, 245d775)…