From 99dce51a25e3323c463b094bd46742d1fd9534fd Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 9 Jul 2026 20:40:13 +0800 Subject: [PATCH 1/5] fix: require scoped Codex auto-resolve command --- .../workflows/codex-autofix-review-comments.yml | 4 ++-- scripts/check-codex-autofix-workflow.mjs | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codex-autofix-review-comments.yml b/.github/workflows/codex-autofix-review-comments.yml index e34f6c537..5b76e0fa1 100644 --- a/.github/workflows/codex-autofix-review-comments.yml +++ b/.github/workflows/codex-autofix-review-comments.yml @@ -38,7 +38,7 @@ jobs: if ( sourceBody.includes("codex-autoresolve:") || sourceBody.includes("codex-autoresolve-pr:") || - sourceBody.includes("@codex resolve all review comments") + sourceBody.includes("@codex resolve actionable Codex review findings for this pull request and current head") ) { core.notice("Skipping Codex auto-resolve request because the source comment already looks like an auto-resolve request."); return; @@ -75,7 +75,7 @@ jobs: const prompt = [ marker, ``, - "@codex resolve all review comments using the repository instructions. Always fix P0 and P1 findings. For P2 and lower findings, decide whether each is worth fixing automatically. Fix clear, scoped, low-risk issues with the best minimal change; otherwise reply explaining why the issue is deferred or not actionable. After each fix or decision, resolve the review conversation if supported. Do not use external APIs, paid services, credentials, dependency changes, or broad refactors unless explicitly authorized. Add targeted tests where behavior changes and run the narrowest relevant validation.", + "@codex resolve actionable Codex review findings for this pull request and current head using the repository instructions. Always fix P0 and P1 findings. For P2 and lower findings, decide whether each is worth fixing automatically. Fix clear, scoped, low-risk issues with the best minimal change; otherwise reply explaining why the issue is deferred or not actionable. Do not update the branch from main, address unrelated reviews, broaden scope, or create more than one scoped fix commit unless explicitly asked. After each fix or decision, resolve the review conversation if supported. Do not use external APIs, paid services, credentials, dependency changes, or broad refactors unless explicitly authorized. Add targeted tests where behavior changes and run the narrowest relevant validation.", ].join("\n\n"); try { diff --git a/scripts/check-codex-autofix-workflow.mjs b/scripts/check-codex-autofix-workflow.mjs index 75a6a6e34..7600a24f8 100644 --- a/scripts/check-codex-autofix-workflow.mjs +++ b/scripts/check-codex-autofix-workflow.mjs @@ -4,6 +4,10 @@ const workflowPath = ".github/workflows/codex-autofix-review-comments.yml"; const workflow = fs.readFileSync(workflowPath, "utf8"); const failures = []; +const scopedResolveCommand = + "@codex resolve actionable Codex review findings for this pull request and current head"; +const scopedResolvePrompt = + `${scopedResolveCommand} using the repository instructions. Always fix P0 and P1 findings. For P2 and lower findings, decide whether each is worth fixing automatically. Fix clear, scoped, low-risk issues with the best minimal change; otherwise reply explaining why the issue is deferred or not actionable. Do not update the branch from main, address unrelated reviews, broaden scope, or create more than one scoped fix commit unless explicitly asked. After each fix or decision, resolve the review conversation if supported. Do not use external APIs, paid services, credentials, dependency changes, or broad refactors unless explicitly authorized. Add targeted tests where behavior changes and run the narrowest relevant validation.`; const forbiddenPatterns = [ { @@ -22,6 +26,10 @@ const forbiddenPatterns = [ pattern: /^\s*pull_request_review:/m, message: "Do not trigger Codex auto-resolve from whole pull_request_review events.", }, + { + pattern: /@codex resolve all review comments/, + message: "Do not use the broad Codex resolve-all command; use the scoped actionable-findings command.", + }, ]; for (const { pattern, message } of forbiddenPatterns) { @@ -38,8 +46,12 @@ if (!workflow.includes("codex-autoresolve-pr:${pr.number}")) { failures.push("Codex auto-resolve marker must be scoped to the pull request, not the head SHA."); } -if (!workflow.includes('sourceBody.includes("@codex resolve all review comments")')) { - failures.push("Codex auto-resolve workflow must skip comments that already look like resolve requests."); +if (!workflow.includes(`sourceBody.includes("${scopedResolveCommand}")`)) { + failures.push("Codex auto-resolve workflow must skip comments that already look like scoped resolve requests."); +} + +if (!workflow.includes(`"${scopedResolvePrompt}"`)) { + failures.push("Codex auto-resolve workflow must emit the scoped actionable-findings resolve prompt."); } if (failures.length > 0) { From aa4506419e1a6876d0e39baa97a1af8dcf119d0b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:18:56 +0000 Subject: [PATCH 2/5] fix: run prettier on check-codex-autofix-workflow.mjs to fix format check CI failure --- scripts/check-codex-autofix-workflow.mjs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/check-codex-autofix-workflow.mjs b/scripts/check-codex-autofix-workflow.mjs index 7600a24f8..d7e5ffa20 100644 --- a/scripts/check-codex-autofix-workflow.mjs +++ b/scripts/check-codex-autofix-workflow.mjs @@ -4,10 +4,8 @@ const workflowPath = ".github/workflows/codex-autofix-review-comments.yml"; const workflow = fs.readFileSync(workflowPath, "utf8"); const failures = []; -const scopedResolveCommand = - "@codex resolve actionable Codex review findings for this pull request and current head"; -const scopedResolvePrompt = - `${scopedResolveCommand} using the repository instructions. Always fix P0 and P1 findings. For P2 and lower findings, decide whether each is worth fixing automatically. Fix clear, scoped, low-risk issues with the best minimal change; otherwise reply explaining why the issue is deferred or not actionable. Do not update the branch from main, address unrelated reviews, broaden scope, or create more than one scoped fix commit unless explicitly asked. After each fix or decision, resolve the review conversation if supported. Do not use external APIs, paid services, credentials, dependency changes, or broad refactors unless explicitly authorized. Add targeted tests where behavior changes and run the narrowest relevant validation.`; +const scopedResolveCommand = "@codex resolve actionable Codex review findings for this pull request and current head"; +const scopedResolvePrompt = `${scopedResolveCommand} using the repository instructions. Always fix P0 and P1 findings. For P2 and lower findings, decide whether each is worth fixing automatically. Fix clear, scoped, low-risk issues with the best minimal change; otherwise reply explaining why the issue is deferred or not actionable. Do not update the branch from main, address unrelated reviews, broaden scope, or create more than one scoped fix commit unless explicitly asked. After each fix or decision, resolve the review conversation if supported. Do not use external APIs, paid services, credentials, dependency changes, or broad refactors unless explicitly authorized. Add targeted tests where behavior changes and run the narrowest relevant validation.`; const forbiddenPatterns = [ { From f7c9656f9943f568b3a45d8a5f7136ae7d24598c Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 9 Jul 2026 21:23:33 +0800 Subject: [PATCH 3/5] ci: pin hosted runner configuration --- .github/workflows/ci.yml | 19 +++--- .../codex-autofix-review-comments.yml | 2 +- .github/workflows/docker-image.yml | 8 +-- .github/workflows/eval-canary.yml | 4 +- .github/workflows/live-drift.yml | 4 +- .github/workflows/sast.yml | 4 +- .github/workflows/secret-scan.yml | 4 +- .github/workflows/summary.yml | 4 +- package.json | 3 +- scripts/check-github-action-pins.mjs | 64 +++++++++++++++++++ 10 files changed, 92 insertions(+), 24 deletions(-) create mode 100644 scripts/check-github-action-pins.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b087b1203..330a76bc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,12 +26,12 @@ jobs: # Intended as a required status check on main together with ui-smoke, # Gitleaks, and one migration replay gate. verify: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 25 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: persist-credentials: false @@ -42,6 +42,9 @@ jobs: cache: npm cache-dependency-path: package-lock.json + - name: Workflow action pin check + run: npm run check:github-actions + - name: Setup Deno uses: denoland/setup-deno@v2 with: @@ -99,12 +102,12 @@ jobs: # rerun does not repeat them. The runner boots its own dev server, so no # build step is needed here. ui-smoke: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 25 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: persist-credentials: false @@ -146,12 +149,12 @@ jobs: # required and reliable, keep this advisory or path-filtered rather than # requiring both replay gates on every PR. db-reset-verify: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 15 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: persist-credentials: false @@ -193,12 +196,12 @@ jobs: release-browser-matrix: if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') needs: [verify, ui-smoke, db-reset-verify] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 70 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/codex-autofix-review-comments.yml b/.github/workflows/codex-autofix-review-comments.yml index 5b76e0fa1..8514c28e8 100644 --- a/.github/workflows/codex-autofix-review-comments.yml +++ b/.github/workflows/codex-autofix-review-comments.yml @@ -16,7 +16,7 @@ concurrency: jobs: request-codex-autoresolve: name: Request Codex auto-resolve - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > github.event.pull_request.state == 'open' && contains(github.event.comment.user.login, 'chatgpt-codex-connector') diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 296b83f7f..475e22254 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -47,11 +47,11 @@ permissions: jobs: app-image: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: persist-credentials: false @@ -72,11 +72,11 @@ jobs: NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=placeholder-ci-publishable-key worker-image: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/eval-canary.yml b/.github/workflows/eval-canary.yml index 0c2afd5e0..dcf871e53 100644 --- a/.github/workflows/eval-canary.yml +++ b/.github/workflows/eval-canary.yml @@ -55,12 +55,12 @@ env: jobs: eval-canary: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/live-drift.yml b/.github/workflows/live-drift.yml index 2345b14be..c8b277bdb 100644 --- a/.github/workflows/live-drift.yml +++ b/.github/workflows/live-drift.yml @@ -24,12 +24,12 @@ env: jobs: live-drift: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/sast.yml b/.github/workflows/sast.yml index a5a2bb4a3..183f81e2f 100644 --- a/.github/workflows/sast.yml +++ b/.github/workflows/sast.yml @@ -24,13 +24,13 @@ jobs: # the merge. Runs OSS-engine only — no Semgrep account or token required. semgrep: name: Semgrep - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 15 container: image: semgrep/semgrep:1.168.0 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml index cad2d8263..14f4d18af 100644 --- a/.github/workflows/secret-scan.yml +++ b/.github/workflows/secret-scan.yml @@ -20,11 +20,11 @@ permissions: jobs: gitleaks: name: Gitleaks - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/summary.yml b/.github/workflows/summary.yml index 7caee0390..ca3e00810 100644 --- a/.github/workflows/summary.yml +++ b/.github/workflows/summary.yml @@ -10,7 +10,7 @@ concurrency: jobs: summary: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 10 permissions: issues: write @@ -49,7 +49,7 @@ jobs: - name: Comment with AI summary if: github.event.issue.state == 'open' - uses: peter-evans/create-or-update-comment@v6 + uses: peter-evans/create-or-update-comment@v5 with: token: ${{ secrets.GITHUB_TOKEN }} issue-number: ${{ github.event.issue.number }} diff --git a/package.json b/package.json index fce7d434a..58afcbc87 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,11 @@ "test:e2e:accessibility": "node scripts/run-playwright.mjs tests/ui-accessibility.spec.ts --project=chromium", "test:e2e:chromium": "node scripts/run-playwright.mjs --project=chromium", "test:e2e:visual": "node scripts/run-playwright.mjs --config=playwright.visual.config.ts", - "verify:cheap": "npm run check:runtime && npm run sitemap:check && npm run lint && npm run typecheck && npm run test", + "verify:cheap": "npm run check:runtime && npm run check:github-actions && npm run sitemap:check && npm run lint && npm run typecheck && npm run test", "verify:ui": "npm run check:runtime && npm run test:e2e:chromium", "verify:release": "npm run check:runtime && npm run lint && npm run typecheck && npm run test && npm run build && npm run test:e2e && npm run check:production-readiness && npm run governance:release && npm run eval:quality:release", "ci:env-check": "node scripts/check-ci-env.mjs", + "check:github-actions": "node scripts/check-github-action-pins.mjs", "sitemap:update": "tsx scripts/generate-site-map.ts", "sitemap:check": "tsx scripts/generate-site-map.ts --check", "check:runtime": "tsx scripts/check-runtime.ts", diff --git a/scripts/check-github-action-pins.mjs b/scripts/check-github-action-pins.mjs new file mode 100644 index 000000000..baf65afda --- /dev/null +++ b/scripts/check-github-action-pins.mjs @@ -0,0 +1,64 @@ +import { readdirSync, readFileSync } from "node:fs"; +import path from "node:path"; + +const workflowDir = path.join(process.cwd(), ".github", "workflows"); + +const supportedMajorRanges = new Map([ + [ + "actions/checkout", + { + min: 4, + max: 6, + reason: "checkout v7 is not a published major; use the current documented v6 pin.", + }, + ], + [ + "peter-evans/create-or-update-comment", + { + min: 5, + max: 5, + reason: "create-or-update-comment v6 is not a documented published major for this workflow.", + }, + ], +]); + +const usesPattern = /^\s*uses:\s*([^@\s]+)@v(\d+)\s*(?:#.*)?$/; +const runsOnLatestPattern = /^\s*runs-on:\s*ubuntu-latest\s*(?:#.*)?$/; +const failures = []; + +for (const fileName of readdirSync(workflowDir) + .filter((name) => /\.ya?ml$/i.test(name)) + .sort()) { + const filePath = path.join(workflowDir, fileName); + const lines = readFileSync(filePath, "utf8").split(/\r?\n/); + + lines.forEach((line, index) => { + if (runsOnLatestPattern.test(line)) { + failures.push( + `${fileName}:${index + 1}: runs-on uses ubuntu-latest. Pin GitHub-hosted Linux jobs to ubuntu-24.04 so CI is not tied to the moving ubuntu-latest alias.`, + ); + } + + const match = line.match(usesPattern); + if (!match) return; + + const [, action, rawMajor] = match; + const range = supportedMajorRanges.get(action); + if (!range) return; + + const major = Number(rawMajor); + if (!Number.isInteger(major) || major < range.min || major > range.max) { + failures.push( + `${fileName}:${index + 1}: ${action}@v${rawMajor} is outside supported range v${range.min}-v${range.max}. ${range.reason}`, + ); + } + }); +} + +if (failures.length > 0) { + console.error("GitHub Actions pin check failed:"); + for (const failure of failures) console.error(`- ${failure}`); + process.exit(1); +} + +console.log("GitHub Actions pin check passed."); From ce6dc4c0c6c2673cb34332158c3a8ab45383149f Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:46:56 +0000 Subject: [PATCH 4/5] fix: apply CodeRabbit auto-fixes Fixed 1 file(s) based on 1 unresolved review comment. Co-authored-by: CodeRabbit --- scripts/check-github-action-pins.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check-github-action-pins.mjs b/scripts/check-github-action-pins.mjs index baf65afda..2f1aa8053 100644 --- a/scripts/check-github-action-pins.mjs +++ b/scripts/check-github-action-pins.mjs @@ -9,7 +9,7 @@ const supportedMajorRanges = new Map([ { min: 4, max: 6, - reason: "checkout v7 is not a published major; use the current documented v6 pin.", + reason: "v6 is the currently supported and documented major that this repo has vetted; upgrading to v7 should be a deliberate, reviewed decision.", }, ], [ From b63fd1ac5f866d4a4d338c0b8d62c664df887d6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:58:24 +0000 Subject: [PATCH 5/5] fix: format scripts/check-github-action-pins.mjs to pass Prettier check --- scripts/check-github-action-pins.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/check-github-action-pins.mjs b/scripts/check-github-action-pins.mjs index 2f1aa8053..04be42f3f 100644 --- a/scripts/check-github-action-pins.mjs +++ b/scripts/check-github-action-pins.mjs @@ -9,7 +9,8 @@ const supportedMajorRanges = new Map([ { min: 4, max: 6, - reason: "v6 is the currently supported and documented major that this repo has vetted; upgrading to v7 should be a deliberate, reviewed decision.", + reason: + "v6 is the currently supported and documented major that this repo has vetted; upgrading to v7 should be a deliberate, reviewed decision.", }, ], [