Skip to content

Duplicate programs created because auto-created program issue title gets double-prefixed with [Autoloop] and scheduler treats it as a separate program #50

Description

@mrjf

Summary

The program-issue auto-creation from #38 has two latent bugs that combine to produce duplicate programs (and therefore duplicate branches and PRs) the moment a file-based program runs its first iteration:

  1. Double [Autoloop] prefix on the auto-created program issue title. The safe-outputs create-issue: title-prefix: "[Autoloop] " auto-prepends the prefix, and the agent prompt also instructs the agent to use [Autoloop: {program-name}] — so the final title is [Autoloop] [Autoloop: {program-name}].

  2. No dedupe between file-based and issue-based program discovery. slugify_issue_title produces autoloop-autoloop-{name} for the double-prefixed title, which does not match the file-based program name {name}. On the next scheduler run, both get discovered as separate programs and both get scheduled.

Result: one file-based program ends up with its own branch/PR/state file, and additionally a shadow "autoloop-autoloop-{name}" program with its own branch/PR/state file. Work gets duplicated, the population in the state file splits, and maintainers see two draft PRs they can't reconcile.

Evidence — how I can tell upstream has this bug

Evidence for Bug 1 (double prefix)

In workflows/autoloop.md:

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

(line ~54)

And later:

### Auto-Creation for File-Based Programs

If `selected_issue` is `null` in `/tmp/gh-aw/autoloop.json`, the program is file-based **and** has no program issue yet. On the first run, create one with `create-issue`:

- **Title**: `[Autoloop: {program-name}]` (the `[Autoloop] ` prefix is added automatically by the safe-output `title-prefix`, so pass the title as `{program-name}`).

(line ~530)

The parenthetical advises the agent to pass "the title as {program-name}" — i.e. a bare name like my-program. But the bold line right before says "Title: [Autoloop: {program-name}]" — i.e. the brackets+prefix form. Agents in practice follow the bold line and supply [Autoloop: my-program] as the title; the safe-output then prepends [Autoloop] [Autoloop] [Autoloop: my-program].

Evidence for Bug 2 (no dedupe)

workflows/scripts/autoloop_scheduler.py::slugify_issue_title:

def slugify_issue_title(title, number=None):
    """Slugify a GitHub issue title into a program name."""
    slug = re.sub(r"[^a-z0-9]+", "-", (title or "").lower()).strip("-")
    slug = re.sub(r"-+", "-", slug)  # collapse consecutive hyphens
    if not slug:
        slug = "issue-{}".format(number) if number is not None else "issue"
    return slug

Given [Autoloop] [Autoloop: my-program], this produces autoloop-autoloop-my-program. That doesn't match the file-based name my-program, so the scheduler treats them as two programs.

There is no subsequent "match issue title to file-based program name" step that would collapse them.

Fix

Three coordinated changes.

Fix 1 — remove the double-prefix ambiguity in the agent prompt

Pick one:

Option A (recommended) — remove the conflict in the prose:

 ### Auto-Creation for File-Based Programs

 If `selected_issue` is `null` in `/tmp/gh-aw/autoloop.json`, the program is file-based **and** has no program issue yet. On the first run, create one with `create-issue`:

-- **Title**: `[Autoloop: {program-name}]` (the `[Autoloop] ` prefix is added automatically by the safe-output `title-prefix`, so pass the title as `{program-name}`).
+- **Title**: supply `[Autoloop: {program-name}]` as the title. **Do not prepend `[Autoloop] `** — the `create-issue` safe-output adds that automatically. Final rendered title will be `[Autoloop] [Autoloop: {program-name}]`, which is intentional: the outer `[Autoloop] ` is the repo-wide filter prefix, and the inner `[Autoloop: {program-name}]` is the program identifier.

That makes the agent's correct behaviour unambiguous. But it also commits to the "double-prefix" rendered title being intentional — which makes Fix 2 mandatory rather than optional.

Option B — drop the outer prefix for create-issue on program issues:

Remove title-prefix: "[Autoloop] " from the create-issue safe-outputs config, and have the agent supply the full title ([Autoloop: {program-name}]) directly. Cleaner semantics, smaller rendered title, no double prefix. Downside: other agentic workflows may rely on the prefix convention for filtering across the whole repo.

Prefer A for minimal disruption.

Fix 2 — extract the canonical name from issue titles before slugifying

Change slugify_issue_title (or the callers, which seems cleaner) to first check for the canonical [Autoloop: {name}] pattern and prefer {name} as the program slug. Fall back to the existing slugify for issues authored by humans with free-form titles.

# workflows/scripts/autoloop_scheduler.py

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:
    """Return the canonical program name if the title matches the auto-created
    `[Autoloop] [Autoloop: <name>]` or `[Autoloop: <name>]` pattern. Otherwise
    None — callers should fall back to `slugify_issue_title`."""
    m = ISSUE_TITLE_RE.match((title or "").strip())
    if m:
        return m.group("name").strip()
    return None

The regex:

  • Optional outer [Autoloop] prefix (from safe-outputs, if present).
  • Required inner [Autoloop: <name>] that the agent supplies.
  • Trailing whitespace tolerated.
  • Case-insensitive so typos in issue edits don't break discovery.

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

In _fetch_issue_programs, after computing the slug:

# Pseudocode — adapt to the actual call site structure.
for issue in autoloop_program_issues:
    canonical = extract_program_name_from_issue_title(issue["title"])
    if canonical is not None:
        slug = canonical
    else:
        slug = slugify_issue_title(issue["title"], issue["number"])

    if slug in file_based_programs:
        # This issue IS the program issue for the existing file-based program.
        # Attach the issue number to that program; do not create a new one.
        file_based_programs[slug].setdefault("program_issue", issue["number"])
        continue

    # True issue-based program — no file backing.
    issue_programs[slug] = {
        "issue_number": issue["number"],
        "file": write_issue_body_to_temp(issue["body"], slug),
        "title": issue["title"],
    }

With this logic:

  • A title [Autoloop] [Autoloop: my-program] → canonical name my-program → matches the file-based program → the issue's number gets attached to the file-based program's record (available as program_issue for the agent). No duplicate program.
  • A title My weekly plan authored by a human with the autoloop-program label → canonical returns None → slugifies to my-weekly-plan → registered as a standalone issue-based program as today.
  • A title [Autoloop: my-program] (Option B of Fix 1) → canonical returns my-program → same dedupe path. Works under either Fix 1 option.

Cleanup for existing deployments

Maintainers who already have shadow programs (auto-created from previous runs) can:

  1. Close the duplicated draft PR.
  2. Delete the autoloop/autoloop-<name> branch.
  3. Delete the autoloop-autoloop-<name>.md state file on memory/autoloop.
  4. Rename the program issue from [Autoloop] [Autoloop: <name>] to [Autoloop: <name>] (single prefix).

Document this cleanup in the release notes for whatever version lands this fix.

Acceptance

  • A file-based program whose first run auto-creates a program issue ends up with exactly one open draft PR (on branch autoloop/{name}) and exactly one state file ({name}.md). No shadow autoloop-{name} or autoloop-autoloop-{name} anything.
  • Issue-based programs authored by humans with arbitrary titles keep working under the slugify fallback. Tests cover both paths.
  • After the fix, re-running discovery against an existing repo that has the old double-prefix title collapses the duplicated program by matching the canonical name; no further duplication.

Related

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