Skip to content

Autoloop: duplicate PRs opening because program-issue title gets double-prefixed with [Autoloop] and scheduler treats file + issue as two programs #192

@mrjf

Description

@mrjf

Symptom

Two concurrent PRs opened for the same program within 45 minutes:

Both target the same file (src/core/series.ts), both run the same benchmark, both propose evolutions to Series.sortValues. The two populations have diverged and now each iteration burns twice the cost for one real program's worth of progress.

The program issue is #189, titled [Autoloop] [Autoloop: tsb-perf-evolve] — note the duplicated [Autoloop] prefix. That title is the thing that made this possible.

Root cause — two overlapping bugs

Bug A: [Autoloop] title-prefix is applied twice

Per #165's single-issue-per-program work, the agent creates a program issue titled [Autoloop: {program-name}] via the create-issue safe output. But .github/workflows/autoloop.md's safe-outputs config also sets:

safe-outputs:
  create-issue:
    title-prefix: "[Autoloop] "
    ...

The safe-output machinery auto-prepends that prefix. Final title:

[Autoloop] [Autoloop: tsb-perf-evolve]

Evidence: issue #189 has exactly that title.

Bug B: File-based and issue-based program discovery do not deduplicate

.github/workflows/scripts/autoloop_scheduler.py discovers programs from two sources:

  1. File-based: directories under .autoloop/programs/<name>/. Program name = directory name.
  2. Issue-based: GitHub issues with the autoloop-program label. Program name = slugified title.

When the auto-created program issue from Bug A is picked up on the next scheduler run, its title slugifies:

# from the current scheduler
slug = re.sub(r'[^a-z0-9]+', '-', title.lower()).strip('-')
# "[Autoloop] [Autoloop: tsb-perf-evolve]" → "autoloop-autoloop-tsb-perf-evolve"

That slug doesn't match the file-based program name tsb-perf-evolve, so the scheduler treats them as two independent programs and schedules both. Each produces its own canonical branch, PR, state file.

Result:

Source Program name Branch PR State file
File tsb-perf-evolve autoloop/tsb-perf-evolve #190 tsb-perf-evolve.md
Issue #189 autoloop-autoloop-tsb-perf-evolve autoloop/autoloop-autoloop-tsb-perf-evolve #191 autoloop-autoloop-tsb-perf-evolve.md

This will recur for every new file-based program the moment it auto-creates its program issue. It's not a one-off.

Near-term cleanup

Before landing any fix, undo the split so tsb-perf-evolve has one home:

  1. Close PR [Autoloop] [Autoloop: autoloop-autoloop-tsb-perf-evolve] #191 with a comment explaining the duplication. Do not merge.
  2. Delete the branch autoloop/autoloop-autoloop-tsb-perf-evolve on the remote.
  3. Delete the stranded state file autoloop-autoloop-tsb-perf-evolve.md on the memory/autoloop branch.
  4. Rename issue [Autoloop: tsb-perf-evolve] #189 from [Autoloop] [Autoloop: tsb-perf-evolve] to [Autoloop: tsb-perf-evolve] (strip the duplicated prefix). The safe-outputs title-prefix: "[Autoloop] " will not re-add it on rename — prefixes are a create-time concern.
  5. Verify the next scheduler run picks up issue [Autoloop: tsb-perf-evolve] #189 as the canonical program issue for the file-based tsb-perf-evolve (match-on-name, see fix [Autoloop] [Autoloop: build-tsb-pandas-typescript-migration] #2 below). Do not proceed until the next run schedules exactly one program.

Root-cause fixes

Fix 1 — single source of truth for the [Autoloop] prefix

Pick one of these; do not do both:

Option A (preferred) — remove the duplicated prefix from the agent prompt. Let the safe-output do it. In .github/workflows/autoloop.md, change the instructions for creating a program issue from:

Title: [Autoloop: {program-name}]

to:

Title: [Autoloop: {program-name}]do not prepend [Autoloop] , the create-issue safe output adds that automatically.

And audit the agent's prompt for any other place it constructs [Autoloop] ... titles explicitly. The title the agent supplies is just the content after the prefix.

Option B — remove title-prefix: "[Autoloop] " from the create-issue safe-outputs config. The agent supplies the full title, including any [Autoloop: ...] pattern. Downside: every other agentic workflow in the repo relies on the prefix convention for filtering; changing it repo-wide is a bigger surface area.

Option A is less disruptive.

Fix 2 — dedupe file-based and issue-based program discovery

.github/workflows/scripts/autoloop_scheduler.py currently emits both the file-based and the issue-based program as separate entries in /tmp/gh-aw/autoloop.json. Change the discovery logic so that:

  • When an issue with autoloop-program label has title matching ^(\[Autoloop\]\s+)?\[Autoloop:\s+(?P<name>[^\]]+)\]\s*$, extract <name>.
  • If a file-based program with that exact name exists, merge: the issue is that program's program issue (store its number in selected_issue or a new program_issue field), not a separate program.
  • Only create a new issue-based program when the title's extracted name does not match any file-based name.

Sketch:

ISSUE_TITLE_RE = re.compile(
    r'^(?:\[Autoloop\]\s+)?\[Autoloop:\s+(?P<name>[^\]]+)\]\s*$',
    re.IGNORECASE
)

def extract_program_name_from_issue_title(title: str) -> str | None:
    m = ISSUE_TITLE_RE.match(title.strip())
    if m:
        return m.group("name").strip()
    return None

# During discovery:
for issue in autoloop_program_issues:
    name = extract_program_name_from_issue_title(issue["title"])
    if name is None:
        # Title doesn't match the canonical pattern — fall back to slugification.
        # These are user-authored issue-based programs from before this fix.
        name = slugify(issue["title"])

    if name in file_based_programs:
        # This issue IS the program issue for an existing file-based program.
        # Attach the issue number to that program; do not create a new program.
        file_based_programs[name]["program_issue"] = issue["number"]
    else:
        # True issue-based program (no file backing). Register normally.
        issue_based_programs[name] = issue

The existing slugify path stays as the fallback for issues authored by humans with arbitrary titles — we just prefer the canonical extract when the title matches the [Autoloop: …] pattern.

Fix 3 — defensive slug normalisation

Even after Fix 1 is live, old issues in the wild still have [Autoloop] [Autoloop: …] titles. Strip known prefixes before slugifying so the scheduler is robust:

def slugify(title: str) -> str:
    s = title.strip()
    # Strip a leading "[Autoloop]" marker (with optional whitespace) — the
    # safe-outputs prefix convention. Repeat until absent so doubly-prefixed
    # titles collapse cleanly.
    prefix = re.compile(r'^\[Autoloop\]\s*', re.IGNORECASE)
    while prefix.match(s):
        s = prefix.sub('', s, count=1)
    # Also strip a leading "[Autoloop: name]" pattern when present, so we
    # slugify just the name portion.
    m = re.match(r'^\[Autoloop:\s+([^\]]+)\]\s*', s, re.IGNORECASE)
    if m:
        s = m.group(1)
    return re.sub(r'[^a-z0-9]+', '-', s.lower()).strip('-')

This makes the scheduler self-healing against prefix mistakes in issue titles — whether introduced by safe-outputs, by a human, or by a future agent that follows old prompting.

Acceptance

  • Issue [Autoloop: tsb-perf-evolve] #189's title is [Autoloop: tsb-perf-evolve]; no duplicate prefix.
  • PR [Autoloop] [Autoloop: autoloop-autoloop-tsb-perf-evolve] #191 is closed; its branch and state file are removed.
  • The scheduler's next run emits exactly one program entry for tsb-perf-evolve with program_issue: 189 attached. No autoloop-autoloop-tsb-perf-evolve entry anywhere.
  • Creating a new file-based program end-to-end (scaffold + let autoloop auto-create its program issue + wait two scheduler runs) produces exactly one program and exactly one open draft PR — never two.
  • Issue-based programs authored by humans (titles without the [Autoloop: ...] pattern) continue to work under the slugify fallback.

Related

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