Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/scripts/workflow-control-plane-contract.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,36 @@ export function assertControlPlaneContract() {
/any\(\.\[\]; \.headRefOid == \$sha\)/u,
"an open PR owns one analysis instead of duplicating its branch push",
);
// The scope pre-flight samples ownership seconds after the push, so a branch
// pushed first and adopted by a PR moments later still reaches the analyzer
// believing it owns analysis. The resulting refs/heads analysis at a live PR
// head makes GHAS open that PR's CodeQL check against the branch snapshot and
// close it `timed_out` with only one of the two configurations present.
assert.match(
codeql,
/- name: Initialize CodeQL[\s\S]*?- name: Confirm this push still owns the analysis[\s\S]*?- name: Analyze the triggering revision/u,
"the ownership re-check sits after database init, so it absorbs the whole init window before the upload",
);
assert.match(
codeql,
/- name: Confirm this push still owns the analysis\n\s+id: ownership\n\s+if: github\.event_name == 'push' && needs\.scope\.outputs\.analysis_ref == ''/u,
"only a branch-ref push re-checks ownership; PR and centrally dispatched runs are untouched",
);
assert.match(
codeql,
/- name: Analyze the triggering revision\n\s+if: needs\.scope\.outputs\.analysis_ref == '' && steps\.ownership\.outputs\.upload != 'false'/u,
"the re-check suppresses only an adopted branch upload, and its skipped empty output still analyzes every other event",
);
assert.match(
codeql,
/if open_prs="\$\(gh pr list[\s\S]*?\)"; then[\s\S]*?\n else\n\s+echo "::warning::Could not re-confirm PR ownership/u,
"a transient ownership lookup keeps the prepared analysis instead of failing the CodeQL check it exists to protect",
);
assert.match(
codeql,
/^ actions: read\n contents: read\n packages: read\n pull-requests: read\n security-events: write$/mu,
"the analyzer reads PR ownership without gaining any write beyond its security-events upload",
);
assert.match(codeql, /ADVANCED_ENABLED: \$\{\{ vars\.CODEQL_ADVANCED_ENABLED \}\}/u);
assert.match(
codeql,
Expand Down
41 changes: 40 additions & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ jobs:
actions: read
contents: read
packages: read
pull-requests: read
security-events: write
strategy:
fail-fast: false
Expand All @@ -244,8 +245,46 @@ jobs:
with:
languages: ${{ matrix.language }}

# The scope pre-flight samples `gh pr list` within seconds of the push,
# so a branch that is pushed FIRST and adopted by a PR moments later
# still reaches this job believing it owns analysis. Uploading a
# refs/heads analysis for a commit that is now an open PR head makes
# GitHub Advanced Security open that PR's CodeQL check against the
# branch snapshot and close it `timed_out` before the PR's own merge-ref
# analyses land. Re-check ownership at the last moment before upload.
- name: Confirm this push still owns the analysis
id: ownership
if: github.event_name == 'push' && needs.scope.outputs.analysis_ref == ''
shell: bash
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail

upload=true
# Every push to every branch reaches this step, so a rate-limited or
# transient lookup must not fail the job. Failing here would produce
# exactly the red CodeQL check this step exists to prevent, and would
# additionally discard a database that is already built. Preserve
# `gh`'s exit status the way the scope pre-flight does for its
# optional lookups — never `|| true` — and fall back to the prior
# behaviour of analyzing.
if open_prs="$(gh pr list --repo "$REPO" --state open \
--head "$GITHUB_REF_NAME" --limit 100 \
--json headRefOid)"; then
if jq -e --arg sha "$GITHUB_SHA" \
'any(.[]; .headRefOid == $sha)' <<<"$open_prs" >/dev/null; then
upload=false
echo "::notice::An open PR adopted $GITHUB_REF_NAME@$GITHUB_SHA after this push started; its pull_request run is the single CodeQL analysis owner."
fi
else
echo "::warning::Could not re-confirm PR ownership of $GITHUB_REF_NAME@$GITHUB_SHA; analyzing this push rather than leaving the commit unscanned."
fi
echo "upload=$upload" >>"$GITHUB_OUTPUT"

- name: Analyze the triggering revision
if: needs.scope.outputs.analysis_ref == ''
if: needs.scope.outputs.analysis_ref == '' && steps.ownership.outputs.upload != 'false'
uses: github/codeql-action/analyze@4c0873ef8656cb3c50b3f42fb63bc1ade0cfa827 # v4
with:
category: /language:${{ matrix.language }}
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,18 @@ every entry is attributed the same way the app changelog attributes them.
and the control-plane contract inventories all active AI workflow/action
YAML while rejecting legacy or hardcoded model selections. — Codex (AI),
2026-08-10
- **A branch pushed just before its PR opens no longer times out that PR's
CodeQL check**: the analyzer's scope pre-flight samples PR ownership seconds
after the push, so a branch adopted by a PR moments later still reached the
analyze job believing it owned analysis. The resulting `refs/heads` analysis
at a live PR head made GitHub Advanced Security open that PR's check against
the branch snapshot and close it `timed_out` with only one of the two
language configurations uploaded — the recurring "1 configuration not found"
symptom. The analyze job now re-confirms ownership after database init and
before upload, ceding to the PR's own run; a transient lookup failure keeps
the prepared analysis rather than failing the check it protects. Only the
branch-ref push path is affected, so `pull_request`, scheduled, and centrally
dispatched merge-ref analyses are unchanged. — Lopu (AI), 2026-08-27

### Added

Expand Down
Loading