Skip to content

Sync silently does not run when a merged source PR has an agent-authored head branch #234

Description

@mmcky

The defect

A merged source PR whose head branch was authored by an agent does not sync, and nothing reports the miss.

Observed 2026-07-30 on lecture-python-programming#408 — head copilot/fix-407, author app/copilot-swe-agent, merged by @jstac. All three sync runs (French #28, Farsi #198, Simplified Chinese #198) were created three seconds after the merge and never executed: conclusion: action_required, run_attempt: 1, run_started_at == created_at. GitHub was holding them for a maintainer to click "Approve and run workflows". lectures/polars.md and lectures/_toc.yml landed in English only, leaving three editions drifted.

The approval gate is evaluated against the head commit's author, not against whoever merges — so a trusted human merging an agent-authored PR does not clear it. This is correct and desirable GitHub behaviour: these workflows carry ANTHROPIC_API_KEY and QUANTECON_SERVICES_PAT, and agent-authored branches are exactly what the gate protects. The bug is not the gate. The bug is that we cannot tell it fired.

Why our safety nets missed it

Both existing nets live inside the action, so a run that never starts is invisible to them:

Net Why it did not fire
Confirmation comment on the source PR Posted by the action; the action never ran
Auto-filed translation-sync-failure issue Filed by the action; the action never ran
Workflow-level failure notification The run's conclusion is action_required, not failure — it is not a failed run

On an open PR the gate is visible: checks sit pending and the merge box reports them. On a merged PR nothing blocks and nothing is pending. The editions simply drift, silently, until someone happens to look.

Recovery works and is already documented — the resync command on the merged PR runs against main, is not an agent-authored ref, and is therefore not gated. Verified on #408 on 2026-07-31: all three runs started immediately. That is a fine recovery; the gap is that it depends on a human noticing.

Suggested improvements

1. Detection from outside the gated path — the actual fix. Anything that can observe this must not itself be a pull_request-triggered workflow on the agent's ref. Two candidates, not mutually exclusive:

  • Ship an examples/detect-stalled-sync.yml — a scheduled workflow for the source repo that lists recent sync-translations-* runs with conclusion: action_required and opens or updates a tracking issue. Self-contained, drops into any wired source repo.
  • Teach the status-translations collector, which already walks these workflows nightly, to flag action_required runs on the dashboard. Better placement for the program view, but only covers repos the collector knows about.

2. A manual check in the CLI. Extend translate status (or add translate doctor --stalled) to query the Actions API for sync runs parked in action_required. Gives maintainers a way to check on demand without standing up new CI, and is useful when wiring a new pair.

3. Document the failure mode. docs/user/faq.md covers "What happens when the sync workflow fails?" — which only describes the case where the action runs and errors. It should also cover the case where the run never starts, with the recognition fingerprint (actor = agent, triggering_actor = approver, run_started_at well after created_at) and the resync recovery.

4. Align the resync gate with the resync parser. The generated workflow matches with contains(github.event.comment.body, '\translate-resync') (src/cli/commands/setup.ts:169), but the action requires startsWith on the trimmed body (src/inputs.ts:307). Two consequences worth tidying:

  • A comment that merely mentions the command starts a run that then no-ops.
  • A comment where the command is not at the start is silently ignored after starting a run.
  • Trailing prose after the command is parsed as a language code, hits the unsupported-language branch, warns, and falls back to an all-language resync. It lands on the right behaviour, but by luck rather than design.

Either relax the action to contains, or keep startsWith and make "own comment, command first" explicit in the FAQ.

5. Trigger redesign — a design question, not a recommendation. Moving the sync trigger from pull_request: types: [closed] to push: branches: [main] would sidestep the gate entirely if push events after a merge are attributed to the merger rather than the head-commit author — that assumption needs verifying before anyone builds on it. The cost is real: sync mode is PR-oriented, using the PR number for the confirmation comment, the failure issue, and changed-file derivation, so the action would need to resolve the PR from the merge commit (GET /repos/{owner}/{repo}/commits/{sha}/pulls). Raising it for the record rather than proposing it.

Scope

Any source repo with sync wiring that accepts agent-authored PRs. #408 is the first case to bite in this estate:

  • #483 (Copilot, merged 2026-03-22) touched only lectures/_config.yml — correctly excluded by the paths filter.
  • #460 (Copilot, merged 2025-12-29) touched lectures/workspace.md, but predates the workflows (zh-cn and fa added 2026-03-20, fr 2026-07-15).
  • lecture-python-intro and lecture-python.myst have had no agent-authored PRs yet — exposed but untested.

As agent-authored PRs become more common across the lecture repos, this moves from a one-off to a standing hazard.

Program-side notes are in infrastructure/sync-workflow-wiring.md in the private planning repo.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghigh-priorityAddress soon

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions