Add github.code_scanning for CodeQL default setup - #122
Conversation
Allow projects to self-service enable GitHub code scanning (CodeQL default setup) from .asf.yaml, as requested in apache#94: github: code_scanning: true or, for more control: github: code_scanning: query_suite: extended threat_model: remote languages: - java-kotlin The directive reads the current default setup configuration first and only issues a PATCH when the desired settings differ, avoiding redundant analysis runs. Removing the section disables the setup only if it was previously managed via .asf.yaml, following the semantics established in apache#121. Language identifiers are deliberately not validated client-side, as GitHub extends the CodeQL language set over time. Assisted-By: Claude Fable 5 <noreply@anthropic.com>
| if enabled: | ||
| desired: dict[str, Any] = { | ||
| "state": "configured", | ||
| "query_suite": settings.get("query_suite", "default"), |
There was a problem hiding this comment.
query_suite is unconditionally included in the PATCH payload, unlike threat_model and languages below, which are only sent when explicitly configured.
Consider a repo where an admin already enabled CodeQL default setup through the UI with the extended suite, and the project now adds code_scanning: true to bring it under .asf.yaml management. _matches_current compares query_suite: extended (current) against default (desired), doesn't match, and the directive PATCHes the suite down to default — silently downgrading the analysis and re-triggering a full scan. The same happens with the map form whenever query_suite is omitted, since strictyaml injects the default default.
Suggest treating it like the other two — only send it when the user actually set it — or documenting in the README that enabling via .asf.yaml resets the suite.
| "state": "configured", | ||
| "query_suite": settings.get("query_suite", "default"), | ||
| } | ||
| # Fields not specified in .asf.yaml are left for GitHub to manage (auto-detection). |
There was a problem hiding this comment.
Because absent fields are omitted from the payload and skipped by _matches_current, settings are one-way sticky: they can be set and changed, but never reverted.
The README says of languages: "if omitted, GitHub auto-detects eligible languages" (README.md:837). But a project that configures languages: [python] and later deletes the key expecting auto-detection to resume gets no PATCH at all — analysis stays pinned to python indefinitely, with no error. Same for dropping threat_model, and for downgrading from the map form back to code_scanning: true.
Either send an explicit reset when a previously-managed field disappears, or adjust the README wording to say the values stick until changed.
| return | ||
| # Section removed after having been managed by .asf.yaml: disable the setup. | ||
| enabled = False | ||
| elif isinstance(scanning, bool): |
There was a problem hiding this comment.
code_scanning: false will disable a default setup that .asf.yaml never managed. The sibling directives guard exactly this case — pr_creation_cap.py:81 and copilot_code_review.py:78 both return early on not enabled and not was_previously_configured.
The PR description says UI-enabled setups are left untouched, but that only holds for the section-removal path above; it doesn't hold for an explicit false. A project copying an .asf.yaml template containing code_scanning: false into a repo whose scanning an admin turned on in the UI would have it switched off on the next push.
Also worth noting: code_scanning: false isn't documented in the README, so the behaviour is invisible to users either way.
|
|
||
| def update_default_setup(self: ASFGitHubFeature, payload: dict[str, Any]) -> None: | ||
| status, _headers, body = self.ghrepo._requester.requestJson("PATCH", _default_setup_endpoint(self), input=payload) | ||
| if status in (200, 202): |
There was a problem hiding this comment.
status in (200, 202) rejects any other 2xx; pr_creation_cap.py:45 uses 200 <= status < 300 for the equivalent call.
If GitHub answers a no-op PATCH with 204 (or adds another 2xx code later), the update actually succeeded but the committer gets an "Unexpected response while updating code scanning default setup: HTTP 204" error email — and since the exception aborts the github feature before the settings cache is written, the same push path will error again next time rather than settling.
| status, _headers, body = self.ghrepo._requester.requestJson("GET", _default_setup_endpoint(self)) | ||
| repo = f"{self.repository.org_id}/{self.repository.name}" | ||
| if status == 200: | ||
| payload = json.loads(body) |
There was a problem hiding this comment.
This json.loads is unguarded, while every other body parse in the module goes through _parse_detail's try/except.
A 200 with a non-JSON body (proxy interposing an HTML error page, an empty body during a GitHub incident) surfaces a raw json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) to the committer instead of one of the deliberately helpful messages the rest of this module produces.
Allows projects to self-service enable GitHub code scanning (CodeQL default setup) from
.asf.yaml, as requested in #94 (code scanning part only; secret scanning can be a follow-up):or, for more control:
Design notes:
enabledkey), matching howrulesetsworks; a plain boolean is accepted for the simple case./repos/{org}/{repo}/code-scanning/default-setupfirst and only PATCHes when the desired settings differ, so unrelated.asf.yamledits do not re-trigger analysis runs, and both 200 and 202 (async validation run) are treated as success..asf.yaml(sameprevious_yamlsemantics as Clear branch protection rules when protected_branches is removed #121); a setup enabled manually through the GitHub UI is left untouched.languagesvalues are deliberately not validated client-side, since GitHub extends the CodeQL language set over time; invalid values surface as a GitHub 422 relayed to the committer by email, as are the 403/409 cases (archived repo, setup change in progress, Actions disabled).github_copilot_code_review.pyfake-requester pattern; README documents the new section.Fixes #94
🤖 Generated with Claude Code