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

@mrjf

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
    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