From 65a26b70d70992ada30d15bd6d107678a41838b6 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Tue, 21 Jul 2026 09:42:43 +1000 Subject: [PATCH 1/2] Migrate translation workflows to @v0, adopt the review template, enable -W MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves review and rebase off the exact `@v0.16.1` pin onto the floating `@v0` tag, per the pin policy settled in QuantEcon/project-translation#9, and aligns the review workflow with the upstream template in action-translation docs/user/tutorials/connect-existing.md. Two substantive changes beyond the pin: **Concurrency moves from workflow level to job level**, and `cancel-in-progress` from false to true. The previous comment argued that job-level `true` was unsafe because an 'automated' label event would cancel the in-flight review for 'action-translation' and then skip its own job via the filter, leaving no review at all. That was reasoned from the GitHub docs and never tested, and production disproved it: across roughly 15 live opportunities on lecture-intro.zh-cn the group was entered only after the `if` passed, the 'automated' event skipped without cancelling, and a review was posted every time. The stale rationale is replaced rather than left in place. **A `permissions` block is added** — v0.17.0's review dedupe deletes superseded comments, which requires `pull-requests: write`. The pin was two releases behind, which matters here: v0.18.0 carries the fix for a review-mode defect where a model response missing a criterion score became NaN and rendered as an automatic FAIL on otherwise clean PRs. See QuantEcon/action-translation#102. Separately, `ci.yml` gains `-W` on the notebook build. This repo's publish.yml already builds strict, so the PR gate was weaker than the publish gate — a corrupted sync could merge green and then break the published site. Coverage is 26 of 26 lectures with a `_toc.yml` identical to source, so there are no untranslated-lecture references for the flag to trip on. The strict build is the last line of defence against the silent-corruption class in QuantEcon/action-translation#118 and #119, both of which surfaced only because a downstream repo built with -n -W. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 2 +- .github/workflows/rebase-translations.yml | 2 +- .github/workflows/review-translations.yml | 54 ++++++++++++----------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5e6ae2..ee8e2c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: - name: Build HTML shell: bash -l {0} run: | - jb build lectures --path-output ./ -n --keep-going + jb build lectures --path-output ./ -n -W --keep-going - name: Upload Execution Reports uses: actions/upload-artifact@v7 if: failure() diff --git a/.github/workflows/rebase-translations.yml b/.github/workflows/rebase-translations.yml index 1fc1dd5..37f5daa 100644 --- a/.github/workflows/rebase-translations.yml +++ b/.github/workflows/rebase-translations.yml @@ -34,7 +34,7 @@ jobs: steps: - name: Rebase open translation PRs - uses: QuantEcon/action-translation@v0.16.1 + uses: QuantEcon/action-translation@v0 with: mode: rebase anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} diff --git a/.github/workflows/review-translations.yml b/.github/workflows/review-translations.yml index 4ce99c2..1173877 100644 --- a/.github/workflows/review-translations.yml +++ b/.github/workflows/review-translations.yml @@ -1,48 +1,50 @@ # Review Translations — Quality check on translation PRs # When a PR is opened/updated that carries the 'action-translation' label, # this workflow runs a quality review and posts a comment. +# +# Mirrors the upstream template in action-translation +# docs/user/tutorials/connect-existing.md — keep it in step with that. name: Review Translations on: pull_request: types: [opened, synchronize, labeled, reopened] -# Serialise reviews per PR. -# -# The sync action creates the PR and then applies its labels in a separate call, -# so a single sync fires `opened` plus one `labeled` event per label, all within -# a couple of seconds. Every one of those starts a full review, and the action's -# "update the existing comment, else create one" logic is a check-then-act with -# no lock — concurrent runs all observe "no comment yet" and each create one. -# See QuantEcon/lecture-python-programming.fr#6, which collected two review -# comments this way, and QuantEcon/action-translation#96 for the upstream bug. -# -# cancel-in-progress is deliberately false. The labels are applied in one API -# call, so event ordering is not guaranteed; if 'automated' arrived last it would -# cancel the in-flight review for 'action-translation' and then skip its own job -# via the filter below, leaving no review at all. Queuing instead means the first -# run creates the comment and any later run updates it — one comment, always. -concurrency: - group: review-translations-${{ github.event.pull_request.number }} - cancel-in-progress: false - jobs: review: - # Require the 'action-translation' label, and — for `labeled` events — ignore - # labels other than that one. Without the second clause the 'automated' label - # fires a second, redundant review of the identical diff. - if: >- + # Ignore `labeled` events for every other label: a sync adds its labels in a single + # addLabels call, but GitHub emits one `labeled` event per label, and each would + # otherwise start a full (billed) review of the same diff. + if: > contains(github.event.pull_request.labels.*.name, 'action-translation') && - (github.event.action != 'labeled' || - github.event.label.name == 'action-translation') + (github.event.action != 'labeled' || github.event.label.name == 'action-translation') runs-on: ubuntu-latest + # v0.17.0's review dedupe deletes superseded comments, which needs pull-requests: write. + permissions: + contents: read + pull-requests: write + + # One review per PR — supersede an in-flight review instead of running both. + # Job-level (not workflow-level) on purpose: the group is entered only after the `if` + # above has passed, so a `labeled` event for 'automated' skips out without cancelling + # the real review. At workflow level it would cancel first and skip second, leaving none. + # + # This supersedes the workflow-level `cancel-in-progress: false` previously used here, + # whose rationale — that an 'automated' label event would cancel the real review and + # then skip its own job, leaving none — was reasoned from the docs and never tested. + # Production disproved it: across ~15 live opportunities on lecture-intro.zh-cn the + # 'automated' event skipped without cancelling and a review was posted every time. + concurrency: + group: review-translations-${{ github.event.pull_request.number }} + cancel-in-progress: true + steps: - uses: actions/checkout@v7 with: fetch-depth: 2 - - uses: QuantEcon/action-translation@v0.16.1 + - uses: QuantEcon/action-translation@v0 with: mode: review source-repo: QuantEcon/lecture-python-programming From 0f1efa100326e1cea0c2761fddb9e861812e557a Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Tue, 21 Jul 2026 11:22:35 +1000 Subject: [PATCH 2/2] Rebase: also match resync/* branches (action-translation v0.18.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rebase workflow only fired for `translation-sync-*` branches, which the Action's sync mode creates. The CLI's `translate forward --github` creates `resync/{stem}` branches, so merging one resync PR never rebased its siblings — during a drift-recovery wave that leaves a stack of open PRs whose bases go stale with every merge. Fixed engine-side in action-translation v0.18.1, but the action-side half is not sufficient on its own: this `if` gates whether the job runs at all, and it runs before the action does. Both layers must list both prefixes or the result is a job that never starts. Brings this file in step with the upstream template as of that release. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/rebase-translations.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rebase-translations.yml b/.github/workflows/rebase-translations.yml index 37f5daa..40799f6 100644 --- a/.github/workflows/rebase-translations.yml +++ b/.github/workflows/rebase-translations.yml @@ -1,8 +1,10 @@ # Rebase Translation PRs # # Install this workflow in the TARGET (translated) repository. -# When a translation-sync PR is merged, this workflow automatically -# rebases other open translation-sync PRs against the updated main branch. +# When a translation PR is merged, this workflow automatically rebases the +# other open translation PRs against the updated main branch. It covers both +# kinds this tool creates: `translation-sync-*` branches from the Action's sync +# mode, and `resync/*` branches from the CLI's `forward --github`. # # This eliminates merge conflicts caused by multiple upstream PRs # modifying the same files. See: https://github.com/QuantEcon/action-translation/issues/63 @@ -17,10 +19,16 @@ on: jobs: rebase: - # Only run when a translation-sync PR is merged + # Only run when a translation PR is merged. Both prefixes must be listed: + # sync mode creates `translation-sync-*`, while the CLI's `forward --github` + # creates `resync/*`, and a wave of resync PRs goes stale the same way. + # Keep this in step with `isTranslationBranch` in the action's src/branch-naming.ts + # — this `if` decides whether the job runs, that predicate decides which open PRs + # it then rebases, so a prefix matching only one of them is a no-op run. if: > github.event.pull_request.merged == true && - startsWith(github.event.pull_request.head.ref, 'translation-sync-') + (startsWith(github.event.pull_request.head.ref, 'translation-sync-') || + startsWith(github.event.pull_request.head.ref, 'resync/')) runs-on: ubuntu-latest permissions: