* fix(ci): publish inline E2E evidence
Upload bounded screenshot evidence from E2E, then publish validated images
from a trusted workflow_run job to commit-pinned branches in the evidence repo.
Wait briefly for the live CI review comment marker before publishing, so
GitHub's read-after-write delay cannot leave an orphaned evidence branch.
* fix(ci): isolate privileged credentials from PR jobs
Keep App private keys and Docker Hub credentials out of PR-controlled
workflows. Use protected environments for trusted publishing and a public
repository variable for the App client ID.
* fix(ci): attach E2E evidence with restricted bot session
Replace the App-backed evidence repository publisher with gh-image uploads
from a dedicated bot session in the gh-image environment.
* fix(ci): publish validated E2E evidence from forks
Let the trusted default-branch publisher handle bounded, validated evidence
artifacts from fork PR CI without checking out or executing fork code.
Composite actions cannot access the secrets context — the runner's
template engine rejects secrets.* references at load time with
'Unrecognized named-value: secrets'.
Move APP_ID and APP_PRIVATE_KEY from direct secrets.* references inside
the composite action to inputs passed by each calling workflow. The
fallback logic (GITHUB_TOKEN when APP_ID is empty, for fork PRs) stays
in the composite action's check step.
Replace the long-lived fine-grained PAT (AUTOFIX_BOT_PAT) with short-lived
(1-hour) installation access tokens minted via a new get-app-token composite
action wrapping actions/create-github-app-token@v3.2.0.
The PAT was used in 13 spots across 8 workflow files for gh CLI / GitHub API
calls. The per-repo GITHUB_TOKEN (1,000 req/hr) was getting rate-limited when
multiple workflows fire concurrently (deploy-site, skills-index, ci-timings,
supply-chain-audit, js-autofix). App installation tokens get 5,000 req/hr
per installation and are scoped to the App's permissions, not a user account.
New composite action: .github/actions/get-app-token/
- Wraps actions/create-github-app-token@bcd2ba49 (v3.2.0, SHA-pinned)
- Reads APP_ID + APP_PRIVATE_KEY repo secrets
- Outputs a 1hr installation token via steps.app-token.outputs.token
Requires two new repo secrets (set after creating the GitHub App):
- APP_ID: the App's numeric ID
- APP_PRIVATE_KEY: the PEM private key
App installation permissions needed:
contents: write (js-autofix push, pypi release upload)
pull-requests: write (js-autofix PR create/merge, supply-chain comment)
issues: write (skills-index-freshness issue creation)
actions: write (skills-index workflow trigger)
workflows: write (skills-index triggers deploy-site.yml)
The AUTOFIX_BOT_PAT secret can be deleted once CI passes on this PR.
The comment in js-autofix.yml noting that PAT pushes trigger downstream
workflows is updated — App tokens have the same property (they are not
GITHUB_TOKEN), so the concurrency-cancel loop logic is unchanged.
generate-patch now emits a has-fixes job output (true/false).
apply-patch is gated on it via `if: needs.generate-patch.outputs.has-fixes == 'true'`,
so when `npm run fix` produces no diff, the privileged job is skipped
entirely — no runner allocation, no redundant checkout/download/push/PR.
When the bot PR auto-merges, main moves — which the poll loop detects
as 'main moved' and tries to close the PR. But the PR is already
merged, so gh pr close fails with an error.
Fix: when main moves, re-check PR state before closing. If it's
already MERGED, exit cleanly. Also make gh pr close non-fatal (|| true)
as a belt-and-suspenders guard against the same race on the other
close paths.
The js-autofix workflow used 'gh pr create --json number --jq .number'
to capture the PR number, but 'gh pr create' doesn't support --json.
Extract the PR number from the URL that 'gh pr create' prints instead.
* fix(js): never format package-lock.json
prettier and eslint should never touch package-lock.json. main has a
repo rule requiring team approval when lockfiles change, so an autofix
PR touching it would hang waiting for review.
- Add .prettierignore at repo root
- Add '**/package-lock.json' to eslint shared config ignores
* fix(ci): js-autofix pushes via PR instead of direct push to main
Main now has repository rules requiring pull requests + required status
checks ("All required checks pass"), so the workflow's direct push to
main is rejected with GH013 every time eslint --fix produces changes.
Switch apply-patch to push to a dedicated bot/js-autofix branch, create
or update a PR, and enable auto-merge (squash). The PR auto-merges once
CI passes. If CI fails or main moves, the PR is auto-closed and the
branch deleted — the next run re-applies on the current state.
The two-job security split is preserved:
- generate-patch stays unprivileged (contents: read only) — it runs npm
on an ephemeral runner with zero push permissions.
- apply-patch (contents: write + pull-requests: write) still never runs
npm, never installs anything, never executes repo code — it applies
the trusted patch artifact and delivers it via PR.