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.
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, authorapp/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.mdandlectures/_toc.ymllanded 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_KEYandQUANTECON_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:
translation-sync-failureissueaction_required, notfailure— it is not a failed runOn 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:examples/detect-stalled-sync.yml— a scheduled workflow for the source repo that lists recentsync-translations-*runs withconclusion: action_requiredand opens or updates a tracking issue. Self-contained, drops into any wired source repo.status-translationscollector, which already walks these workflows nightly, to flagaction_requiredruns 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 addtranslate doctor --stalled) to query the Actions API for sync runs parked inaction_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.mdcovers "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_atwell aftercreated_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 requiresstartsWithon the trimmed body (src/inputs.ts:307). Two consequences worth tidying:Either relax the action to
contains, or keepstartsWithand 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]topush: 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:
lectures/_config.yml— correctly excluded by the paths filter.lectures/workspace.md, but predates the workflows (zh-cn and fa added 2026-03-20, fr 2026-07-15).lecture-python-introandlecture-python.mysthave 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.mdin the private planning repo.