Skip to content

Commit 3334733

Browse files
Copilotmrjf
andauthored
Fix multiple PRs by adding pre-step PR detection and filtering stale branches
The autoloop agent was creating new PRs with hash-suffixed branches on each iteration instead of pushing to the existing PR. Root causes addressed: 1. Pre-step now detects existing open PRs for the selected program via the GitHub API and includes `existing_pr` and `head_branch` in autoloop.json. The agent no longer needs to discover PRs itself — the information is provided upfront. 2. Agent instructions updated to check `existing_pr` first and only call `create-pull-request` when no PR exists. 3. sync-branches.md now filters out stale branches with 16-char hex suffixes (created by the framework), only syncing canonical autoloop/{name} branches. 4. Lock files recompiled. Agent-Logs-Url: https://github.com/githubnext/tsessebe/sessions/61bed3ca-384f-49d2-8f81-d557fff66250 Co-authored-by: mrjf <180956+mrjf@users.noreply.github.com>
1 parent f742d49 commit 3334733

4 files changed

Lines changed: 94 additions & 38 deletions

File tree

.github/workflows/autoloop.lock.yml

Lines changed: 18 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/autoloop.md

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -431,11 +431,47 @@ steps:
431431
if selected in issue_programs:
432432
selected_issue = issue_programs[selected]["issue_number"]
433433
434+
# Look up existing PR for the selected program's canonical branch
435+
existing_pr = None
436+
head_branch = None
437+
if selected:
438+
head_branch = f"autoloop/{selected}"
439+
owner = repo.split("/")[0] if "/" in repo else ""
440+
try:
441+
pr_api_url = (
442+
f"https://api.github.com/repos/{repo}/pulls"
443+
f"?state=open&head={owner}:{head_branch}&per_page=5"
444+
)
445+
pr_req = urllib.request.Request(pr_api_url, headers={
446+
"Authorization": f"token {github_token}",
447+
"Accept": "application/vnd.github.v3+json",
448+
})
449+
with urllib.request.urlopen(pr_req, timeout=30) as pr_resp:
450+
open_prs = json.loads(pr_resp.read().decode())
451+
if open_prs:
452+
existing_pr = open_prs[0]["number"]
453+
print(f" Found existing PR #{existing_pr} for branch {head_branch}")
454+
else:
455+
print(f" No existing PR found for branch {head_branch}")
456+
except Exception as e:
457+
print(f" Warning: could not check for existing PRs: {e}")
458+
459+
# Also check the state file for a recorded PR number as fallback
460+
if existing_pr is None:
461+
state = read_program_state(selected)
462+
pr_field = state.get("pr") or ""
463+
pr_match = re.match(r'#?(\d+)', pr_field.strip())
464+
if pr_match:
465+
existing_pr = int(pr_match.group(1))
466+
print(f" Found PR #{existing_pr} from state file for {selected}")
467+
434468
result = {
435469
"selected": selected,
436470
"selected_file": selected_file,
437471
"selected_issue": selected_issue,
438472
"selected_target_metric": selected_target_metric,
473+
"existing_pr": existing_pr,
474+
"head_branch": head_branch,
439475
"issue_programs": {name: info["issue_number"] for name, info in issue_programs.items()},
440476
"deferred": deferred,
441477
"skipped": skipped,
@@ -449,6 +485,10 @@ steps:
449485
450486
print("=== Autoloop Program Check ===")
451487
print(f"Selected program: {selected or '(none)'} ({selected_file or 'n/a'})")
488+
if existing_pr:
489+
print(f"Existing PR: #{existing_pr} (branch: {head_branch})")
490+
else:
491+
print(f"Existing PR: (none — will create on first accepted iteration)")
452492
print(f"Deferred (next run): {deferred or '(none)'}")
453493
print(f"Programs skipped: {[s['name'] for s in skipped] or '(none)'}")
454494
print(f"Programs unconfigured: {unconfigured or '(none)'}")
@@ -538,6 +578,8 @@ The pre-step has already determined which program to run. Read `/tmp/gh-aw/autol
538578
- **`selected_file`**: The full path to the program's markdown file (either `.autoloop/programs/<name>/program.md`, `.autoloop/programs/<name>.md`, or `/tmp/gh-aw/issue-programs/<name>.md` for issue-based programs).
539579
- **`selected_issue`**: The GitHub issue number if the selected program came from an issue, or `null` if it came from a file.
540580
- **`selected_target_metric`**: The `target-metric` value from the program's frontmatter (a number), or `null` if the program is open-ended. Used to check the [halting condition](#halting-condition) after each accepted iteration.
581+
- **`existing_pr`**: The PR number (e.g., `42`) of an already-open PR for this program's branch, or `null` if no open PR exists. **If this is not null, you MUST use `push-to-pull-request-branch` to push to this PR — do NOT call `create-pull-request`.**
582+
- **`head_branch`**: The canonical branch name for this program (e.g., `autoloop/coverage`). Always use this exact branch name — never append suffixes.
541583
- **`issue_programs`**: A mapping of program name → issue number for all discovered issue-based programs.
542584
- **`deferred`**: Other programs that were due but will be handled in future runs.
543585
- **`unconfigured`**: Programs that still have the sentinel or placeholder content.
@@ -550,6 +592,7 @@ If `selected` is not null:
550592
3. Read the current state of all target files.
551593
4. Read the state file `{selected}.md` from the repo-memory folder for all state: the ⚙️ Machine State table (scheduling fields) plus the research sections (priorities, lessons, foreclosed avenues, iteration history).
552594
5. If `selected_issue` is not null, this is an issue-based program — also read the issue comments for any human steering input.
595+
6. **Check `existing_pr`**: if it is not null, a PR already exists — use `push-to-pull-request-branch` to push commits to it. Only call `create-pull-request` when `existing_pr` is null.
553596

554597
## Multiple Programs
555598

@@ -694,7 +737,7 @@ Each run executes **one iteration for the single selected program**:
694737

695738
If the state file does not yet exist, create it in the repo-memory folder using the template defined in the [Repo Memory](#repo-memory) section.
696739

697-
3. Note the `PR` field from the Machine State table. If it contains a PR number (e.g., `#42`), that is the **existing draft PR** for this program — you must update it, not create a new one.
740+
3. Note the `existing_pr` field from `/tmp/gh-aw/autoloop.json`. If it is not null, that is the **existing draft PR** for this program — you must push to it using `push-to-pull-request-branch`, not create a new one. Also check the `PR` field from the Machine State table as a fallback.
698741

699742
### Step 2: Analyze and Propose
700743

@@ -743,15 +786,15 @@ Each run executes **one iteration for the single selected program**:
743786
- Commit message body (after a blank line): `Run: {run_url}` referencing the GitHub Actions run URL.
744787
2. Push the commit to the long-running branch `autoloop/{program-name}`.
745788
3. **Find the existing PR or create one** — follow these steps in order:
746-
a. Check the `PR` field in the state file's **⚙️ Machine State** table. If it contains a PR number (e.g., `#42`), that is the existing draft PR.
747-
b. If the state file has no PR number, search GitHub for open PRs with head branch `autoloop/{program-name}`. Use the GitHub API: `GET /repos/{owner}/{repo}/pulls?state=open&head={owner}:autoloop/{program-name}`.
748-
c. **If an existing PR is found** (from either step a or b): use `push-to-pull-request-branch` to push additional commits to the existing PR. Update the PR body with the latest metric and a summary of the most recent accepted iteration. Add a comment to the PR summarizing the iteration: what changed, old metric, new metric, improvement delta, and a link to the actions run. **Do NOT call `create-pull-request`.**
749-
d. **If NO PR exists** for `autoloop/{program-name}`: create one using `create-pull-request`:
789+
a. **First, check `existing_pr` from `/tmp/gh-aw/autoloop.json`.** The pre-step has already looked up the open PR for this program. If `existing_pr` is not null, that is the existing draft PR — skip to step (c).
790+
b. If `existing_pr` is null, also check the `PR` field in the state file's **⚙️ Machine State** table as a fallback. If it contains a PR number (e.g., `#42`), verify it is still open via the GitHub API.
791+
c. **If an existing PR is found** (from step a or b): use `push-to-pull-request-branch` to push additional commits to the existing PR. Update the PR body with the latest metric and a summary of the most recent accepted iteration. Add a comment to the PR summarizing the iteration: what changed, old metric, new metric, improvement delta, and a link to the actions run. **Do NOT call `create-pull-request`.**
792+
d. **If NO PR exists** for `autoloop/{program-name}` (both `existing_pr` is null AND the state file has no PR): create one using `create-pull-request`:
750793
- Branch: `autoloop/{program-name}` (the branch you already created in Step 3 — do NOT let the framework auto-generate a branch name)
751794
- Title: `[Autoloop: {program-name}]`
752795
- Body includes: a summary of the program goal, link to the steering issue, the current best metric, and AI disclosure: `🤖 *This PR is maintained by Autoloop. Each accepted iteration adds a commit to this branch.*`
753796

754-
> ⚠️ **Never create a new PR if one already exists for `autoloop/{program-name}`.** Each program must have exactly one draft PR at any time. If you are unsure whether a PR exists, check the GitHub API before calling `create-pull-request`.
797+
> ⚠️ **Never create a new PR if one already exists for `autoloop/{program-name}`.** Each program must have exactly one draft PR at any time. The pre-step provides `existing_pr` in autoloop.json — always check it first. Only call `create-pull-request` when `existing_pr` is null AND the state file has no PR number.
755798
4. Ensure the steering issue exists (see [Steering Issue](#steering-issue) below). Add a comment to the steering issue linking to the commit and actions run.
756799
5. Add an entry to the experiment log issue.
757800
6. Update the state file `{program-name}.md` in the repo-memory folder:
@@ -1148,9 +1191,10 @@ After each iteration, prepend an entry to the **📊 Iteration History** section
11481191
> **Do NOT create a new branch with a suffix for each iteration.**
11491192
> Correct: `autoloop/coverage`
11501193
> Wrong: `autoloop/coverage-abc123`, `autoloop/coverage-iter42`, `autoloop/coverage-deadbeef1234`
1194+
> Use the `head_branch` field from `autoloop.json` — it is always the canonical name.
11511195
11521196
> **Do NOT create a new PR if one already exists for `autoloop/{program-name}`.**
1153-
> Always check the state file's `PR` field and the GitHub API before calling `create-pull-request`. If a PR exists, use `push-to-pull-request-branch` instead.
1197+
> The pre-step provides `existing_pr` in `autoloop.json`. If it is not null, **always** use `push-to-pull-request-branch` — never call `create-pull-request`. Only create a PR when `existing_pr` is null AND the state file has no PR number.
11541198
11551199
> **Do NOT let the gh-aw framework auto-generate a branch name when creating a PR.**
11561200
> Always specify the branch explicitly as `autoloop/{program-name}` when calling `create-pull-request`.

0 commit comments

Comments
 (0)