From 1d9c70bc9b6817ab1003c336b83b4044d93d0e93 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 10:09:57 +0900 Subject: [PATCH 01/15] fix(codeql): keep a clean dispatch scan when status publish 403s opencode-agent is installed with statuses:read, so POST /statuses to a target repo returns HTTP 403 after the SARIF gate already passed. Treat the completed dispatch scan job as terminal evidence and let the required shard consume that public run on rerun instead of fail-closing a clean scan. --- .github/workflows/codeql-pr.yml | 30 +++++++++ .github/workflows/codeql-scan-dispatch.yml | 5 ++ tests/test_codeql_pr_workflow_contract.py | 66 ++++++++++++++++++- ..._codeql_scan_dispatch_workflow_contract.py | 18 +++++ 4 files changed, 117 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-pr.yml b/.github/workflows/codeql-pr.yml index db06b7c4ba..9c61471ef4 100644 --- a/.github/workflows/codeql-pr.yml +++ b/.github/workflows/codeql-pr.yml @@ -208,6 +208,36 @@ jobs: exit 0 ;; esac + + expected_title="CodeQL Scan Dispatch ${TARGET_REPOSITORY}#${PR_NUMBER}@${PR_HEAD_SHA}" + expected_job="CodeQL dispatch scan (${LANGUAGE})" + runs_json="$(gh api "repos/ContextualWisdomLab/.github/actions/workflows/codeql-scan-dispatch.yml/runs?per_page=30")" + run_id="$(printf '%s' "$runs_json" | jq -r --arg title "$expected_title" --arg path ".github/workflows/codeql-scan-dispatch.yml" ' + [ + .workflow_runs[] + | select(.path == $path) + | select(.event == "repository_dispatch") + | select(.status == "completed") + | select(.display_title == $title or .name == $title) + ] + | first + | .id // empty + ')" + if [[ "$run_id" =~ ^[1-9][0-9]*$ ]]; then + jobs_json="$(gh api "repos/ContextualWisdomLab/.github/actions/runs/${run_id}/jobs?per_page=20")" + job_conclusion="$(printf '%s' "$jobs_json" | jq -r --arg name "$expected_job" ' + [.jobs[] | select(.name == $name)] + | if length == 1 then .[0].conclusion else empty end + ')" + case "$job_conclusion" in + success|failure) + echo "verdict=${job_conclusion}" >>"$GITHUB_OUTPUT" + echo "Found completed CodeQL dispatch scan job for ${LANGUAGE}: ${job_conclusion}." + exit 0 + ;; + esac + fi + if [ "$RUN_ATTEMPT" != "1" ]; then echo "::error::Exact CodeQL job was rerun without an authenticated terminal verdict." exit 1 diff --git a/.github/workflows/codeql-scan-dispatch.yml b/.github/workflows/codeql-scan-dispatch.yml index 521ceeb167..d31c9c6c58 100644 --- a/.github/workflows/codeql-scan-dispatch.yml +++ b/.github/workflows/codeql-scan-dispatch.yml @@ -503,6 +503,11 @@ jobs: exit 0 fi + if [ "$GATE_OUTCOME" = "success" ]; then + echo "::notice::Could not publish the CodeQL dispatch status after all configured credentials failed. The completed dispatch scan job remains the evidence for this head." + exit 0 + fi + echo "::error::Could not publish the CodeQL dispatch status after all configured credentials failed; the exact required job will remain failed and will not be woken with stale or missing evidence." exit 1 diff --git a/tests/test_codeql_pr_workflow_contract.py b/tests/test_codeql_pr_workflow_contract.py index 2d11ca0141..6dd49eaf70 100644 --- a/tests/test_codeql_pr_workflow_contract.py +++ b/tests/test_codeql_pr_workflow_contract.py @@ -133,7 +133,12 @@ def test_codeql_pr_dispatch_and_release_run_blocks_are_valid_bash() -> None: def _run_verdict_read( - tmp_path: Path, statuses: list[dict] + tmp_path: Path, + statuses: list[dict], + *, + dispatch_runs: dict | None = None, + dispatch_jobs: dict | None = None, + run_attempt: str = "2", ) -> tuple[subprocess.CompletedProcess[str], subprocess.CompletedProcess[str]]: """Execute the real one-shot status read and verdict enforcement blocks.""" bash = shutil.which("bash") @@ -157,6 +162,8 @@ def _run_verdict_read( 'case "$2" in\n' " */pulls/*) printf '%s\\n' \"$FAKE_PULL_JSON\" ;;\n" " */statuses) printf '%s\\n' \"$FAKE_STATUSES_JSON\" ;;\n" + " */codeql-scan-dispatch.yml/runs*) printf '%s\\n' \"$FAKE_DISPATCH_RUNS_JSON\" ;;\n" + " */actions/runs/*/jobs*) printf '%s\\n' \"$FAKE_DISPATCH_JOBS_JSON\" ;;\n" " *) exit 1 ;;\n" "esac\n", encoding="utf-8", @@ -169,6 +176,12 @@ def _run_verdict_read( "PATH": f"{fake_bin}:{os.environ['PATH']}", "FAKE_PULL_JSON": json.dumps(live_pr), "FAKE_STATUSES_JSON": json.dumps(statuses), + "FAKE_DISPATCH_RUNS_JSON": json.dumps( + dispatch_runs if dispatch_runs is not None else {"workflow_runs": []} + ), + "FAKE_DISPATCH_JOBS_JSON": json.dumps( + dispatch_jobs if dispatch_jobs is not None else {"jobs": []} + ), "GH_TOKEN": "fake-token", "TARGET_REPOSITORY": "ContextualWisdomLab/naruon", "PR_NUMBER": "42", @@ -178,7 +191,7 @@ def _run_verdict_read( "BASE_REF": "main", "BASE_SHA": "a" * 40, "HEAD_REF": "feature", - "RUN_ATTEMPT": "2", + "RUN_ATTEMPT": run_attempt, "REQUIRED_RUN_ID": "42", "REQUIRED_JOB_ID": "43", "GITHUB_OUTPUT": str(output), @@ -248,6 +261,51 @@ def test_codeql_pr_one_shot_read_accepts_the_opencode_agent_creator(tmp_path: Pa assert "Current-head CodeQL dispatch verdict for python: success." in verdict_result.stdout +def test_codeql_pr_one_shot_read_accepts_completed_dispatch_scan_job_when_status_unpublishable( + tmp_path: Path, +) -> None: + """A completed dispatch scan job is terminal evidence when statuses:write 403s. + + Live 2026-09-08 naruon#1596 dispatch run 34173910106 scanned clean, then + POST /statuses returned HTTP 403 for opencode-agent (statuses:read only) + and github.token (cross-repo). The required shard must consume that + completed scan job instead of staying fail-closed on a missing status. + """ + head_sha = "b" * 40 + dispatch_result, verdict_result = _run_verdict_read( + tmp_path, + statuses=[], + dispatch_runs={ + "workflow_runs": [ + { + "id": 34173910106, + "event": "repository_dispatch", + "path": ".github/workflows/codeql-scan-dispatch.yml", + "status": "completed", + "display_title": ( + "CodeQL Scan Dispatch ContextualWisdomLab/naruon#42@" + head_sha + ), + "name": ( + "CodeQL Scan Dispatch ContextualWisdomLab/naruon#42@" + head_sha + ), + } + ] + }, + dispatch_jobs={ + "jobs": [ + { + "name": "CodeQL dispatch scan (python)", + "conclusion": "success", + } + ] + }, + ) + assert dispatch_result.returncode == 0, dispatch_result.stderr + dispatch_result.stdout + assert verdict_result.returncode == 0, verdict_result.stderr + verdict_result.stdout + assert "completed CodeQL dispatch scan job for python: success" in dispatch_result.stdout + assert "Current-head CodeQL dispatch verdict for python: success." in verdict_result.stdout + + def test_codeql_action_steps_use_one_version_per_workflow() -> None: """Prevent CodeQL init/analyze version splits from failing the scheduled scan.""" workflow = (REPO_ROOT / ".github/workflows/scheduled-security-scan.yml").read_text( @@ -322,6 +380,8 @@ def test_codeql_pr_attempt_one_without_verdict_fails_pending_without_dispatch( 'case "$2" in\n' " */pulls/*) printf '%s\\n' \"$FAKE_PULL_JSON\" ;;\n" " */statuses) printf '%s\\n' \"$FAKE_STATUSES_JSON\" ;;\n" + " */codeql-scan-dispatch.yml/runs*) printf '%s\\n' \"$FAKE_DISPATCH_RUNS_JSON\" ;;\n" + " */actions/runs/*/jobs*) printf '%s\\n' \"$FAKE_DISPATCH_JOBS_JSON\" ;;\n" " *) exit 1 ;;\n" "esac\n", encoding="utf-8", @@ -333,6 +393,8 @@ def test_codeql_pr_attempt_one_without_verdict_fails_pending_without_dispatch( "PATH": f"{fake_bin}:{os.environ['PATH']}", "FAKE_PULL_JSON": json.dumps({"head": {"sha": head_sha}, "state": "open"}), "FAKE_STATUSES_JSON": json.dumps([]), + "FAKE_DISPATCH_RUNS_JSON": json.dumps({"workflow_runs": []}), + "FAKE_DISPATCH_JOBS_JSON": json.dumps({"jobs": []}), "FAKE_POST_LOG": str(post_log), "GH_TOKEN": "fake-token", "TARGET_REPOSITORY": "ContextualWisdomLab/naruon", diff --git a/tests/test_codeql_scan_dispatch_workflow_contract.py b/tests/test_codeql_scan_dispatch_workflow_contract.py index dea1326494..9562387473 100644 --- a/tests/test_codeql_scan_dispatch_workflow_contract.py +++ b/tests/test_codeql_scan_dispatch_workflow_contract.py @@ -505,6 +505,24 @@ def test_codeql_scan_dispatch_is_not_in_the_required_workflow_ruleset_scope(): assert ".github/workflows/codeql-scan-dispatch.yml" not in required_paths +def test_dispatch_publish_keeps_successful_scan_when_status_write_is_denied() -> None: + """A clean SARIF gate must not fail the handler solely because POST /statuses 403s. + + opencode-agent is installed with statuses:read. Cross-repo github.token cannot + write naruon commit statuses. The completed scan job is the remaining evidence. + """ + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + publish = workflow.split(" - name: Publish CodeQL dispatch status\n", 1)[1].split( + "\n - name: Wake exact CodeQL required job\n", 1 + )[0] + + assert "GATE_OUTCOME" in publish + assert 'if [ "$GATE_OUTCOME" = "success" ]; then' in publish + assert "completed dispatch scan job remains the evidence" in publish + assert "continue-on-error:" not in publish + assert "cancel-in-progress: true" not in publish + + def test_dispatch_wakes_only_the_exact_failed_codeql_job() -> None: workflow = WORKFLOW_PATH.read_text(encoding="utf-8") wake = workflow.split(" - name: Wake exact CodeQL required job\n", 1)[1].split( From 6c4ed678a2fbbd9d624b320c73b81e06ba1599fe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 11:04:34 +0900 Subject: [PATCH 02/15] fix(codeql): dispatch remaining languages on workflow reruns Attempt 2 of .github#2028 skipped Dispatch current-head because the coordinator required github.run_attempt == 1, so no codeql-scan was posted. Later attempts still skip when every language already has a terminal opencode-agent verdict. --- .github/workflows/codeql-pr.yml | 1 - tests/test_codeql_pr_workflow_contract.py | 22 +++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-pr.yml b/.github/workflows/codeql-pr.yml index 9c61471ef4..74cc8be0c7 100644 --- a/.github/workflows/codeql-pr.yml +++ b/.github/workflows/codeql-pr.yml @@ -281,7 +281,6 @@ jobs: always() && github.event.action != 'closed' && github.event.pull_request.state != 'closed' - && github.run_attempt == 1 && needs.detect-languages.result == 'success' && needs.detect-languages.outputs.code == 'true' runs-on: ubuntu-24.04 diff --git a/tests/test_codeql_pr_workflow_contract.py b/tests/test_codeql_pr_workflow_contract.py index 6dd49eaf70..af5072f18e 100644 --- a/tests/test_codeql_pr_workflow_contract.py +++ b/tests/test_codeql_pr_workflow_contract.py @@ -87,8 +87,28 @@ def test_codeql_pr_shards_do_not_dispatch_and_coordinator_sends_the_full_matrix_ assert "needs: [detect-languages, analyze-head]" in coordinator assert "always()" in coordinator.split("\n runs-on:", 1)[0] assert "github.event.action != 'closed'" in coordinator.split("\n runs-on:", 1)[0] - assert "github.run_attempt == 1" in coordinator.split("\n runs-on:", 1)[0] + coordinator_if = coordinator.split("\n runs-on:", 1)[0] + assert "github.run_attempt == 1" not in coordinator_if assert coordinator.count("repos/ContextualWisdomLab/.github/dispatches") == 1 + + +def test_codeql_coordinator_dispatches_later_attempts_when_no_terminal_verdict() -> None: + """A rerun must still POST codeql-scan if attempt 1 never dispatched. + + Live ContextualWisdomLab/.github#2028 run 34175742278 was attempt 2. + ``github.run_attempt == 1`` skipped Dispatch current-head, so no + codeql-scan-dispatch.yml run existed and compatibility stayed pending. + The coordinator script already skips when every language has a terminal + opencode-agent verdict, so later attempts are safe. + """ + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + coordinator_if = workflow.split(" dispatch-current-head:\n", 1)[1].split( + "\n runs-on:", 1 + )[0] + coordinator = workflow.split(" dispatch-current-head:\n", 1)[1] + + assert "github.run_attempt == 1" not in coordinator_if + assert "All detected CodeQL languages already have authenticated terminal verdicts" in coordinator assert 'event_type:"codeql-scan"' in coordinator assert "required_jobs:$required_jobs" in coordinator assert "required_run_id:$required_run_id" in coordinator From c92e3367dd259b4d0a8a7256380414f60a0475d9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 11:09:32 +0900 Subject: [PATCH 03/15] test(codeql): require paginated dispatch evidence lookup --- tests/test_codeql_pr_workflow_contract.py | 46 ++++++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/tests/test_codeql_pr_workflow_contract.py b/tests/test_codeql_pr_workflow_contract.py index af5072f18e..303c41d1d9 100644 --- a/tests/test_codeql_pr_workflow_contract.py +++ b/tests/test_codeql_pr_workflow_contract.py @@ -156,7 +156,7 @@ def _run_verdict_read( tmp_path: Path, statuses: list[dict], *, - dispatch_runs: dict | None = None, + dispatch_runs: dict | list[dict] | None = None, dispatch_jobs: dict | None = None, run_attempt: str = "2", ) -> tuple[subprocess.CompletedProcess[str], subprocess.CompletedProcess[str]]: @@ -197,7 +197,9 @@ def _run_verdict_read( "FAKE_PULL_JSON": json.dumps(live_pr), "FAKE_STATUSES_JSON": json.dumps(statuses), "FAKE_DISPATCH_RUNS_JSON": json.dumps( - dispatch_runs if dispatch_runs is not None else {"workflow_runs": []} + dispatch_runs + if isinstance(dispatch_runs, list) + else [dispatch_runs if dispatch_runs is not None else {"workflow_runs": []}] ), "FAKE_DISPATCH_JOBS_JSON": json.dumps( dispatch_jobs if dispatch_jobs is not None else {"jobs": []} @@ -326,6 +328,46 @@ def test_codeql_pr_one_shot_read_accepts_completed_dispatch_scan_job_when_status assert "Current-head CodeQL dispatch verdict for python: success." in verdict_result.stdout +def test_codeql_pr_finds_completed_dispatch_scan_beyond_first_results_page( + tmp_path: Path, +) -> None: + """The exact completed dispatch remains discoverable on later API pages.""" + head_sha = "b" * 40 + expected_title = "CodeQL Scan Dispatch ContextualWisdomLab/naruon#42@" + head_sha + dispatch_result, verdict_result = _run_verdict_read( + tmp_path, + statuses=[], + dispatch_runs=[ + {"workflow_runs": []}, + { + "workflow_runs": [ + { + "id": 34173910106, + "event": "repository_dispatch", + "path": ".github/workflows/codeql-scan-dispatch.yml", + "status": "completed", + "display_title": expected_title, + "name": expected_title, + } + ] + }, + ], + dispatch_jobs={ + "jobs": [ + { + "name": "CodeQL dispatch scan (python)", + "conclusion": "success", + } + ] + }, + ) + + assert dispatch_result.returncode == 0, dispatch_result.stderr + dispatch_result.stdout + assert verdict_result.returncode == 0, verdict_result.stderr + verdict_result.stdout + assert "completed CodeQL dispatch scan job for python: success" in dispatch_result.stdout + + + def test_codeql_action_steps_use_one_version_per_workflow() -> None: """Prevent CodeQL init/analyze version splits from failing the scheduled scan.""" workflow = (REPO_ROOT / ".github/workflows/scheduled-security-scan.yml").read_text( From 3cefd2c844e394ee2be2ddd3ea0cd3520a8032c3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 11:10:16 +0900 Subject: [PATCH 04/15] test(codeql): require paginated dispatch job lookup --- tests/test_codeql_pr_workflow_contract.py | 25 ++++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tests/test_codeql_pr_workflow_contract.py b/tests/test_codeql_pr_workflow_contract.py index 303c41d1d9..bcb57e78a9 100644 --- a/tests/test_codeql_pr_workflow_contract.py +++ b/tests/test_codeql_pr_workflow_contract.py @@ -157,7 +157,7 @@ def _run_verdict_read( statuses: list[dict], *, dispatch_runs: dict | list[dict] | None = None, - dispatch_jobs: dict | None = None, + dispatch_jobs: dict | list[dict] | None = None, run_attempt: str = "2", ) -> tuple[subprocess.CompletedProcess[str], subprocess.CompletedProcess[str]]: """Execute the real one-shot status read and verdict enforcement blocks.""" @@ -202,7 +202,9 @@ def _run_verdict_read( else [dispatch_runs if dispatch_runs is not None else {"workflow_runs": []}] ), "FAKE_DISPATCH_JOBS_JSON": json.dumps( - dispatch_jobs if dispatch_jobs is not None else {"jobs": []} + dispatch_jobs + if isinstance(dispatch_jobs, list) + else [dispatch_jobs if dispatch_jobs is not None else {"jobs": []}] ), "GH_TOKEN": "fake-token", "TARGET_REPOSITORY": "ContextualWisdomLab/naruon", @@ -352,14 +354,17 @@ def test_codeql_pr_finds_completed_dispatch_scan_beyond_first_results_page( ] }, ], - dispatch_jobs={ - "jobs": [ - { - "name": "CodeQL dispatch scan (python)", - "conclusion": "success", - } - ] - }, + dispatch_jobs=[ + {"jobs": []}, + { + "jobs": [ + { + "name": "CodeQL dispatch scan (python)", + "conclusion": "success", + } + ] + }, + ], ) assert dispatch_result.returncode == 0, dispatch_result.stderr + dispatch_result.stdout From d51da47d1c4ed0aefccff18bb52123315a3c2f39 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 11:10:43 +0900 Subject: [PATCH 05/15] fix(codeql): paginate exact dispatch evidence --- .github/workflows/codeql-pr.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-pr.yml b/.github/workflows/codeql-pr.yml index 74cc8be0c7..9e0e04a9d1 100644 --- a/.github/workflows/codeql-pr.yml +++ b/.github/workflows/codeql-pr.yml @@ -211,10 +211,10 @@ jobs: expected_title="CodeQL Scan Dispatch ${TARGET_REPOSITORY}#${PR_NUMBER}@${PR_HEAD_SHA}" expected_job="CodeQL dispatch scan (${LANGUAGE})" - runs_json="$(gh api "repos/ContextualWisdomLab/.github/actions/workflows/codeql-scan-dispatch.yml/runs?per_page=30")" + runs_json="$(gh api --paginate --slurp "repos/ContextualWisdomLab/.github/actions/workflows/codeql-scan-dispatch.yml/runs")" run_id="$(printf '%s' "$runs_json" | jq -r --arg title "$expected_title" --arg path ".github/workflows/codeql-scan-dispatch.yml" ' [ - .workflow_runs[] + .[] | .workflow_runs[] | select(.path == $path) | select(.event == "repository_dispatch") | select(.status == "completed") @@ -224,9 +224,9 @@ jobs: | .id // empty ')" if [[ "$run_id" =~ ^[1-9][0-9]*$ ]]; then - jobs_json="$(gh api "repos/ContextualWisdomLab/.github/actions/runs/${run_id}/jobs?per_page=20")" + jobs_json="$(gh api --paginate --slurp "repos/ContextualWisdomLab/.github/actions/runs/${run_id}/jobs")" job_conclusion="$(printf '%s' "$jobs_json" | jq -r --arg name "$expected_job" ' - [.jobs[] | select(.name == $name)] + [.[] | .jobs[] | select(.name == $name)] | if length == 1 then .[0].conclusion else empty end ')" case "$job_conclusion" in From 26e4e80409ea770d4d519028d2d26130e4b5aa9f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 11:14:52 +0900 Subject: [PATCH 06/15] test(codeql): parse paginated gh endpoint options --- tests/test_codeql_pr_workflow_contract.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_codeql_pr_workflow_contract.py b/tests/test_codeql_pr_workflow_contract.py index bcb57e78a9..b5858ac06a 100644 --- a/tests/test_codeql_pr_workflow_contract.py +++ b/tests/test_codeql_pr_workflow_contract.py @@ -179,7 +179,8 @@ def _run_verdict_read( "#!/usr/bin/env bash\n" "set -euo pipefail\n" 'test "$1" = api\n' - 'case "$2" in\n' + 'endpoint="${@: -1}"\n' + 'case "$endpoint" in\n' " */pulls/*) printf '%s\\n' \"$FAKE_PULL_JSON\" ;;\n" " */statuses) printf '%s\\n' \"$FAKE_STATUSES_JSON\" ;;\n" " */codeql-scan-dispatch.yml/runs*) printf '%s\\n' \"$FAKE_DISPATCH_RUNS_JSON\" ;;\n" @@ -444,7 +445,8 @@ def test_codeql_pr_attempt_one_without_verdict_fails_pending_without_dispatch( ' printf \'%s\\n\' "$4" >>"$FAKE_POST_LOG"\n' " exit 0\n" "fi\n" - 'case "$2" in\n' + 'endpoint="${@: -1}"\n' + 'case "$endpoint" in\n' " */pulls/*) printf '%s\\n' \"$FAKE_PULL_JSON\" ;;\n" " */statuses) printf '%s\\n' \"$FAKE_STATUSES_JSON\" ;;\n" " */codeql-scan-dispatch.yml/runs*) printf '%s\\n' \"$FAKE_DISPATCH_RUNS_JSON\" ;;\n" From c99d49a86a2728d136d9e3a34a6b82a2c0e84a90 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 11:16:53 +0900 Subject: [PATCH 07/15] style(codeql): normalize pagination fixture spacing --- tests/test_codeql_pr_workflow_contract.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_codeql_pr_workflow_contract.py b/tests/test_codeql_pr_workflow_contract.py index b5858ac06a..d511ec8385 100644 --- a/tests/test_codeql_pr_workflow_contract.py +++ b/tests/test_codeql_pr_workflow_contract.py @@ -373,7 +373,6 @@ def test_codeql_pr_finds_completed_dispatch_scan_beyond_first_results_page( assert "completed CodeQL dispatch scan job for python: success" in dispatch_result.stdout - def test_codeql_action_steps_use_one_version_per_workflow() -> None: """Prevent CodeQL init/analyze version splits from failing the scheduled scan.""" workflow = (REPO_ROOT / ".github/workflows/scheduled-security-scan.yml").read_text( From e715a5e5e5235130cd8d6054f14088cd2c6cad6f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 11:39:28 +0900 Subject: [PATCH 08/15] test(codeql): match paginated empty dispatch fixtures Signed-off-by: Seongho Bae --- tests/test_codeql_pr_workflow_contract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_codeql_pr_workflow_contract.py b/tests/test_codeql_pr_workflow_contract.py index d511ec8385..9a31e4de14 100644 --- a/tests/test_codeql_pr_workflow_contract.py +++ b/tests/test_codeql_pr_workflow_contract.py @@ -461,8 +461,8 @@ def test_codeql_pr_attempt_one_without_verdict_fails_pending_without_dispatch( "PATH": f"{fake_bin}:{os.environ['PATH']}", "FAKE_PULL_JSON": json.dumps({"head": {"sha": head_sha}, "state": "open"}), "FAKE_STATUSES_JSON": json.dumps([]), - "FAKE_DISPATCH_RUNS_JSON": json.dumps({"workflow_runs": []}), - "FAKE_DISPATCH_JOBS_JSON": json.dumps({"jobs": []}), + "FAKE_DISPATCH_RUNS_JSON": json.dumps([{"workflow_runs": []}]), + "FAKE_DISPATCH_JOBS_JSON": json.dumps([{"jobs": []}]), "FAKE_POST_LOG": str(post_log), "GH_TOKEN": "fake-token", "TARGET_REPOSITORY": "ContextualWisdomLab/naruon", From fd76b8863f558a7a110975a8a6423ef3912df06e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 11:43:38 +0900 Subject: [PATCH 09/15] =?UTF-8?q?fix(codeql):=20=EC=96=B8=EC=96=B4?= =?UTF-8?q?=EB=B3=84=20=EC=9E=AC=EC=8B=9C=EC=9E=91=20=EA=B2=BD=ED=95=A9?= =?UTF-8?q?=EA=B3=BC=20=EC=9E=AC=EB=B6=84=EC=84=9D=20=EB=B0=98=EB=B3=B5=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Codex Signed-off-by: Seongho Bae --- .github/workflows/codeql-pr.yml | 26 +++++++- .github/workflows/codeql-scan-dispatch.yml | 55 ++++++++++------- AGENTS.md | 14 +++++ docs/doctoring/codeql-matrix-wake-race.md | 55 +++++++++++++++++ tests/test_codeql_pr_workflow_contract.py | 59 +++++++++++++++---- ..._codeql_scan_dispatch_workflow_contract.py | 51 ++++++++++++---- ...d_codeql_dispatch_runner_image_contract.py | 5 +- 7 files changed, 217 insertions(+), 48 deletions(-) create mode 100644 docs/doctoring/codeql-matrix-wake-race.md diff --git a/.github/workflows/codeql-pr.yml b/.github/workflows/codeql-pr.yml index 9e0e04a9d1..9c414955b3 100644 --- a/.github/workflows/codeql-pr.yml +++ b/.github/workflows/codeql-pr.yml @@ -217,7 +217,7 @@ jobs: .[] | .workflow_runs[] | select(.path == $path) | select(.event == "repository_dispatch") - | select(.status == "completed") + | select(.status == "completed" or .status == "in_progress") | select(.display_title == $title or .name == $title) ] | first @@ -226,7 +226,9 @@ jobs: if [[ "$run_id" =~ ^[1-9][0-9]*$ ]]; then jobs_json="$(gh api --paginate --slurp "repos/ContextualWisdomLab/.github/actions/runs/${run_id}/jobs")" job_conclusion="$(printf '%s' "$jobs_json" | jq -r --arg name "$expected_job" ' - [.[] | .jobs[] | select(.name == $name)] + [.[] | .jobs[]] as $jobs + | select([$jobs[] | select(.name == "validate-dispatch" and .status == "completed" and .conclusion == "success")] | length == 1) + | [$jobs[] | select(.name == $name and .status == "completed")] | if length == 1 then .[0].conclusion else empty end ')" case "$job_conclusion" in @@ -354,6 +356,18 @@ jobs: done < <(printf '%s' "$include_json" | jq -c '.[]') statuses="$(gh api "repos/${TARGET_REPOSITORY}/commits/${PR_HEAD_SHA}/statuses")" + expected_title="CodeQL Scan Dispatch ${TARGET_REPOSITORY}#${PR_NUMBER}@${PR_HEAD_SHA}" + dispatch_runs="$(gh api --paginate --slurp "repos/ContextualWisdomLab/.github/actions/workflows/codeql-scan-dispatch.yml/runs")" + dispatch_run_id="$(printf '%s' "$dispatch_runs" | jq -r --arg title "$expected_title" ' + [.[] | .workflow_runs[] + | select(.path == ".github/workflows/codeql-scan-dispatch.yml" and .event == "repository_dispatch") + | select(.status == "completed" or .status == "in_progress") + | select(.display_title == $title or .name == $title)] | first | .id // empty + ')" + dispatch_jobs='[]' + if [[ "$dispatch_run_id" =~ ^[1-9][0-9]*$ ]]; then + dispatch_jobs="$(gh api --paginate --slurp "repos/ContextualWisdomLab/.github/actions/runs/${dispatch_run_id}/jobs")" + fi pending_matrix='[]' while IFS= read -r entry; do language="$(printf '%s' "$entry" | jq -r '.language // empty')" @@ -368,6 +382,14 @@ jobs: ] | first // {} | .state // empty ')" + if ! [[ "$verdict_state" =~ ^(success|failure|error)$ ]]; then + verdict_state="$(printf '%s' "$dispatch_jobs" | jq -r --arg name "CodeQL dispatch scan (${language})" ' + [.[] | .jobs[]] as $jobs + | select([$jobs[] | select(.name == "validate-dispatch" and .status == "completed" and .conclusion == "success")] | length == 1) + | [$jobs[] | select(.name == $name and .status == "completed")] + | if length == 1 then .[0].conclusion else empty end + ')" + fi case "$verdict_state" in success|failure|error) echo "Found authenticated current-head CodeQL verdict for ${language}: ${verdict_state}." diff --git a/.github/workflows/codeql-scan-dispatch.yml b/.github/workflows/codeql-scan-dispatch.yml index d31c9c6c58..fded11c9f9 100644 --- a/.github/workflows/codeql-scan-dispatch.yml +++ b/.github/workflows/codeql-scan-dispatch.yml @@ -275,7 +275,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 30 permissions: - actions: write + actions: read contents: read security-events: read id-token: write @@ -511,10 +511,23 @@ jobs: echo "::error::Could not publish the CodeQL dispatch status after all configured credentials failed; the exact required job will remain failed and will not be woken with stale or missing evidence." exit 1 + wake-required: + name: Wake verified CodeQL required jobs + needs: [validate-dispatch, scan] + if: >- + always() + && needs.validate-dispatch.result == 'success' + && needs.scan.result != 'cancelled' + && needs.scan.result != 'skipped' + runs-on: ubuntu-24.04 + timeout-minutes: 8 + permissions: + actions: write + contents: read + steps: - name: Wake exact CodeQL required job if: >- always() - && steps.publish_status.outcome == 'success' && needs.validate-dispatch.outputs.target_repository != '' && needs.validate-dispatch.outputs.pr_number != '' && needs.validate-dispatch.outputs.head_sha != '' @@ -527,7 +540,6 @@ jobs: HEAD_SHA: ${{ needs.validate-dispatch.outputs.head_sha }} REQUIRED_RUN_ID: ${{ needs.validate-dispatch.outputs.required_run_id }} REQUIRED_JOBS: ${{ needs.validate-dispatch.outputs.required_jobs }} - REQUIRED_LANGUAGE: ${{ matrix.language }} WAKE_TOKEN_SOURCE: ${{ needs.validate-dispatch.outputs.target_repository == github.repository && 'github-token' || secrets.PR_REVIEW_MERGE_TOKEN != '' && 'PR_REVIEW_MERGE_TOKEN' || secrets.OPENCODE_APPROVE_TOKEN != '' && 'OPENCODE_APPROVE_TOKEN' || 'unavailable' }} run: | set -euo pipefail @@ -535,13 +547,7 @@ jobs: echo "::error::Actions-capable CodeQL wake credential is unavailable." exit 1 fi - REQUIRED_JOB_ID="$(printf '%s' "$REQUIRED_JOBS" | jq -r --arg lang "$REQUIRED_LANGUAGE" ' - [.[] | select(.language == $lang) | .job_id | tostring] - | if length == 1 and (.[0] | test("^[1-9][0-9]*$")) then .[0] else empty end - ')" - if ! [[ "$REQUIRED_RUN_ID" =~ ^[1-9][0-9]*$ ]] || - ! [[ "$REQUIRED_JOB_ID" =~ ^[1-9][0-9]*$ ]] || - ! [[ "$REQUIRED_LANGUAGE" =~ ^[a-z0-9-]+$ ]]; then + if ! [[ "$REQUIRED_RUN_ID" =~ ^[1-9][0-9]*$ ]]; then echo "::error::CodeQL wake identity is non-canonical." exit 1 fi @@ -560,23 +566,28 @@ jobs: | select(.event == "pull_request") | select(.path == ".github/workflows/codeql-pr.yml") | select(.head_sha == $head) - | .id // empty - ')" - expected_name="CodeQL compatibility analysis (${REQUIRED_LANGUAGE})" - job="$(gh api "repos/${TARGET_REPOSITORY}/actions/jobs/${REQUIRED_JOB_ID}")" - job_identity="$(printf '%s' "$job" | jq -r --arg head "$HEAD_SHA" --arg name "$expected_name" --argjson run_id "$REQUIRED_RUN_ID" --argjson job_id "$REQUIRED_JOB_ID" ' - select(.id == $job_id) - | select(.run_id == $run_id) - | select(.head_sha == $head) - | select(.name == $name) | select(.status == "completed" and .conclusion == "failure") | .id // empty ')" + jobs="$(gh api --paginate --slurp "repos/${TARGET_REPOSITORY}/actions/runs/${REQUIRED_RUN_ID}/jobs")" + jobs_verified="$(printf '%s' "$jobs" | jq -r --arg head "$HEAD_SHA" --argjson run_id "$REQUIRED_RUN_ID" --argjson required "$REQUIRED_JOBS" ' + [.[] | .jobs[]] as $jobs + | [$jobs[] | select(.conclusion == "failure")] as $failed + | ($required | length > 0) + and ($failed | length > 0) + and ($jobs | all(.status == "completed")) + and ($required | all(. as $expected | + [$jobs[] | select(.id == $expected.job_id) + | select(.run_id == $run_id and .head_sha == $head) + | select(.name == ("CodeQL compatibility analysis (" + $expected.language + ")")) + | select(.conclusion == "failure")] | length == 1)) + and (($failed | map(.id) | sort) == ($required | map(.job_id) | sort)) + ')" if [ "$run_identity" != "$REQUIRED_RUN_ID" ] || - [ "$job_identity" != "$REQUIRED_JOB_ID" ]; then + [ "$jobs_verified" != "true" ]; then echo "::error::CodeQL wake rejected missing or ambiguous exact run/job identity." exit 1 fi - gh api -X POST "repos/${TARGET_REPOSITORY}/actions/jobs/${REQUIRED_JOB_ID}/rerun" >/dev/null - echo "Re-ran exact failed CodeQL job ${REQUIRED_JOB_ID} for ${REQUIRED_LANGUAGE} on ${HEAD_SHA}." + gh api -X POST "repos/${TARGET_REPOSITORY}/actions/runs/${REQUIRED_RUN_ID}/rerun-failed-jobs" >/dev/null + echo "Re-ran verified failed CodeQL jobs once for run ${REQUIRED_RUN_ID} on ${HEAD_SHA}." diff --git a/AGENTS.md b/AGENTS.md index e955f8b36a..cb8edb4864 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -212,3 +212,17 @@ them alone proves succession. variable in CI, so a failure class exists that cannot reproduce locally. Before calling a scheduler change clean, run the affected tests both ways, including `GITHUB_ACTIONS=true python3 -m pytest `. +- CodeQL wake test doubles must reproduce GitHub rejecting a job rerun while + its containing run is already running. A fake POST that always succeeds hides + the language-shard race observed in dispatch run `34178442472`. Keep scan, + status publication, authenticated verdict consumption, and required-job + recovery as separate outcomes; a published `success` from an unaccepted + creator is not a passing required check. Do not mask this with blanket 403 + suppression, sleep polling, or a wider unauthenticated creator allowlist. +- Finish the language matrix before one trusted coordinator requests recovery. + Before a native failed-jobs rerun, verify the current PR head, completed failed + run, and the complete failed-job set against the supplied CodeQL identities. + Keep verdict readers and dispatch admission aligned: completed validated scan + jobs must stop redispatch after both success and real findings, even while + the recovery coordinator is still finishing. Otherwise recovery starts a new + scan loop or waits for its own containing workflow to finish. diff --git a/docs/doctoring/codeql-matrix-wake-race.md b/docs/doctoring/codeql-matrix-wake-race.md new file mode 100644 index 0000000000..5dc37a770a --- /dev/null +++ b/docs/doctoring/codeql-matrix-wake-race.md @@ -0,0 +1,55 @@ +# CodeQL matrix recovery race + +Status: local implementation under verification; not merged or deployed. + +## Evidence and failure boundary + +On 2026-09-08, `.github` PR #2029 head +`eb79481bc1696c63273b6c2ca22b5e34f68d0208` produced two successful scan +statuses in dispatch run `34178442472`. Python job `101912523347` then failed +at the wake step with `The workflow run containing this job is already running +(HTTP 403)`. The actions shard had already requested recovery of required run +`34177963535`. This was not a CodeQL finding or a status publication denial. +Both published statuses were authored by `github-actions[bot]`, which the +required consumer deliberately does not trust as the OpenCode app identity. + +The existing test double accepted every POST. On base +`c99d49a86a2728d136d9e3a34a6b82a2c0e84a90`, all 24 dispatch tests passed. +Reproducing GitHub's observed active-run rejection made the parallel-language +test fail while completed-run recovery still passed (1 failed, 1 passed). + +## Proposed repair and alternatives + +One coordinator follows the complete scan matrix and requests one native +failed-jobs rerun. It validates the live open PR head, exact workflow/run, +terminal run state, and equality of the entire failed-job set with the supplied +CodeQL job identities. An unrelated failure, stale identity, or active run +fails closed without a POST. Scan jobs retain read-only Actions access; +recovery alone needs write access. No polling or extra model invocation is added. + +The consumer can read a completed scan job only after the same dispatch run's +validation job succeeds. The coordinator may still be finishing; requiring +the entire dispatch run to finish would introduce a race with the jobs it +just restarted. Admission uses the same terminal-evidence conditions to stop +rescanning both clean results and real findings. Findings remain failed checks. + +Rejected alternatives: ignoring 403 loses recovery; repeating per-language +requests races again; sleeping occupies runners without establishing identity; +trusting every status creator weakens the verification boundary. A separate +completion-event workflow adds another event and evidence-transfer contract. + +## Verification still required + +Run both CodeQL contract modules, broader workflow admission/image contracts, +and current-head hosted checks. Inspect the rendered guidance. Prove one +recovery request for a real multilingual PR and terminal required checks before +claiming rollout. Local fake APIs cannot establish hosted convergence or relief +of the organization-wide 60-job ceiling. + +## Reference + +GitHub. (n.d.). *REST API endpoints for workflow runs*. Retrieved September 8, +2026, from https://docs.github.com/en/rest/actions/workflow-runs + +The documented failed-jobs endpoint restarts failed jobs and their dependent +jobs; therefore the full failed-job set must be validated before calling it. diff --git a/tests/test_codeql_pr_workflow_contract.py b/tests/test_codeql_pr_workflow_contract.py index d511ec8385..7751ca43fb 100644 --- a/tests/test_codeql_pr_workflow_contract.py +++ b/tests/test_codeql_pr_workflow_contract.py @@ -6,6 +6,8 @@ import sys from pathlib import Path +import pytest + from tests.test_opencode_workflow_shell_syntax import _extract_run_block @@ -226,13 +228,13 @@ def _run_verdict_read( env=dispatch_env, timeout=60, ) output_values = dict( - line.split("=", 1) for line in output.read_text(encoding="utf-8").splitlines() + line.split("=", 1) for line in (output.read_text(encoding="utf-8").splitlines() if output.exists() else []) ) verdict_env = { **os.environ, "LANGUAGE": "python", - "DISPATCH_OUTCOME": "success", - "VERDICT_STATE": output_values["verdict"], + "DISPATCH_OUTCOME": "success" if dispatch_result.returncode == 0 else "failure", + "VERDICT_STATE": output_values.get("verdict", ""), } verdict_result = subprocess.run( [bash], input=verdict_script, text=True, capture_output=True, check=False, @@ -286,8 +288,15 @@ def test_codeql_pr_one_shot_read_accepts_the_opencode_agent_creator(tmp_path: Pa assert "Current-head CodeQL dispatch verdict for python: success." in verdict_result.stdout +@pytest.mark.parametrize("run_status,validation_status,scan_status,expected", [ + ("completed", "success", "completed", 0), + ("in_progress", "success", "completed", 0), + ("in_progress", "failure", "completed", 1), + ("in_progress", "success", "in_progress", 1), +]) def test_codeql_pr_one_shot_read_accepts_completed_dispatch_scan_job_when_status_unpublishable( tmp_path: Path, + run_status: str, validation_status: str, scan_status: str, expected: int, ) -> None: """A completed dispatch scan job is terminal evidence when statuses:write 403s. @@ -306,7 +315,7 @@ def test_codeql_pr_one_shot_read_accepts_completed_dispatch_scan_job_when_status "id": 34173910106, "event": "repository_dispatch", "path": ".github/workflows/codeql-scan-dispatch.yml", - "status": "completed", + "status": run_status, "display_title": ( "CodeQL Scan Dispatch ContextualWisdomLab/naruon#42@" + head_sha ), @@ -318,17 +327,20 @@ def test_codeql_pr_one_shot_read_accepts_completed_dispatch_scan_job_when_status }, dispatch_jobs={ "jobs": [ + {"name": "validate-dispatch", "status": "completed", "conclusion": validation_status}, { "name": "CodeQL dispatch scan (python)", + "status": scan_status, "conclusion": "success", } ] }, ) - assert dispatch_result.returncode == 0, dispatch_result.stderr + dispatch_result.stdout - assert verdict_result.returncode == 0, verdict_result.stderr + verdict_result.stdout - assert "completed CodeQL dispatch scan job for python: success" in dispatch_result.stdout - assert "Current-head CodeQL dispatch verdict for python: success." in verdict_result.stdout + assert dispatch_result.returncode == expected, dispatch_result.stderr + dispatch_result.stdout + assert verdict_result.returncode == expected, verdict_result.stderr + verdict_result.stdout + if expected == 0: + assert "completed CodeQL dispatch scan job for python: success" in dispatch_result.stdout + assert "Current-head CodeQL dispatch verdict for python: success." in verdict_result.stdout def test_codeql_pr_finds_completed_dispatch_scan_beyond_first_results_page( @@ -359,8 +371,10 @@ def test_codeql_pr_finds_completed_dispatch_scan_beyond_first_results_page( {"jobs": []}, { "jobs": [ + {"name": "validate-dispatch", "status": "completed", "conclusion": "success"}, { "name": "CodeQL dispatch scan (python)", + "status": "completed", "conclusion": "success", } ] @@ -461,8 +475,8 @@ def test_codeql_pr_attempt_one_without_verdict_fails_pending_without_dispatch( "PATH": f"{fake_bin}:{os.environ['PATH']}", "FAKE_PULL_JSON": json.dumps({"head": {"sha": head_sha}, "state": "open"}), "FAKE_STATUSES_JSON": json.dumps([]), - "FAKE_DISPATCH_RUNS_JSON": json.dumps({"workflow_runs": []}), - "FAKE_DISPATCH_JOBS_JSON": json.dumps({"jobs": []}), + "FAKE_DISPATCH_RUNS_JSON": json.dumps([{"workflow_runs": []}]), + "FAKE_DISPATCH_JOBS_JSON": json.dumps([{"jobs": []}]), "FAKE_POST_LOG": str(post_log), "GH_TOKEN": "fake-token", "TARGET_REPOSITORY": "ContextualWisdomLab/naruon", @@ -539,6 +553,8 @@ def _write_coordinator_fakes( 'case "$path" in\n' " */pulls/*) body=$FAKE_PULL_JSON ;;\n" " */statuses) body=$FAKE_STATUSES_JSON ;;\n" + " */codeql-scan-dispatch.yml/runs) body=$FAKE_DISPATCH_RUNS_JSON ;;\n" + " repos/ContextualWisdomLab/.github/actions/runs/*/jobs) body=$FAKE_DISPATCH_JOBS_JSON ;;\n" " */actions/runs/*/jobs) body=$FAKE_JOBS_JSON ;;\n" " *) exit 1 ;;\n" "esac\n" @@ -614,6 +630,8 @@ def _run_coordinator( "FAKE_PULL_JSON": json.dumps(pull), "FAKE_JOBS_JSON": json.dumps(jobs), "FAKE_STATUSES_JSON": json.dumps(statuses), + "FAKE_DISPATCH_RUNS_JSON": json.dumps([{"workflow_runs": []}]), + "FAKE_DISPATCH_JOBS_JSON": json.dumps([{"jobs": []}]), "FAKE_POST_LOG": str(post_log), "FAKE_POST_BODY": str(post_body), "FAKE_CURL_LOG": str(tmp_path / "curl.log"), @@ -699,6 +717,27 @@ def test_codeql_coordinator_skips_dispatch_when_every_language_has_a_verdict( assert "already have authenticated terminal verdicts" in result.stdout +@pytest.mark.parametrize("scan_conclusion", ["success", "failure"]) +def test_codeql_coordinator_does_not_redispatch_completed_scan_jobs( + tmp_path: Path, scan_conclusion: str, +) -> None: + """Terminal fallback evidence stops rescan loops, including real findings.""" + title = "CodeQL Scan Dispatch ContextualWisdomLab/naruon#42@" + "b" * 40 + result, post_log, _ = _run_coordinator(tmp_path, env_overrides={ + "FAKE_DISPATCH_RUNS_JSON": json.dumps([{"workflow_runs": [{ + "id": 123, "path": ".github/workflows/codeql-scan-dispatch.yml", + "event": "repository_dispatch", "status": "in_progress", "display_title": title, + }]}]), + "FAKE_DISPATCH_JOBS_JSON": json.dumps([{"jobs": [ + {"name": "validate-dispatch", "status": "completed", "conclusion": "success"}, + *[{"name": f"CodeQL dispatch scan ({language})", "status": "completed", + "conclusion": scan_conclusion} for language in ("python", "actions")], + ]}]), + }) + assert result.returncode == 0, result.stderr + assert not post_log.exists() + + def test_codeql_coordinator_fails_closed_when_a_shard_job_id_is_missing( tmp_path: Path, ) -> None: diff --git a/tests/test_codeql_scan_dispatch_workflow_contract.py b/tests/test_codeql_scan_dispatch_workflow_contract.py index 9562387473..9c1ec664a4 100644 --- a/tests/test_codeql_scan_dispatch_workflow_contract.py +++ b/tests/test_codeql_scan_dispatch_workflow_contract.py @@ -513,7 +513,7 @@ def test_dispatch_publish_keeps_successful_scan_when_status_write_is_denied() -> """ workflow = WORKFLOW_PATH.read_text(encoding="utf-8") publish = workflow.split(" - name: Publish CodeQL dispatch status\n", 1)[1].split( - "\n - name: Wake exact CodeQL required job\n", 1 + "\n wake-required:\n", 1 )[0] assert "GATE_OUTCOME" in publish @@ -529,18 +529,21 @@ def test_dispatch_wakes_only_the_exact_failed_codeql_job() -> None: "\n\n - name:", 1 )[0] - assert "steps.publish_status.outcome == 'success'" in wake + coordinator = workflow.split(" wake-required:\n", 1)[1] + assert "needs: [validate-dispatch, scan]" in coordinator + assert "matrix:" not in coordinator + assert "steps.publish_status.outcome" not in wake assert 'gh api "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}"' in wake assert 'gh api "repos/${TARGET_REPOSITORY}/actions/runs/${REQUIRED_RUN_ID}"' in wake - assert 'gh api "repos/${TARGET_REPOSITORY}/actions/jobs/${REQUIRED_JOB_ID}"' in wake + assert 'actions/runs/${REQUIRED_RUN_ID}/jobs' in wake assert 'select(.event == "pull_request")' in wake assert 'select(.path == ".github/workflows/codeql-pr.yml")' in wake assert "select(.head_sha == $head)" in wake - assert "select(.run_id == $run_id)" in wake - assert "select(.name == $name)" in wake + assert "select(.run_id == $run_id and .head_sha == $head)" in wake + assert '$expected.language' in wake assert 'select(.status == "completed" and .conclusion == "failure")' in wake - assert 'actions/jobs/${REQUIRED_JOB_ID}/rerun' in wake - assert "rerun-failed-jobs" not in wake + assert wake.count('gh api -X POST') == 1 + assert 'actions/runs/${REQUIRED_RUN_ID}/rerun-failed-jobs' in wake assert "while " not in wake assert "sleep " not in wake @@ -550,7 +553,8 @@ def test_dispatch_wake_has_only_trusted_actions_write_boundary() -> None: scan = workflow.split(" scan:\n", 1)[1] scan_permissions = scan.split(" strategy:\n", 1)[0] - assert "actions: write" in scan_permissions + assert "actions: write" not in scan_permissions + assert "actions: write" in workflow.split(" wake-required:\n", 1)[1] assert "pull_request:" not in workflow assert "pull_request_target:" not in workflow assert "needs.validate-dispatch.outputs.required_run_id != ''" in scan @@ -564,6 +568,7 @@ def _run_wake_step( pull: dict | None = None, run: dict | None = None, job: dict | None = None, + extra_jobs: list[dict] | None = None, ) -> tuple[subprocess.CompletedProcess[str], Path]: """Execute the exact wake block against fixture-backed GitHub API responses.""" bash = shutil.which("bash") @@ -602,9 +607,14 @@ def _run_wake_step( 'if [ "${2:-}" = "-X" ]; then\n' ' test "$3" = POST\n' ' printf \'%s\\n\' "$4" >>"$FAKE_POST_LOG"\n' + ' if [ "$(printf \'%s\' "$FAKE_RUN_JSON" | jq -r .status)" != completed ]; then\n' + ' echo "gh: The workflow run containing this job is already running (HTTP 403)" >&2\n' + ' exit 1\n' + ' fi\n' " exit 0\n" "fi\n" 'case "$2" in\n' + ' --paginate) printf \'%s\\n\' "$FAKE_JOBS_JSON" ;;\n' ' */pulls/*) printf \'%s\\n\' "$FAKE_PULL_JSON" ;;\n' ' */actions/runs/*) printf \'%s\\n\' "$FAKE_RUN_JSON" ;;\n' ' */actions/jobs/*) printf \'%s\\n\' "$FAKE_JOB_JSON" ;;\n' @@ -619,6 +629,11 @@ def _run_wake_step( "FAKE_PULL_JSON": json.dumps(pull), "FAKE_RUN_JSON": json.dumps(run), "FAKE_JOB_JSON": json.dumps(job), + "FAKE_JOBS_JSON": json.dumps([{"jobs": [job, { + "id": 44, "run_id": 42, "head_sha": head_sha, + "name": "CodeQL compatibility analysis (actions)", + "status": "completed", "conclusion": "failure", + }, *(extra_jobs or [])]}]), "FAKE_POST_LOG": str(post_log), "GH_TOKEN": "fake-token", "WAKE_TOKEN_SOURCE": "PR_REVIEW_MERGE_TOKEN", @@ -645,7 +660,7 @@ def test_dispatch_wake_reruns_only_fixture_bound_exact_job(tmp_path: Path) -> No assert result.returncode == 0, result.stderr assert post_log.read_text(encoding="utf-8").splitlines() == [ - "repos/ContextualWisdomLab/naruon/actions/jobs/43/rerun" + "repos/ContextualWisdomLab/naruon/actions/runs/42/rerun-failed-jobs" ] @@ -694,8 +709,8 @@ def test_dispatch_wake_rejects_ambiguous_or_nonfailed_job_identity(tmp_path: Pat assert not successful_job_log.exists() -def test_dispatch_wake_allows_parallel_language_rerun_on_same_exact_run(tmp_path: Path) -> None: - """Another language may already have moved the shared run back to in_progress.""" +def test_dispatch_wake_rejects_a_run_already_in_progress(tmp_path: Path) -> None: + """Never POST another rerun while the parent run is already active.""" result, post_log = _run_wake_step( tmp_path, run={ @@ -708,8 +723,18 @@ def test_dispatch_wake_allows_parallel_language_rerun_on_same_exact_run(tmp_path }, ) - assert result.returncode == 0, result.stderr - assert post_log.exists() + assert result.returncode == 1, result.stderr + assert not post_log.exists() + + +def test_dispatch_wake_rejects_unrelated_failed_jobs(tmp_path: Path) -> None: + """The native failed-jobs endpoint must not retry unvalidated jobs.""" + result, post_log = _run_wake_step(tmp_path, extra_jobs=[{ + "id": 45, "run_id": 42, "head_sha": "b" * 40, + "name": "Other failed job", "status": "completed", "conclusion": "failure", + }]) + assert result.returncode == 1 + assert not post_log.exists() def test_codeql_scan_dispatch_serialises_the_matrix_payload() -> None: diff --git a/tests/test_scheduler_and_codeql_dispatch_runner_image_contract.py b/tests/test_scheduler_and_codeql_dispatch_runner_image_contract.py index ba0b2598a9..38750871b5 100644 --- a/tests/test_scheduler_and_codeql_dispatch_runner_image_contract.py +++ b/tests/test_scheduler_and_codeql_dispatch_runner_image_contract.py @@ -54,7 +54,10 @@ def test_codeql_scan_dispatch_uses_explicit_supported_image(self) -> None: """Require both CodeQL Scan Dispatch jobs to pin Ubuntu 24.04.""" workflow = CODEQL_SCAN_DISPATCH.read_text(encoding="utf-8") self.assertNotIn("runs-on: ubuntu-latest", workflow) - self.assertEqual(workflow.count("runs-on: ubuntu-24.04"), 2) + self.assertEqual(workflow.count("runs-on: ubuntu-24.04"), 3) + recovery = workflow.split(" wake-required:\n", 1)[1] + self.assertIn("needs: [validate-dispatch, scan]", recovery) + self.assertNotIn("matrix:", recovery) def test_python_security_uses_explicit_supported_image(self) -> None: """Require all three Python Security jobs to pin Ubuntu 24.04.""" From b97d98259c30057190f64915423cf73b227bd6dd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 13:16:52 +0900 Subject: [PATCH 10/15] test(codeql): bind coordinator fallback to exact dispatch identity --- tests/test_codeql_pr_workflow_contract.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/test_codeql_pr_workflow_contract.py b/tests/test_codeql_pr_workflow_contract.py index 16904b566f..463c986eed 100644 --- a/tests/test_codeql_pr_workflow_contract.py +++ b/tests/test_codeql_pr_workflow_contract.py @@ -500,6 +500,18 @@ def test_codeql_pr_fallback_binds_live_base_and_required_run_identity() -> None: ) in shard assert "Could not validate live pull request base SHA before CodeQL verdict read." in shard + +def test_codeql_coordinator_fallback_binds_live_base_and_required_run_identity() -> None: + """Coordinator lookup must use the exact dispatch run identity from #2028.""" + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + coordinator = workflow.split(" dispatch-current-head:\\n", 1)[1] + + assert ( + 'expected_title="CodeQL Scan Dispatch ${TARGET_REPOSITORY}#${PR_NUMBER}' + '@${PR_HEAD_SHA}/${live_base}/${REQUIRED_RUN_ID}"' + ) in coordinator + + def test_codeql_action_steps_use_one_version_per_workflow() -> None: """Prevent CodeQL init/analyze version splits from failing the scheduled scan.""" workflow = (REPO_ROOT / ".github/workflows/scheduled-security-scan.yml").read_text( @@ -841,7 +853,7 @@ def test_codeql_coordinator_does_not_redispatch_completed_scan_jobs( tmp_path: Path, scan_conclusion: str, ) -> None: """Terminal fallback evidence stops rescan loops, including real findings.""" - title = "CodeQL Scan Dispatch ContextualWisdomLab/naruon#42@" + "b" * 40 + title = _dispatch_scan_title() result, post_log, _ = _run_coordinator(tmp_path, env_overrides={ "FAKE_DISPATCH_RUNS_JSON": json.dumps([{"workflow_runs": [{ "id": 123, "path": ".github/workflows/codeql-scan-dispatch.yml", From 78aca1ded85d70e71e94470eacd4cb1ca203c904 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 13:21:02 +0900 Subject: [PATCH 11/15] test(codeql): align coordinator fallback fixture with required run --- tests/test_codeql_pr_workflow_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_codeql_pr_workflow_contract.py b/tests/test_codeql_pr_workflow_contract.py index 463c986eed..4949ec7d6d 100644 --- a/tests/test_codeql_pr_workflow_contract.py +++ b/tests/test_codeql_pr_workflow_contract.py @@ -853,7 +853,7 @@ def test_codeql_coordinator_does_not_redispatch_completed_scan_jobs( tmp_path: Path, scan_conclusion: str, ) -> None: """Terminal fallback evidence stops rescan loops, including real findings.""" - title = _dispatch_scan_title() + title = _dispatch_scan_title(required_run_id="99") result, post_log, _ = _run_coordinator(tmp_path, env_overrides={ "FAKE_DISPATCH_RUNS_JSON": json.dumps([{"workflow_runs": [{ "id": 123, "path": ".github/workflows/codeql-scan-dispatch.yml", From 868a208b83d60c855da42477ac0501cb66a69f80 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 13:21:20 +0900 Subject: [PATCH 12/15] fix(codeql): bind coordinator fallback to exact dispatch run --- .github/workflows/codeql-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-pr.yml b/.github/workflows/codeql-pr.yml index 3a102bb741..8c305ad1f8 100644 --- a/.github/workflows/codeql-pr.yml +++ b/.github/workflows/codeql-pr.yml @@ -373,7 +373,7 @@ jobs: done < <(printf '%s' "$include_json" | jq -c '.[]') statuses="$(gh api "repos/${TARGET_REPOSITORY}/commits/${PR_HEAD_SHA}/statuses")" - expected_title="CodeQL Scan Dispatch ${TARGET_REPOSITORY}#${PR_NUMBER}@${PR_HEAD_SHA}" + expected_title="CodeQL Scan Dispatch ${TARGET_REPOSITORY}#${PR_NUMBER}@${PR_HEAD_SHA}/${live_base}/${REQUIRED_RUN_ID}" dispatch_runs="$(gh api --paginate --slurp "repos/ContextualWisdomLab/.github/actions/workflows/codeql-scan-dispatch.yml/runs")" dispatch_run_id="$(printf '%s' "$dispatch_runs" | jq -r --arg title "$expected_title" ' [.[] | .workflow_runs[] From d1affc03a02112e0927e9f168b41adfc53a0ba00 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 13:23:12 +0900 Subject: [PATCH 13/15] docs(gap): record exact CodeQL coordinator identity repair --- docs/product-technical-gap-baseline.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index dedfd1793b..e8f62742c8 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -3360,4 +3360,4 @@ same name in another file can carry the opposite safety property.** - **Exact evidence:** dispatch run `34182987578` for .github#2033@`de96b8b46143fe63d8fec1929b5739a4babee8c4` completed both language scans and published `codeql-dispatch/python=success` and `codeql-dispatch/actions=success`. The actions wake then failed with GitHub HTTP 403 because the python wake had already restarted required run `34181386094`. - **Gap / failure scene:** matrix shards independently mutated one shared required run. The first job rerun made the run active, so the second job could not wake; a clean security result remained a failed required check. - **Action:** #2032 moves Actions write to one post-matrix coordinator, validates the exact open PR/head/base/run and complete failed-job set, then requests one `rerun-failed-jobs`. It retains #2028's immutable base-SHA and required-run binding and admits completed scan evidence only after dispatch validation succeeds. -- **Status:** Proposed. Non-force restacked onto protected main after 51 focused contract cases plus Python/YAML syntax checks; exact-head hosted Checks remain authoritative before merge. +- **Status:** Proposed. Non-force restacked onto protected main; a cross-PR RED then proved the coordinator still used the pre-#2028 title, and GREEN aligned it with `head/base/required_run`. Final local evidence is 52 focused contract cases plus Python/YAML syntax checks; exact-head hosted Checks remain authoritative before merge. From c19d31f0e2c4fe0ad85ffe2840b1e8a5f29a5af6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 13:59:40 +0900 Subject: [PATCH 14/15] docs(actions): distinguish workflow counts from job capacity Correct the repository-run versus organization-job comparison and preserve observed dynamic scan coverage boundaries. Co-Authored-By: Codex Signed-off-by: Seongho Bae --- AGENTS.md | 6 ++++++ docs/product-technical-gap-baseline.md | 22 ++++++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index cb8edb4864..7cf37ad836 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -171,6 +171,12 @@ them alone proves succession. ## Test-gate regressions and stale-PR merges +- Queue measurements must label timestamp, repository scope, and units. Workflow + runs, check runs, and executing jobs are not interchangeable; a repository's + in-progress run count cannot establish organization-wide job-ceiling utilization. + Distinguish runner-admission wait from execution time. Before consolidating + dynamic and central scanners, verify query coverage and target SARIF publication + equivalence; additional job fan-out alone does not prove duplicate security coverage. - A red `tests`, coverage, or `interrogate` gate on your pull request is not proof that your diff caused it. Full-suite execution on a push to `main` is not guaranteed: the workflows that run `pytest tests` on push are `paths:`-filtered, so a pairing broken outside their diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index e8f62742c8..9b94fc9c92 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -3214,18 +3214,32 @@ requesting `ubuntu-latest`. Pinned all six to `ubuntu-24.04` (10 total job occur `tests/test_scheduler_and_codeql_dispatch_runner_image_contract.py` covering all six. **This does not, by itself, explain today's stall**: a direct query of `.github`'s own queued-run backlog (307 queued, confirmed via `actions/runs?status=queued`, cross-checked against `status=in_progress` returning only -5-6 -- itself anomalous against the documented 60-job Team-plan ceiling, since 5-6 is far below 60) showed +5-6 workflow runs in this repository, not organization-wide executing jobs) showed the dominant contributors by far were `Required PR Review Merge Scheduler` (~32 of a ~300-run sample), `Python Security` (~29), `CodeQL PR` (~25), `Security Scan` (~23), `SAST Semgrep` (~20), and `Agent Review Runtime Quality CI` (~16) -- and four of those six (`pr-review-merge-scheduler.yml`, `security-scan.yml`, `sast-semgrep.yml`, `agent-review-runtime-quality-ci.yml`) were *already* pinned to `ubuntu-24.04` before this pass, per their own existing contract tests, and equally stuck. GitHub's own status page showed no -active incident at the time. The 5-6-vs-60 in-progress gap therefore remains unexplained -- not resolved -by this fix, not attributable to a known starved image, and not (per prior explicit ruling; see +active incident at the time. These observations do not establish a 5-6-vs-60 capacity gap: +a workflow run can contain multiple matrix jobs, and this repository-only sample excludes +other organization repositories. The congestion remains unresolved by this fix, is not +attributable to a known starved image, and is not (per prior explicit ruling; see `project_actions_plan_concurrency_ceiling.md`) a case for proposing paid additional capacity. Flagging for whoever investigates next: check org-level Actions settings (a policy-level concurrent-job cap below 60), a spending/usage limit (though billing access was unavailable to verify), or a GitHub-side runner -provisioning degradation not severe enough to reach the public status page. +provisioning degradation not severe enough to reach the public status page. First count actual +executing jobs across the organization at a recorded timestamp and distinguish runner admission +wait from execution duration; do not infer either a lower cap or spare capacity from run counts. + +**Measurement correction, 2026-09-08:** The historical 307 queued / 5-6 in-progress observations +above are retained as run-level evidence, not job-ceiling utilization. Current Naruon head +`64bf6c766e315b86eaa180fbd1a82f9087202e66` also exposed two dynamic Code Quality jobs and three +dynamic default-CodeQL jobs alongside the required central CodeQL lane. This is additional +fan-out, not proof of redundant coverage: default setup selects extended queries while the +inspected central source preserves SARIF as artifacts with `upload: false`. Coverage and target +publication equivalence must precede consolidation; see the verified +[queue finding](https://github.com/ContextualWisdomLab/.github/issues/712#issuecomment-5579207955) +and [coverage comparison](https://github.com/ContextualWisdomLab/.github/issues/712#issuecomment-5579235044). **Separately found while validating this fix, not yet fixed:** `tests/test_pr_review_autofix_nvidia_nim_contract.py::test_review_fix_caller_runs_once_each_hour` fails on a clean `origin/main` checkout, independent of this fix — `hourly-review-repair.yml` was renamed to From d66f1d5a1388b6acdcbb72f15d93a4705f1da2a8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 14:19:23 +0900 Subject: [PATCH 15/15] test(codeql): execute coordinator identity contract --- .github/workflows/codeql-scan-dispatch.yml | 1 - tests/test_codeql_pr_workflow_contract.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/codeql-scan-dispatch.yml b/.github/workflows/codeql-scan-dispatch.yml index aa4b85c8e3..03c340bac5 100644 --- a/.github/workflows/codeql-scan-dispatch.yml +++ b/.github/workflows/codeql-scan-dispatch.yml @@ -593,4 +593,3 @@ jobs: gh api -X POST "repos/${TARGET_REPOSITORY}/actions/runs/${REQUIRED_RUN_ID}/rerun-failed-jobs" >/dev/null echo "Re-ran verified failed CodeQL jobs once for run ${REQUIRED_RUN_ID} on ${HEAD_SHA}." - diff --git a/tests/test_codeql_pr_workflow_contract.py b/tests/test_codeql_pr_workflow_contract.py index 4949ec7d6d..cfa1d3837b 100644 --- a/tests/test_codeql_pr_workflow_contract.py +++ b/tests/test_codeql_pr_workflow_contract.py @@ -504,7 +504,7 @@ def test_codeql_pr_fallback_binds_live_base_and_required_run_identity() -> None: def test_codeql_coordinator_fallback_binds_live_base_and_required_run_identity() -> None: """Coordinator lookup must use the exact dispatch run identity from #2028.""" workflow = WORKFLOW_PATH.read_text(encoding="utf-8") - coordinator = workflow.split(" dispatch-current-head:\\n", 1)[1] + coordinator = workflow.split(" dispatch-current-head:\n", 1)[1] assert ( 'expected_title="CodeQL Scan Dispatch ${TARGET_REPOSITORY}#${PR_NUMBER}'