githubnext / githubnext/autoloop

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

Aperta
#50 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
Lingua principale
Python
Stelle
71
Fork
6
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Descrizione

## 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`:

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

(line ~54)

And later:

```markdown
### 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`:

```python
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:**

```diff
### 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.

```python
# workflows/scripts/autoloop_scheduler.py

ISSUE_TITLE_RE = re.compile(
r"^(?:\[Autoloop\]\s+)?\[Autoloop:\s+(?P[^\]]+)\]\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: ]` or `[Autoloop: ]` 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: ]` 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:

```python
# 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-` branch.
3. Delete the `autoloop-autoloop-.md` state file on `memory/autoloop`.
4. Rename the program issue from `[Autoloop] [Autoloop: ]` to `[Autoloop: ]` (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

- #38 — introduced the auto-created program issue that triggered this collision; this issue is a follow-up fix, not a regression of #38.
- #46 — versioning/release-notes: cleanup instructions for affected deployments should ship in the release notes of whichever version lands this fix.

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.