Skip to content

Autoloop: fast-forward long-running branch to main when ahead=0 (prevents E003 on new PR creation) #178

@mrjf

Description

@mrjf

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:

  1. Is sync-branches actually running? Check its recent runs.
  2. 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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions