diff --git a/.github/workflows/__unsupported-macos.yml b/.github/workflows/__unsupported-macos.yml index 5e388d1ec..66bdb69cd 100644 --- a/.github/workflows/__unsupported-macos.yml +++ b/.github/workflows/__unsupported-macos.yml @@ -114,7 +114,7 @@ jobs: - name: Install dependencies run: npm ci - name: Download workflow artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: pattern: test-mode-sarif-* path: sarif diff --git a/CHANGELOG.md b/CHANGELOG.md index 950e8ffbe..2aa6b37e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,14 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th ## [UNRELEASED] +No user facing changes. + +## 4.39.2 - 03 Nov 2026 + - Update default CodeQL bundle version to [2.27.4](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.4). [#4284](https://github.com/github/codeql-action/pull/4284) +- _Upcoming breaking change_: CodeQL version 2.22.3 and earlier were discontinued on 16 October 2026 alongside GitHub Enterprise Server 3.18, and will be unsupported by the next minor release of the CodeQL Action. Added a deprecation warning for customers using these versions of CodeQL. [#4266](https://github.com/github/codeql-action/pull/4266) +- Starting with macOS 27 and Xcode 27, traced analyses are unsupported. See [this blog post for more information](https://github.blog/changelog/2026-10-19-codeql-changes-for-macos-27-xcode-27-and-swift-6-4/). The CodeQL Action will now try to identify if a workflow is about to perform a traced analysis on version 27 or above of macOS or Xcode. If so, the CodeQL Action will emit a corresponding warning that will also be visible on the [Tool Status Page](https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration/use-the-tools-status-page-for-code-scanning). This check will be rolled out following this release of the CodeQL Action. [#4221](https://github.com/github/codeql-action/pull/4221) +- The undocumented TRAP cache cleanup feature that could be enabled using the `CODEQL_ACTION_CLEANUP_TRAP_CACHES` environment variable has been removed, as announced in the release notes for CodeQL Action 4.35.2. The `analyze` Action now warns if this environment variable is set. If you used TRAP cache cleanup to limit Actions cache usage, we recommend disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action. [#4243](https://github.com/github/codeql-action/pull/4243) ## 4.39.1 - 21 Oct 2026 diff --git a/lib/entry-points.js b/lib/entry-points.js index 533b7c381..e0c268fa3 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -177993,7 +177993,7 @@ function getDiffRangesJsonFilePath(env = getEnv()) { return path2.join(getTemporaryDirectory(env), PR_DIFF_RANGE_JSON_FILENAME); } function getActionVersion() { - return "4.39.2"; + return "4.39.3"; } function getWorkflowEventName(env = getEnv()) { return env.getRequired("GITHUB_EVENT_NAME" /* GITHUB_EVENT_NAME */); diff --git a/package-lock.json b/package-lock.json index c1a68345a..a33fe2871 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codeql", - "version": "4.39.2", + "version": "4.39.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codeql", - "version": "4.39.2", + "version": "4.39.3", "license": "MIT", "workspaces": [ "pr-checks" diff --git a/package.json b/package.json index 73d61b35d..93429872c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeql", - "version": "4.39.2", + "version": "4.39.3", "private": true, "description": "CodeQL action", "scripts": { diff --git a/pr-checks/checks/unsupported-macos.yml b/pr-checks/checks/unsupported-macos.yml index 5881b174a..f5388f1d6 100644 --- a/pr-checks/checks/unsupported-macos.yml +++ b/pr-checks/checks/unsupported-macos.yml @@ -48,7 +48,7 @@ validationJobs: installNode: true steps: - name: Download workflow artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: pattern: test-mode-sarif-* path: sarif diff --git a/pr-checks/sync-back.test.ts b/pr-checks/sync-back.test.ts index 52d4c98f3..142487449 100755 --- a/pr-checks/sync-back.test.ts +++ b/pr-checks/sync-back.test.ts @@ -56,9 +56,18 @@ jobs: const result = scanGeneratedWorkflows(workflowDir); - assert.equal(result["actions/checkout"], "v4"); - assert.equal(result["actions/setup-node"], "v5"); - assert.equal(result["actions/setup-go"], "v6"); + assert.deepEqual( + result.actionVersions["actions/checkout"], + new Set(["v4"]), + ); + assert.deepEqual( + result.actionVersions["actions/setup-node"], + new Set(["v5"]), + ); + assert.deepEqual( + result.actionVersions["actions/setup-go"], + new Set(["v6"]), + ); }); await it("scanning workflows with version comments", () => { @@ -78,12 +87,18 @@ jobs: const result = scanGeneratedWorkflows(workflowDir); - assert.equal(result["actions/checkout"], "v4"); - assert.equal( - result["ruby/setup-ruby"], - "44511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0", + assert.deepEqual( + result.actionVersions["actions/checkout"], + new Set(["v4"]), + ); + assert.deepEqual( + result.actionVersions["ruby/setup-ruby"], + new Set(["44511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0"]), + ); + assert.deepEqual( + result.actionVersions["actions/setup-python"], + new Set(["v6 # Latest Python"]), ); - assert.equal(result["actions/setup-python"], "v6 # Latest Python"); }); await it("ignores local actions", () => { @@ -103,9 +118,15 @@ jobs: const result = scanGeneratedWorkflows(workflowDir); - assert.equal(result["actions/checkout"], "v4"); - assert.equal("./.github/actions/local-action" in result, false); - assert.equal("./another-local-action" in result, false); + assert.deepEqual( + result.actionVersions["actions/checkout"], + new Set(["v4"]), + ); + assert.equal( + "./.github/actions/local-action" in result.actionVersions, + false, + ); + assert.equal("./another-local-action" in result.actionVersions, false); }); }); @@ -128,8 +149,8 @@ const steps = [ fs.writeFileSync(syncTsPath, syncTsContent); const actionVersions = { - "actions/setup-node": "v5", - "actions/setup-go": "v6", + "actions/setup-node": new Set(["v5"]), + "actions/setup-go": new Set(["v6"]), }; const result = updateSyncTs(syncTsPath, actionVersions); @@ -155,7 +176,7 @@ const steps = [ fs.writeFileSync(syncTsPath, syncTsContent); const actionVersions = { - "actions/setup-node": "v5 # Latest version", + "actions/setup-node": new Set(["v5 # Latest version"]), }; const result = updateSyncTs(syncTsPath, actionVersions); @@ -182,7 +203,7 @@ const steps = [ fs.writeFileSync(syncTsPath, syncTsContent); const actionVersions = { - "actions/setup-node": "v5", + "actions/setup-node": new Set(["v5"]), }; const result = updateSyncTs(syncTsPath, actionVersions); @@ -206,7 +227,9 @@ const steps = [ fs.writeFileSync(syncTsPath, syncTsContent); const actionVersions = { - "actions/setup-node": "48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0", + "actions/setup-node": new Set([ + "48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0", + ]), }; const result = updateSyncTs(syncTsPath, actionVersions); @@ -244,8 +267,9 @@ steps: "actions/checkout": "v4", "actions/setup-node": "v5 # Latest", }; + const byWorkflow = { ["test.yml"]: actionVersions }; - const result = updateTemplateFiles(checksDir, actionVersions); + const result = updateTemplateFiles(checksDir, byWorkflow); assert.equal(result.length, 1); assert.ok(result.includes(templatePath)); @@ -269,8 +293,9 @@ steps: const actionVersions = { "ruby/setup-ruby": "55511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0", }; + const byWorkflow = { ["test.yml"]: actionVersions }; - const result = updateTemplateFiles(checksDir, actionVersions); + const result = updateTemplateFiles(checksDir, byWorkflow); assert.equal(result.length, 1); const updatedContent = fs.readFileSync(templatePath, "utf8"); diff --git a/pr-checks/sync-back.ts b/pr-checks/sync-back.ts index 233621ea7..702886467 100755 --- a/pr-checks/sync-back.ts +++ b/pr-checks/sync-back.ts @@ -27,16 +27,39 @@ const CHECKS_DIR = path.join(PR_CHECKS_DIR, "checks"); const WORKFLOW_DIR = path.join(REPO_ROOT, ".github", "workflows"); const SYNC_TS_PATH = path.join(PR_CHECKS_DIR, "sync.ts"); +/** Type alias for action names. */ +type ActionName = string; + +/** Type alias for action versions. May include a comment. */ +type ActionVersion = string; + +/** Mappings of {@link ActionName}s to {@link ActionVersion}s. */ +type ActionVersions = Record; + +/** Mappings of workflow names to {@link ActionVersions}. */ +type ActionVersionsByWorkflow = Record; + +/** The result of {@link scanGeneratedWorkflows}. */ +type ScanWorkflowResult = { + /** A mapping of {@link ActionName}s to all encountered {@link ActionVersion}s. */ + actionVersions: Record>; + + /** A mapping of workflow names to {@link ActionVersions}. */ + actionVersionsByWorkflow: ActionVersionsByWorkflow; +}; + /** * Scan generated workflow files to extract the latest action versions. * * @param workflowDir - Path to .github/workflows directory - * @returns Map from action names to their latest versions (including comments) + * + * @returns A {@link ScanWorkflowResult} object containing information about the extracted action versions. */ export function scanGeneratedWorkflows( workflowDir: string, -): Record { - const actionVersions: Record = {}; +): ScanWorkflowResult { + const actionVersionsByWorkflow: ActionVersionsByWorkflow = {}; + const actionVersions: Record> = {}; const generatedFiles = fs .readdirSync(workflowDir) @@ -46,6 +69,10 @@ export function scanGeneratedWorkflows( for (const filePath of generatedFiles) { const content = fs.readFileSync(filePath, "utf8"); + // Get the basename of the workflow and drop the leading `__` from it. + const workflowName = path.basename(filePath).slice(2); + actionVersionsByWorkflow[workflowName] = {}; + // Find all action uses in the file, including potential comments // This pattern captures: action_name@version_with_possible_comment const pattern = /uses:\s+([^/\s]+\/[^@\s]+)@([^@\n]+)/g; @@ -57,13 +84,26 @@ export function scanGeneratedWorkflows( // Only track non-local actions (those with / but not starting with ./) if (!actionName.startsWith("./")) { - // Assume that version numbers are consistent (this should be the case on a Dependabot update PR) - actionVersions[actionName] = versionWithComment; + // Record the version of the action for this workflow. + actionVersionsByWorkflow[workflowName][actionName] = versionWithComment; + + // Record this version for the action so that we can write it to `sync.ts` if needed. + // Usually we'd expect there to be exactly one version per action across all workflows, + // but sometimes these can go out-of-sync if e.g. a workflow is added in a branch and a + // Dependabot PR is merged in the meantime that changes the version of an action that is + // used in the new workflow. Different versions of the same action across different + // workflows is not fundamentally a problem, but we may need to pick one for `sync.ts` + // so we warn there if we have multiple versions of an action. + if (actionVersions[actionName] === undefined) { + actionVersions[actionName] = new Set([versionWithComment]); + } else if (!actionVersions[actionName].has(versionWithComment)) { + actionVersions[actionName].add(versionWithComment); + } } } } - return actionVersions; + return { actionVersions, actionVersionsByWorkflow }; } /** @@ -79,7 +119,7 @@ export function scanGeneratedWorkflows( */ export function updateSyncTs( syncTsPath: string, - actionVersions: Record, + actionVersions: Record>, ): boolean { if (!fs.existsSync(syncTsPath)) { throw new Error(`Could not find ${syncTsPath}`); @@ -89,9 +129,18 @@ export function updateSyncTs( const originalContent = content; // Update hardcoded action versions - for (const [actionName, versionWithComment] of Object.entries( + for (const [actionName, versionsWithComments] of Object.entries( actionVersions, )) { + // We usually expect `versionsWithComments` to contain one version. If there are multiple, + // we arbitrarily pick one and warn about it. + const versionsWithCommentsArray = Array.from(versionsWithComments); + const versionWithComment = versionsWithCommentsArray[0]; + if (versionsWithComments.size > 1) { + console.warn( + `Inconsistent versions for ${actionName}. Arbitrarily picking '${versionWithComment}'. All found: ${versionsWithCommentsArray.join(", ")}`, + ); + } // Split the scanned value into the ref (e.g. a commit SHA) and the optional // trailing version comment (e.g. `v6.0.3`). const ref = versionWithComment.includes("#") @@ -138,12 +187,12 @@ export function updateSyncTs( * Update action versions in template files in pr-checks/checks/ * * @param checksDir - Path to pr-checks/checks directory - * @param actionVersions - Map of action names to versions (may include comments) + * @param actionVersionsByWorkflow - Map of workflows to maps of action names to versions (may include comments) * @returns List of files that were modified */ export function updateTemplateFiles( checksDir: string, - actionVersions: Record, + actionVersionsByWorkflow: ActionVersionsByWorkflow, ): string[] { const modifiedFiles: string[] = []; @@ -156,6 +205,16 @@ export function updateTemplateFiles( let content = fs.readFileSync(filePath, "utf8"); const originalContent = content; + const workflowName = path.basename(filePath); + const actionVersions = actionVersionsByWorkflow[workflowName]; + + if (actionVersions === undefined) { + console.info( + `Found no action versions for workflow template '${filePath}'.`, + ); + continue; + } + // Update action versions for (const [actionName, versionWithComment] of Object.entries( actionVersions, @@ -191,12 +250,15 @@ function main(): number { const verbose = values.verbose ?? false; console.info("Scanning generated workflows for latest action versions..."); - const actionVersions = scanGeneratedWorkflows(WORKFLOW_DIR); + const { actionVersions, actionVersionsByWorkflow } = + scanGeneratedWorkflows(WORKFLOW_DIR); if (verbose) { console.info("Found action versions:"); - for (const [action, version] of Object.entries(actionVersions)) { - console.info(` ${action}@${version}`); + for (const [action, versions] of Object.entries(actionVersions)) { + for (const version of versions) { + console.info(` ${action}@${version}`); + } } } @@ -215,7 +277,10 @@ function main(): number { } // Update template files - const templateModified = updateTemplateFiles(CHECKS_DIR, actionVersions); + const templateModified = updateTemplateFiles( + CHECKS_DIR, + actionVersionsByWorkflow, + ); modifiedFiles.push(...templateModified); if (modifiedFiles.length > 0) { diff --git a/unreleased-change-notes/ghes-3-18-deprecation.md b/unreleased-change-notes/ghes-3-18-deprecation.md deleted file mode 100644 index 0e33d3932..000000000 --- a/unreleased-change-notes/ghes-3-18-deprecation.md +++ /dev/null @@ -1 +0,0 @@ -- _Upcoming breaking change_: CodeQL version 2.22.3 and earlier were discontinued on 16 October 2026 alongside GitHub Enterprise Server 3.18, and will be unsupported by the next minor release of the CodeQL Action. Added a deprecation warning for customers using these versions of CodeQL. [#4266](https://github.com/github/codeql-action/pull/4266) diff --git a/unreleased-change-notes/macos-xcode-deprecation.md b/unreleased-change-notes/macos-xcode-deprecation.md deleted file mode 100644 index ea7586d08..000000000 --- a/unreleased-change-notes/macos-xcode-deprecation.md +++ /dev/null @@ -1 +0,0 @@ -- Starting with macOS 27 and Xcode 27, traced analyses are unsupported. See [this blog post for more information](https://github.blog/changelog/2026-10-19-codeql-changes-for-macos-27-xcode-27-and-swift-6-4/). The CodeQL Action will now try to identify if a workflow is about to perform a traced analysis on version 27 or above of macOS or Xcode. If so, the CodeQL Action will emit a corresponding warning that will also be visible on the [Tool Status Page](https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration/use-the-tools-status-page-for-code-scanning). This check will be rolled out following this release of the CodeQL Action. [#4221](https://github.com/github/codeql-action/pull/4221) diff --git a/unreleased-change-notes/remove-trap-cache-cleanup.md b/unreleased-change-notes/remove-trap-cache-cleanup.md deleted file mode 100644 index b3cb822b8..000000000 --- a/unreleased-change-notes/remove-trap-cache-cleanup.md +++ /dev/null @@ -1 +0,0 @@ -- The undocumented TRAP cache cleanup feature that could be enabled using the `CODEQL_ACTION_CLEANUP_TRAP_CACHES` environment variable has been removed, as announced in the release notes for CodeQL Action 4.35.2. The `analyze` Action now warns if this environment variable is set. If you used TRAP cache cleanup to limit Actions cache usage, we recommend disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action. [#4243](https://github.com/github/codeql-action/pull/4243)