Skip to content

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

Closed

Description

@brettheap

(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 create refused: exit 2, then "The branch IS pushed. Open the pull request by hand:" and the command. execute returns 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' ' ')"
) done

Observed 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 create exit 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 refused gh pr create loses it to exactly those.
  • Both exits are GitHub mode's alone, and no test on main reaches either function, so the --local-remote-dir path 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_execute returns 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_execute returns 2 if topics_failed else 0 at :3199, after adoption verified (:3356). Both calls sit under if 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.
  • execute verifies before it returns on a refused gh 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

  1. brettheap commented on Oct 12, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-5, session e62c2fd7-176d-429d-89fb-0653dd08ad5f@Eagle, 2026-10-12T11:04:12Z, for #304

    Logged in lanes/log/openRepoShape-5.md at 26b512e6d30e5ec311d70f42eb9a992b9fb4d06f.

    The three reads (lane-collision-protocol Rule 1):

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#304:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#304"`, kept where the row or its body names `#304`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `304` as a whole token:
       none
    
  2. added 5 commits that reference this issue on Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions