Skip to content

Use per-language CodeQL bundles - #4146

Open
henrymercer wants to merge 14 commits into
henrymercer/bundle-resolution-prfrom
henrymercer/per-language-bundles-pr
Open

henrymercer wants to merge 14 commits into
henrymercer/bundle-resolution-prfrom
henrymercer/per-language-bundles-pr

Conversation

@henrymercer

Copy link
Copy Markdown
Contributor

Download self-contained per-language bundles for eligible analyses on GitHub.com. Selection requires one explicitly configured language, a GitHub-hosted runner, zstd, and a supported language/platform combination. Release bundles require CodeQL 2.27.1 or newer; nightlies use the same eligibility rules without the release-version check. The per_language_bundles feature flag remains off by default.

Extract per-language bundles into the runner's temporary directory rather than the toolcache. If an automatically selected asset returns 404, fall back to the combined bundle from the same release. Explicit per-language tools URLs are not substituted.

Add bundle-language and fallback telemetry, plus generated nightly PR checks for all ten languages covering extractor contents, toolcache isolation, database creation and analysis. Update existing cache-dependent checks to request multiple languages so they continue to use the combined bundle.

Risk assessment

Low risk: Automatic per-language bundle selection is feature-flagged and off by default. The unflagged behavior is limited to rare, explicitly supplied per-language bundle URLs and prevents those partial installs from entering the toolcache.

Which use cases does this change impact?

Workflow types:

  • Advanced setup
  • Managed

Products:

  • Code Scanning
  • Code Quality
  • Other first-party - Analyses using the shared CodeQL setup path.

Environments:

  • Dotcom - Automatic per-language selection on GitHub.com; explicit bundle URL handling also applies to data-residency environments.
  • GHES - Explicit per-language bundle URL handling only. Automatic selection remains disabled.

How did/will you validate this change?

  • Unit tests - Eligibility, bundle selection, version reporting, fallback, and toolcache isolation.
  • End-to-end tests - Generated nightly PR checks cover all ten languages. These still need to run for this PR.

If something goes wrong after this change is released, what are the mitigation and rollback strategies?

  • Feature flags - Disable automatic selection with per_language_bundles.
  • Rollback - Release a fix or revert if explicit bundle URL handling regresses; that path is not controlled by the flag.

How will you know if something goes wrong after this change is released?

  • Telemetry - Monitor per-language downloads, observed combined-bundle fallbacks, download timings and initialization failures through the rollout dashboard.

Are there any special considerations for merging or releasing this change?

  • Special considerations - Merge the bundle-resolution prerequisite, #4145, first. Keep the feature flag disabled until the end-to-end checks pass, matching assets are available, and telemetry consumer support is deployed.

Merge / deployment checklist

  • Confirm this change is backwards compatible with existing workflows.
  • Consider adding a changelog entry for this change.
  • Confirm the readme and docs have been updated if necessary.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@henrymercer
henrymercer requested a balanced review from Copilot September 15, 2026 17:18
@github-actions github-actions Bot added the size/XXL May be extremely hard to review label Sep 15, 2026
@henrymercer
henrymercer added this pull request to stack #4147 September 15, 2026 17:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

  • Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.

Copilot review overview

🟡 Changes recommended

Fallback telemetry underreports total setup duration by excluding the failed per-language request.

Get a fresh assessment by requesting another Copilot review.

Review tier: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Adds feature-gated per-language CodeQL bundle selection, extraction, fallback, telemetry, and validation.

Changes:

  • Selects eligible release/nightly per-language bundles with 404 fallback.
  • Keeps partial bundles out of the toolcache and reports telemetry.
  • Adds unit and generated end-to-end validation across ten languages.
File Description
src/​per-language-bundles.ts Implements bundle eligibility and URL recognition.
src/​per-language-bundles.test.ts Tests eligibility and recognition.
src/​setup-codeql.ts Resolves, downloads, caches, and falls back between bundles.
src/​setup-codeql.test.ts Tests selection, fallback, versions, and caching.
src/​tools-download.ts Extends download telemetry.
src/​status-report.ts Defines telemetry fields.
src/​init-action.ts Reports init telemetry.
src/​setup-codeql-action.ts Reports setup telemetry.
src/​feature-flags.ts Adds the disabled-by-default feature flag.
pr-checks/​sync.ts Supports custom generated matrices.
pr-checks/​checks/​per-language-bundle-validation.yml Validates all supported language bundles.
pr-checks/​checks/​bundle-toolcache.yml Preserves combined-bundle cache testing.
.github/​workflows/​codescanning-config-cli.yml Ensures reusable combined-bundle caching.
lib/​entry-points.js Generated artifact; content excluded.
.github/​workflows/​__bundle-toolcache.yml Generated workflow; content excluded.
.github/​workflows/​__per-language-bundle-validation.yml Generated workflow; content excluded.
Files excluded by content exclusion policy (3)
  • .github/workflows/__bundle-toolcache.yml
  • .github/workflows/__per-language-bundle-validation.yml
  • lib/entry-points.js

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/setup-codeql.ts
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@henrymercer
henrymercer marked this pull request as ready for review September 15, 2026 18:57
@henrymercer
henrymercer requested a review from a team as a code owner September 15, 2026 18:57

@mbg mbg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have had a look through the changes here and added quite a few detailed comments. Additionally, some high-level ones:

  • Like with the other PR this is stacked on, this was a lot more difficult (and time-consuming) to review than it needed to be, because all of the changes were contained in one commit (except the follow-up to Copilot's review comment). There were clear opportunities for breaking the changes up and structuring them to be more incremental.
  • Quite a few of the function-level comments and test names make sense in the context of this PR where it's obvious that they relate to per-language vs combined bundles, but wouldn't make sense outside of this context. Some of my review comments highlight specific cases of this, but I didn't comment on all of them.
  • Try and remember to make use of the new ActionState infrastructure whenever possible in new or changed code. In particular, the getPerLanguageBundleLanguage function would benefit from this since the tests would not need to be serial.

Comment thread src/status-report.ts Outdated
Comment on lines +648 to +654
/** The language of the single-language bundle that was downloaded, if any. */
tools_bundle_language?: string;
/**
* Whether we tried to download a single-language bundle, but it did not exist and we fell back to
* the combined bundle.
*/
tools_per_language_bundle_fallback?: boolean;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Consider whether it's worth creating an interface for this that can be used in ToolsDownloadStatusReport as well as here to avoid copying the differently named fields in src/init-action.ts and src/setup-codeql-action.ts.

Comment thread src/per-language-bundles.ts Outdated
Comment on lines +65 to +66
features: FeatureEnablement,
logger: Logger,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider replacing this with ActionState<["Logger", "FeatureFlags"]> and making it the first parameter.

Comment thread src/per-language-bundles.ts Outdated

if (compressionMethod !== "zstd") {
// Per-language bundles are only published as zstd archives.
return explain(`the bundle would be downloaded as ${compressionMethod}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return explain(`the bundle would be downloaded as ${compressionMethod}`);
return explain(`the bundle would be downloaded as '${compressionMethod}'`);

Comment thread src/per-language-bundles.ts Outdated
Comment on lines +106 to +110
if (!isGitHubHostedRunner()) {
// Per-language installs stay out of the toolcache; self-hosted runners should retain
// the reusable combined bundle instead.
return explain("the job is not running on a GitHub-hosted runner");
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if this is the correct choice. Self-hosted runners could use custom images / persistent storage to pre-install the desired CLI bundle in the toolcache. If that's not available, wouldn't it be more desirable to download the language-specific bundle for improved performance like on GH-hosted runners?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-hosted runner can indeed preinstall CodeQL, but I don't think we should assume it. I would prefer to have a better way to determine whether the toolcache is persistent or not before rolling out to self-hosted runners to avoid a situation where we install many per-language bundles and use an unnecessary amount of disk space.

Comment thread src/per-language-bundles.ts Outdated
if (!semver.gte(cliVersion, MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION)) {
return explain(
`CodeQL ${cliVersion} is older than ${MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION}, which is the ` +
"first version that publishes per-language bundles",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: The version itself doesn't publish anything:

Suggested change
"first version that publishes per-language bundles",
"first version for which per-language bundles are published",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated as suggested.

Comment thread src/setup-codeql.test.ts Outdated
Comment on lines +67 to +74
const client = github.getOctokit("123", {
request: {
fetch: async () =>
new Response(JSON.stringify([{ tag_name: tagName }]), {
headers: { "content-type": "application/json" },
}),
},
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is less precise than the stubbing that stubHostedNightly replaces. Consider whether to stub sinon.stub(client.rest.repos, "listReleases"); instead of request.

Comment thread src/setup-codeql.test.ts Outdated
}

test.serial(
"getCodeQLSource downloads the combined nightly bundle when not eligible",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clear in the context of the PR, but not in general.

Suggested change
"getCodeQLSource downloads the combined nightly bundle when not eligible",
"getCodeQLSource downloads a combined nightly bundle when per-language bundle is not eligible"

Comment thread src/setup-codeql.test.ts
Comment on lines +647 to +651
{ languages: ["java"], features: createFeatures([]) },
{
languages: ["java", "python"],
features: createFeatures([Feature.PerLanguageBundles]),
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Add comments to the cases to state why they are not eligible.

Comment thread src/setup-codeql.test.ts Outdated
],
};

test.serial("getCodeQLBundleName names the per-language bundle", (t) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering the actual checks in this test:

Suggested change
test.serial("getCodeQLBundleName names the per-language bundle", (t) => {
test.serial("getCodeQLBundleName returns per-language bundle when a language is specified", (t) => {

Comment thread src/setup-codeql.ts Outdated
...result,
statusReport: {
...result.statusReport,
totalDurationMs: Math.round(performance.now() - startTime),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two thoughts here:

  1. Minor: It would be nice to avoid a potential divergence between how this is calculated (i.e. Math.rounded) in downloadAndExtract and here by having a small wrapper function somewhere for function durationMsSince(startTime) { return Math.round(performance.now() - startTime) } and use that consistently.
  2. I don't love that this (potentially) covers a bunch more code for the fallback case than for the non-fallback case. I am not sure that there is anything critical right now that would be included in this figure that shouldn't be, but perhaps it would be nice to guard against it if you can think of a not too complex way of getting the total duration from the first attempt at downloadCodeQL in the error case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Good idea, done.
  2. I had a look, but I think it's probably more complexity than it's worth.

henrymercer and others added 11 commits September 16, 2026 12:16
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@henrymercer

Copy link
Copy Markdown
Contributor Author

Lots of good ideas, thank you for the detailed review!

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XXL May be extremely hard to review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants