From fa32c8f849e08d3e71bcd8410da67a921bd8428a Mon Sep 17 00:00:00 2001 From: Shekhar Mudarapu Date: Fri, 31 Jul 2026 22:38:31 -0400 Subject: [PATCH 1/3] ci: make the cross-audits re-runnable, and stop them looking un-run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two problems, both hit during today's PRs. RE-AUDITING WAS UNREACHABLE. The audits fire on `opened`/`reopened`/`labeled` and deliberately not on `synchronize`, so a fix pushed in response to a finding was never re-audited — the posted verdict kept describing the first commit. The documented escape hatch, "add the `agent:audit` label", does not work either once the PR already carries it: re-adding a label a PR already has fires no `labeled` event at all, so it silently does nothing. On #207 the only way to get a verdict on the fixed code was to REMOVE the label and add it back. `synchronize` is now a trigger, but only when the PR already carries `agent:audit`. A plain PR still gets exactly one audit on open, so the fix loop's pushes don't re-spend credits; labelling one says "keep auditing this as I fix it" and every later push re-audits until the label comes off. That makes the label meaningful rather than a one-shot, and makes re-auditing reachable without the remove/re-add dance. THE STATUS READ AS "NEVER RAN". The Classifier's `domain:*` labels land seconds after open, and each one triggers this workflow, which correctly skips. That skipped run is NEWER than the real audit, so `gh pr checks` reports "skipping" while the audit had in fact succeeded moments earlier. I read that as "the auditors don't run for this change class" and said so — wrong, and the sort of wrong that gets a review gate quietly written off. GitHub cannot filter label names in `on:`, so the skipped run is unavoidable; both headers now explain the display, and the job comment says which labels are allowed to spend credits. sdlc-design-review is untouched: its `types: [labeled]` + `domain:ui` gate is the intended routing pattern, not this bug. actionlint clean. Behaviour verified on the PR carrying this change. --- .github/workflows/sdlc-audit-gemini.yml | 29 +++++++++++++++++++++---- .github/workflows/sdlc-audit.yml | 29 +++++++++++++++++++++---- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/.github/workflows/sdlc-audit-gemini.yml b/.github/workflows/sdlc-audit-gemini.yml index 98df16ed..85a63876 100644 --- a/.github/workflows/sdlc-audit-gemini.yml +++ b/.github/workflows/sdlc-audit-gemini.yml @@ -2,12 +2,22 @@ # independent third opinion alongside the Claude reviewer and Codex auditor) and on # demand via the `agent:audit` label. Read-only; posts Gemini's verdict as a PR comment. # Needs secret GEMINI_API_KEY — skips cleanly (no failure) when it is absent. -# Not on `synchronize`, so the fix loop's pushes don't re-spend Gemini credits each round. +# Re-auditing, and why it is opt-in: a plain push does NOT re-audit, so the fix +# loop's pushes don't re-spend Gemini credits each round. Label the PR `agent:audit` +# and every subsequent push re-audits until the label comes off — which is the ONLY +# way to re-audit, because re-adding a label the PR already has fires no `labeled` +# event at all, so "add the label again" silently does nothing. +# +# Reading the check status: the Classifier's `domain:*` labels land seconds after +# open and each one triggers this workflow, which then skips. That SKIPPED run is +# newer than the real audit, so `gh pr checks` shows "skipping" for it while the +# audit itself succeeded moments earlier. Check the run list, not the summary, +# before concluding an audit never ran. name: "sdlc · audit (Gemini)" on: pull_request: - types: [opened, reopened, labeled] + types: [opened, reopened, labeled, synchronize] permissions: contents: read @@ -15,9 +25,20 @@ permissions: jobs: audit-gemini: - # Auto on open/reopen, or on the agent:audit label. (label.name is null on open events.) + # Auto on open/reopen. On a label, ONLY `agent:audit` (any other label — the + # Classifier's `domain:*` land seconds after open — must not spend credits). + # On a push, only when the PR ALREADY carries `agent:audit`: that is the + # opt-in for "keep auditing this one as I fix it", and it is what makes a + # re-audit reachable at all (see the header). # dependabot-actor runs get Dependabot's (empty) secret store — agent can't auth, so skip - if: github.actor != 'dependabot[bot]' && (github.event.action != 'labeled' || github.event.label.name == 'agent:audit') + if: >- + github.actor != 'dependabot[bot]' && ( + github.event.action == 'opened' || + github.event.action == 'reopened' || + (github.event.action == 'labeled' && github.event.label.name == 'agent:audit') || + (github.event.action == 'synchronize' && + contains(github.event.pull_request.labels.*.name, 'agent:audit')) + ) runs-on: ubuntu-latest steps: # Secrets aren't readable in a job-level `if`, so gate every later step on this diff --git a/.github/workflows/sdlc-audit.yml b/.github/workflows/sdlc-audit.yml index 51d5fbdc..650fde07 100644 --- a/.github/workflows/sdlc-audit.yml +++ b/.github/workflows/sdlc-audit.yml @@ -1,12 +1,22 @@ # Auditor (Codex) — independent cross-audit. Runs automatically when a PR opens (an # independent second opinion to the Claude reviewer) and on demand via the `agent:audit` # label. Read-only; posts Codex's verdict as a PR comment. Needs secret OPENAI_API_KEY. -# Not on `synchronize`, so the fix loop's pushes don't re-spend Codex credits each round. +# Re-auditing, and why it is opt-in: a plain push does NOT re-audit, so the fix +# loop's pushes don't re-spend Codex credits each round. Label the PR `agent:audit` +# and every subsequent push re-audits until the label comes off — which is the ONLY +# way to re-audit, because re-adding a label the PR already has fires no `labeled` +# event at all, so "add the label again" silently does nothing. +# +# Reading the check status: the Classifier's `domain:*` labels land seconds after +# open and each one triggers this workflow, which then skips. That SKIPPED run is +# newer than the real audit, so `gh pr checks` shows "skipping" for it while the +# audit itself succeeded moments earlier. Check the run list, not the summary, +# before concluding an audit never ran. name: "sdlc · audit (Codex)" on: pull_request: - types: [opened, reopened, labeled] + types: [opened, reopened, labeled, synchronize] permissions: contents: read @@ -14,9 +24,20 @@ permissions: jobs: audit: - # Auto on open/reopen, or on the agent:audit label. (label.name is null on open events.) + # Auto on open/reopen. On a label, ONLY `agent:audit` (any other label — the + # Classifier's `domain:*` land seconds after open — must not spend credits). + # On a push, only when the PR ALREADY carries `agent:audit`: that is the + # opt-in for "keep auditing this one as I fix it", and it is what makes a + # re-audit reachable at all (see the header). # dependabot-actor runs get Dependabot's (empty) secret store — agent can't auth, so skip - if: github.actor != 'dependabot[bot]' && (github.event.action != 'labeled' || github.event.label.name == 'agent:audit') + if: >- + github.actor != 'dependabot[bot]' && ( + github.event.action == 'opened' || + github.event.action == 'reopened' || + (github.event.action == 'labeled' && github.event.label.name == 'agent:audit') || + (github.event.action == 'synchronize' && + contains(github.event.pull_request.labels.*.name, 'agent:audit')) + ) runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 From 0feeef636d5d3ec546d58311c3575fabe5b801b5 Mon Sep 17 00:00:00 2001 From: Shekhar Mudarapu Date: Fri, 31 Jul 2026 22:40:46 -0400 Subject: [PATCH 2/3] docs(ci): note in the header that the label also re-audits later pushes --- .github/workflows/sdlc-audit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sdlc-audit.yml b/.github/workflows/sdlc-audit.yml index 650fde07..8c810ff2 100644 --- a/.github/workflows/sdlc-audit.yml +++ b/.github/workflows/sdlc-audit.yml @@ -1,6 +1,6 @@ # Auditor (Codex) — independent cross-audit. Runs automatically when a PR opens (an # independent second opinion to the Claude reviewer) and on demand via the `agent:audit` -# label. Read-only; posts Codex's verdict as a PR comment. Needs secret OPENAI_API_KEY. +# label, which also keeps auditing every later push (see below). Read-only; posts Codex's verdict as a PR comment. Needs secret OPENAI_API_KEY. # Re-auditing, and why it is opt-in: a plain push does NOT re-audit, so the fix # loop's pushes don't re-spend Codex credits each round. Label the PR `agent:audit` # and every subsequent push re-audits until the label comes off — which is the ONLY From 1d3156822e84a6067d135868af8f6db88e3b0b14 Mon Sep 17 00:00:00 2001 From: Shekhar Mudarapu Date: Fri, 31 Jul 2026 23:13:11 -0400 Subject: [PATCH 3/3] docs(ci): match the Gemini header to the Codex one on re-audit behaviour --- .github/workflows/sdlc-audit-gemini.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sdlc-audit-gemini.yml b/.github/workflows/sdlc-audit-gemini.yml index 85a63876..8c51df82 100644 --- a/.github/workflows/sdlc-audit-gemini.yml +++ b/.github/workflows/sdlc-audit-gemini.yml @@ -1,6 +1,7 @@ # Auditor (Gemini) — third cross-model audit. Runs automatically when a PR opens (an # independent third opinion alongside the Claude reviewer and Codex auditor) and on -# demand via the `agent:audit` label. Read-only; posts Gemini's verdict as a PR comment. +# demand via the `agent:audit` label, which also keeps auditing every later push (see +# below). Read-only; posts Gemini's verdict as a PR comment. # Needs secret GEMINI_API_KEY — skips cleanly (no failure) when it is absent. # Re-auditing, and why it is opt-in: a plain push does NOT re-audit, so the fix # loop's pushes don't re-spend Gemini credits each round. Label the PR `agent:audit`