Symptom
Iteration 279 of perf-comparison failed to open a new PR with:
E003: Cannot create pull request with more than 100 files (received 1434). The code changes were not applied.
(See the comment on #131: #131 (comment).)
The iteration itself only added 5 new benchmark pairs (~10 files). The other 1424 file changes were historical.
Root cause
The canonical branch autoloop/perf-comparison has drifted far behind main:
main → autoloop/perf-comparison : ahead_by=97, behind_by=0, files_count=292
autoloop/perf-comparison → main : ahead_by=0, behind_by=97, total_commits=0
All of the branch's history is already in main (it landed via the merged hash-suffixed PRs from the preserve-branch-name upstream bug — #128, #141, #142, #147, #148, #150, etc.). The canonical branch just hasn't been updated since its last successful merge.
When the agent starts a new iteration on that stale branch and tries to open a new PR (because the previous PR has merged and closed), gh-aw generates a patch that walks the 97 divergent commits and emits ~1434 file-change operations (≈292 net files × multiple touches across the commit series). gh-aw's hard-coded MAX_FILES = 100 in create_pull_request.cjs rejects the patch with E003.
The iteration's actual change is ~10 files. The other 1424 file ops are noise from a branch that's just out of date.
Fix
Before the agent starts writing code, bring the canonical branch to exactly main's HEAD when possible:
git fetch origin main autoloop/{program-name}
ahead=$(git rev-list --count origin/main..origin/autoloop/{program-name})
behind=$(git rev-list --count origin/autoloop/{program-name}..origin/main)
if [ "$ahead" = "0" ] && [ "$behind" != "0" ]; then
# Safe fast-forward: all the branch's commits are already in main.
git checkout -B autoloop/{program-name} origin/main
git push --force-with-lease origin autoloop/{program-name}
elif [ "$ahead" != "0" ] && [ "$behind" != "0" ]; then
# True divergence: merge main into the branch (current behavior).
git checkout -B autoloop/{program-name} origin/autoloop/{program-name}
git merge origin/main --no-edit -m "Merge main into autoloop/{program-name}"
else
# Already at main, or nothing to do.
git checkout -B autoloop/{program-name} origin/autoloop/{program-name}
fi
Key behavior change: when ahead=0, behind>0, do a fast-forward (force-push), not a merge. Fast-forward is lossless because ahead=0 proves every commit on the branch is already reachable from main. A merge in this case produces a "merge main into branch" commit that re-exposes all 292 files as patch touches — exactly the failure mode that caused E003.
The relevant prompt text in .github/workflows/autoloop.md (around L810) currently does an unconditional merge:
if git ls-remote --exit-code origin autoloop/{program-name}; then
git checkout -b autoloop/{program-name} origin/autoloop/{program-name}
git merge origin/main --no-edit -m "Merge main into autoloop/{program-name}"
Replace with the branch-reset logic above.
Why this is the right fix (not upstream gh-aw's MAX_FILES)
The upstream gh-aw limit of 100 files per PR is hard-coded (confirmed by reading actions/setup/js/create_pull_request.cjs::enforcePullRequestLimits()). We could request that it be made configurable, but:
- Even with a higher limit, a 1434-file PR is not what we want — it's a symptom of stale-branch noise.
- The real PR content is 10 files. Making PRs small and focused is good for review, for CI runtime, and for reasoning about what an iteration actually did.
- Fixing the branch-freshness problem also improves diff readability for humans who look at autoloop PRs.
An upstream max-files config is still worth asking for as a safety net (filed separately is fine), but this branch-hygiene fix is the primary solution.
Related workflow: sync-branches.md
autoloop.md (L736) states:
A sync workflow automatically merges the default branch into all active autoloop/* branches whenever the default branch changes, keeping them up to date.
There's a corresponding .github/workflows/sync-branches.md. That workflow is clearly not keeping autoloop/perf-comparison in sync — the branch is 97 commits behind. Two things to investigate in a follow-up:
- Is
sync-branches actually running? Check its recent runs.
- If it is running, is it doing a merge (creating new merge commits that re-expose old diffs) rather than a fast-forward?
If sync-branches adopts the same ahead=0 → fast-forward, else → merge logic proposed above, it would keep all autoloop branches clean and avoid the need for per-iteration defensive logic.
Acceptance
- On
autoloop/perf-comparison today: the branch fast-forwards to main (behind=0, ahead=0) before the next iteration runs.
- A fresh iteration that adds 5 benchmark pairs produces a PR with ~10 files changed, not 1434. No E003.
sync-branches (or the equivalent in autoloop.md) keeps autoloop branches at ahead=0, behind=0 between iterations whenever possible.
Cross-references
Symptom
Iteration 279 of
perf-comparisonfailed to open a new PR with:(See the comment on #131: #131 (comment).)
The iteration itself only added 5 new benchmark pairs (~10 files). The other 1424 file changes were historical.
Root cause
The canonical branch
autoloop/perf-comparisonhas drifted far behind main:All of the branch's history is already in main (it landed via the merged hash-suffixed PRs from the
preserve-branch-nameupstream bug — #128, #141, #142, #147, #148, #150, etc.). The canonical branch just hasn't been updated since its last successful merge.When the agent starts a new iteration on that stale branch and tries to open a new PR (because the previous PR has merged and closed), gh-aw generates a patch that walks the 97 divergent commits and emits ~1434 file-change operations (≈292 net files × multiple touches across the commit series). gh-aw's hard-coded
MAX_FILES = 100increate_pull_request.cjsrejects the patch with E003.The iteration's actual change is ~10 files. The other 1424 file ops are noise from a branch that's just out of date.
Fix
Before the agent starts writing code, bring the canonical branch to exactly main's HEAD when possible:
git fetch origin main autoloop/{program-name} ahead=$(git rev-list --count origin/main..origin/autoloop/{program-name}) behind=$(git rev-list --count origin/autoloop/{program-name}..origin/main) if [ "$ahead" = "0" ] && [ "$behind" != "0" ]; then # Safe fast-forward: all the branch's commits are already in main. git checkout -B autoloop/{program-name} origin/main git push --force-with-lease origin autoloop/{program-name} elif [ "$ahead" != "0" ] && [ "$behind" != "0" ]; then # True divergence: merge main into the branch (current behavior). git checkout -B autoloop/{program-name} origin/autoloop/{program-name} git merge origin/main --no-edit -m "Merge main into autoloop/{program-name}" else # Already at main, or nothing to do. git checkout -B autoloop/{program-name} origin/autoloop/{program-name} fiKey behavior change: when
ahead=0, behind>0, do a fast-forward (force-push), not a merge. Fast-forward is lossless becauseahead=0proves every commit on the branch is already reachable from main. A merge in this case produces a "merge main into branch" commit that re-exposes all 292 files as patch touches — exactly the failure mode that caused E003.The relevant prompt text in
.github/workflows/autoloop.md(around L810) currently does an unconditional merge:Replace with the branch-reset logic above.
Why this is the right fix (not upstream gh-aw's MAX_FILES)
The upstream gh-aw limit of 100 files per PR is hard-coded (confirmed by reading
actions/setup/js/create_pull_request.cjs::enforcePullRequestLimits()). We could request that it be made configurable, but:An upstream
max-filesconfig is still worth asking for as a safety net (filed separately is fine), but this branch-hygiene fix is the primary solution.Related workflow:
sync-branches.mdautoloop.md(L736) states:There's a corresponding
.github/workflows/sync-branches.md. That workflow is clearly not keepingautoloop/perf-comparisonin sync — the branch is 97 commits behind. Two things to investigate in a follow-up:sync-branchesactually running? Check its recent runs.If
sync-branchesadopts the sameahead=0 → fast-forward, else → mergelogic proposed above, it would keep all autoloop branches clean and avoid the need for per-iteration defensive logic.Acceptance
autoloop/perf-comparisontoday: the branch fast-forwards to main (behind=0, ahead=0) before the next iteration runs.sync-branches(or the equivalent inautoloop.md) keeps autoloop branches atahead=0, behind=0between iterations whenever possible.Cross-references
create_pull_requestinstead ofpush_to_pull_request_branchin the first place. When state is read correctly andexisting_pris populated, the agent pushes to the existing PR (which has no MAX_FILES limit). Fixing Autoloop pre-step can't read state files — build-tsb starved since 2026-04-12 #162 AND this issue together means fewer E003 failures from both directions.preserve-branch-nameregression that created the hash-suffixed-branch mess in the first place: create-pull-request: preserve-branch-name silently bypassed when remote branch already exists github/gh-aw#27454.