Rollout step 3 of #192: the four deployed sync workflows in the production source repos still carry the ungated \translate-resync trigger. #219 fixes the canonical templates in this repo — it does not touch anything already deployed, and there is no mechanism by which it would. These four need the edit applied by hand, in their own repos.
Why this is not "already fixed by #219"
The action pins @v0, so deployed workflows do pick up new action code — but the gate is in the workflow file, not in the action. src/inputs.ts has always enforced the same three-way association set internally, so a hostile resync is rejected inside the run; the run still starts, still checks out, still resolves and loads the action bundle, and still holds ANTHROPIC_API_KEY and the services PAT in its environment while doing so. Runner minutes are spent per comment, and they are spent as fast as someone can comment. The workflow-level gate is the only thing that stops the job from starting at all.
All three repos are public (lecture-python-intro 31 forks, lecture-python-programming 30, lecture-python.myst 58), so "any GitHub account" is the real audience, not a hypothetical one.
Current state — audited 2026-07-26
| Repo / workflow |
on a PR? |
trusted author? |
permissions: |
status |
lecture-python-intro / sync-translations-zh-cn.yml |
✗ |
✗ |
✗ |
needs fix |
lecture-python-programming / sync-translations-zh-cn.yml |
✗ |
✗ |
✗ |
needs fix |
lecture-python-programming / sync-translations-fa.yml |
✗ |
✗ |
✗ |
needs fix |
lecture-python-programming / sync-translations-fr.yml |
✗ |
✗ |
✗ |
needs fix |
lecture-python.myst / sync-translations-zh-cn.yml |
✓ |
✓ |
✓ |
done (8abdb57) — this is the reference shape |
test-translation-sync / sync-translations-{zh-cn,fa,ml}.yml |
✓ |
✗ |
✗ |
step 2 — self-heals, see below |
Audited by fetching each file from its default branch and checking for github.event.issue.pull_request, author_association and a job-level permissions: block. The set is complete: an org-wide code search for translate-resync returns these eight workflows and nothing else outside this repo.
The change
Two edits per file. Replace the issue_comment clause of the job's if:, and add a job-level permissions: block after runs-on:.
jobs:
sync:
# The issue_comment path requires all three: a comment on a PR (not a bare
# issue), the command, and a trusted author — otherwise any account could
# fire a secrets-bearing run (Anthropic spend plus the PAT) from any comment.
if: >
(github.event_name == 'pull_request' && github.event.pull_request.merged == true) ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '\translate-resync') &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association))
runs-on: ubuntu-latest
# The action authenticates with QUANTECON_SERVICES_PAT; the ambient
# GITHUB_TOKEN is unused beyond checkout, so keep it read-only.
permissions:
contents: read
steps:
...
Nothing else in these files changes — same triggers, same paths:, same inputs, same @v0 pin.
Note the secret name in that comment: the production repos authenticate with QUANTECON_SERVICES_PAT, whereas the canonical templates in this repo say TRANSLATION_PAT. Neither is wrong; only the comment differs.
Checklist
The three lecture-python-programming files can go in one PR; lecture-python-intro needs its own.
Verification, and the one thing worth actually testing
The if: uses a folded scalar whose continuation lines are more-indented, so YAML preserves those newlines rather than folding them to spaces — the expression GitHub evaluates contains literal newlines. That is fine, and it is not theory: this exact shape has been live on lecture-python.myst since 2026-07-22 and both branches have fired since (issue_comment runs on 07-23, pull_request runs through 07-26). Still worth confirming per repo after the edit, because the failure mode is silent — a broken if: does not error, it simply stops the workflow firing on merges, which looks exactly like "no PRs have been merged lately".
Cheapest confirmation, in order: (1) GitHub's own workflow linter rejects a malformed if: on push, so a clean push is already evidence; (2) merge any PR touching lectures/ and check the run appears; (3) comment \translate-resync on a recent merged PR as a trusted user and confirm it fires, then confirm a comment lacking the command does not.
Related work, deliberately not in scope here
Step 2, the harness (test-translation-sync) is not a manual edit. Those three workflows are rendered and force-pushed from tool-test-action-on-github/test-action-on-github-data/sync-workflow-template.yml, which #219 fixes — so they pick the gate up on the next harness run, and a hand-edit would be overwritten. Until then they are ungated (they already require issue.pull_request, so the exposure is narrower). The harness repo is public but exists to be reset, so the cost exposure is bounded by whoever notices it.
Target repos (*.zh-cn, *.fa, *.fr, *.ml) are unaffected: their review and rebase workflows have no issue_comment trigger and already carry permissions: blocks from examples/.
Refs #192, #219, #138. Original sightings: QuantEcon/test-translation-sync#671 and QuantEcon/lecture-python.myst#979.
Rollout step 3 of #192: the four deployed sync workflows in the production source repos still carry the ungated
\translate-resynctrigger. #219 fixes the canonical templates in this repo — it does not touch anything already deployed, and there is no mechanism by which it would. These four need the edit applied by hand, in their own repos.Why this is not "already fixed by #219"
The action pins
@v0, so deployed workflows do pick up new action code — but the gate is in the workflow file, not in the action.src/inputs.tshas always enforced the same three-way association set internally, so a hostile resync is rejected inside the run; the run still starts, still checks out, still resolves and loads the action bundle, and still holdsANTHROPIC_API_KEYand the services PAT in its environment while doing so. Runner minutes are spent per comment, and they are spent as fast as someone can comment. The workflow-level gate is the only thing that stops the job from starting at all.All three repos are public (
lecture-python-intro31 forks,lecture-python-programming30,lecture-python.myst58), so "any GitHub account" is the real audience, not a hypothetical one.Current state — audited 2026-07-26
permissions:lecture-python-intro/sync-translations-zh-cn.ymllecture-python-programming/sync-translations-zh-cn.ymllecture-python-programming/sync-translations-fa.ymllecture-python-programming/sync-translations-fr.ymllecture-python.myst/sync-translations-zh-cn.ymltest-translation-sync/sync-translations-{zh-cn,fa,ml}.ymlAudited by fetching each file from its default branch and checking for
github.event.issue.pull_request,author_associationand a job-levelpermissions:block. The set is complete: an org-wide code search fortranslate-resyncreturns these eight workflows and nothing else outside this repo.The change
Two edits per file. Replace the
issue_commentclause of the job'sif:, and add a job-levelpermissions:block afterruns-on:.Nothing else in these files changes — same triggers, same
paths:, same inputs, same@v0pin.Note the secret name in that comment: the production repos authenticate with
QUANTECON_SERVICES_PAT, whereas the canonical templates in this repo sayTRANSLATION_PAT. Neither is wrong; only the comment differs.Checklist
QuantEcon/lecture-python-intro—sync-translations-zh-cn.ymlQuantEcon/lecture-python-programming—sync-translations-zh-cn.ymlQuantEcon/lecture-python-programming—sync-translations-fa.ymlQuantEcon/lecture-python-programming—sync-translations-fr.ymlThe three
lecture-python-programmingfiles can go in one PR;lecture-python-introneeds its own.Verification, and the one thing worth actually testing
The
if:uses a folded scalar whose continuation lines are more-indented, so YAML preserves those newlines rather than folding them to spaces — the expression GitHub evaluates contains literal newlines. That is fine, and it is not theory: this exact shape has been live onlecture-python.mystsince 2026-07-22 and both branches have fired since (issue_commentruns on 07-23,pull_requestruns through 07-26). Still worth confirming per repo after the edit, because the failure mode is silent — a brokenif:does not error, it simply stops the workflow firing on merges, which looks exactly like "no PRs have been merged lately".Cheapest confirmation, in order: (1) GitHub's own workflow linter rejects a malformed
if:on push, so a clean push is already evidence; (2) merge any PR touchinglectures/and check the run appears; (3) comment\translate-resyncon a recent merged PR as a trusted user and confirm it fires, then confirm a comment lacking the command does not.Related work, deliberately not in scope here
Step 2, the harness (
test-translation-sync) is not a manual edit. Those three workflows are rendered and force-pushed fromtool-test-action-on-github/test-action-on-github-data/sync-workflow-template.yml, which #219 fixes — so they pick the gate up on the next harness run, and a hand-edit would be overwritten. Until then they are ungated (they already requireissue.pull_request, so the exposure is narrower). The harness repo is public but exists to be reset, so the cost exposure is bounded by whoever notices it.Target repos (
*.zh-cn,*.fa,*.fr,*.ml) are unaffected: their review and rebase workflows have noissue_commenttrigger and already carrypermissions:blocks fromexamples/.Refs #192, #219, #138. Original sightings: QuantEcon/test-translation-sync#671 and QuantEcon/lecture-python.myst#979.