From cf8f7b978792800ea9d527b0cfdef71cae962575 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 10:27:31 +0100 Subject: [PATCH 01/17] Add `ActionConfig` type --- src/config/action-config.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/config/action-config.ts b/src/config/action-config.ts index de6882e77e..fcb6a9482a 100644 --- a/src/config/action-config.ts +++ b/src/config/action-config.ts @@ -126,3 +126,19 @@ export interface Config { */ enableFileCoverageInformation: boolean; } + +/** Properties available in `Config`. */ +export type ActionConfigProperties = keyof Config; + +/** + * The CodeQL Action configuration state that is persisted + * between steps of the CodeQL Action in a CodeQL workflow. + * + * By default, this is equivalent to `Config`. By explicitly + * providing required property names for `T` this can be restricted + * to requiring only those. For example, in `ActionConfig<"codeqlCmd">` + * all properties except `codeqlCmd` are optional. + */ +export type ActionConfig< + T extends ActionConfigProperties = ActionConfigProperties, +> = Partial & Pick; From 309f6af89b6ee50bcf8876171b5afb2a329c3a32 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 11:47:59 +0100 Subject: [PATCH 02/17] Change `getGeneratedCodeScanningConfigPath` to not require full `Config` --- src/codeql.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/codeql.ts b/src/codeql.ts index 8913224fab..99ffd69512 100644 --- a/src/codeql.ts +++ b/src/codeql.ts @@ -15,6 +15,7 @@ import * as api from "./api-client"; import * as outputCache from "./cli/output-cache"; import type { VersionInfo } from "./cli/types"; import { CliError, wrapCliConfigurationError } from "./cli-errors"; +import type { ActionConfig } from "./config/action-config"; import { appendExtraQueryExclusions, type Config } from "./config-utils"; import { DocUrl } from "./doc-url"; import { EnvVar, getEnv, exportEnvVar } from "./environment"; @@ -1237,7 +1238,9 @@ export async function getTrapCachingExtractorConfigArgsForLang( * * This will not exist if the configuration is being parsed in the Action. */ -function getGeneratedCodeScanningConfigPath(config: Config): string { +function getGeneratedCodeScanningConfigPath( + config: ActionConfig<"tempDir">, +): string { return path.resolve(config.tempDir, "user-config.yaml"); } From 317f264e1f59264b7f18cfaa42f4642f755257cc Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 11:51:58 +0100 Subject: [PATCH 03/17] Change `diagnosticsExport` to not require full `Config` --- src/codeql.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codeql.ts b/src/codeql.ts index 99ffd69512..68273d27b5 100644 --- a/src/codeql.ts +++ b/src/codeql.ts @@ -198,7 +198,7 @@ export interface CodeQL { diagnosticsExport( sarifFile: string, automationDetailsId: string | undefined, - config: Config, + config: ActionConfig<"tempDir">, ): Promise; /** Get the location of an extractor for the specified language. */ resolveExtractor(language: Language): Promise; @@ -914,7 +914,7 @@ async function getCodeQLForCmd( async diagnosticsExport( sarifFile: string, automationDetailsId: string | undefined, - config: Config, + config: ActionConfig<"tempDir">, ): Promise { const args = [ "diagnostics", From 19445ddfed42397ecf3ccc1dca379e957cc7631f Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 10:33:15 +0100 Subject: [PATCH 04/17] Reduce `Config` requirements for `tryUploadSarifIfRunFailed` --- src/config-utils.ts | 14 ++++++++++---- src/init-action-post-helper.ts | 16 +++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/config-utils.ts b/src/config-utils.ts index f1ec30b2c8..ebfabad36e 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -21,7 +21,7 @@ import { import * as api from "./api-client"; import { getCachingKind } from "./caching-utils"; import { type CodeQL } from "./codeql"; -import { type Config } from "./config/action-config"; +import { type ActionConfig, type Config } from "./config/action-config"; import { calculateAugmentation, ExcludeQueryFilter, @@ -1618,21 +1618,27 @@ export function appendExtraQueryExclusions( /** * Returns `true` if Code Scanning analysis is enabled, or `false` if not. */ -export function isCodeScanningEnabled(config: Config): boolean { +export function isCodeScanningEnabled( + config: ActionConfig<"analysisKinds">, +): boolean { return config.analysisKinds.includes(AnalysisKind.CodeScanning); } /** * Returns `true` if Code Quality analysis is enabled, or `false` if not. */ -export function isCodeQualityEnabled(config: Config): boolean { +export function isCodeQualityEnabled( + config: ActionConfig<"analysisKinds">, +): boolean { return config.analysisKinds.includes(AnalysisKind.CodeQuality); } /** * Returns `true` if Code Scanning Risk Assessment analysis is enabled, or `false` if not. */ -export function isRiskAssessmentEnabled(config: Config): boolean { +export function isRiskAssessmentEnabled( + config: ActionConfig<"analysisKinds">, +): boolean { return config.analysisKinds.includes(AnalysisKind.RiskAssessment); } diff --git a/src/init-action-post-helper.ts b/src/init-action-post-helper.ts index 93ee391aaa..ce06e17191 100644 --- a/src/init-action-post-helper.ts +++ b/src/init-action-post-helper.ts @@ -7,6 +7,7 @@ import * as actionsUtil from "./actions-util"; import { CodeScanning, RiskAssessment } from "./analyses"; import { getApiClient, getGitHubVersion } from "./api-client"; import { CodeQL, getCodeQL } from "./codeql"; +import type { ActionConfig } from "./config/action-config"; import { Config, isCodeScanningEnabled, @@ -86,6 +87,11 @@ interface FailedSarifInfo { checkoutPath: string; } +/** The minimal Action configuration required to prepare and upload a failed SARIF. */ +export type FailedSarifConfig = ActionConfig< + "codeQLCmd" | "tempDir" | "analysisKinds" | "languages" +>; + /** * Tries to prepare a SARIF file that contains information about a failed analysis. * @@ -94,7 +100,7 @@ interface FailedSarifInfo { async function prepareFailedSarif( logger: Logger, features: FeatureEnablement, - config: Config, + config: FailedSarifConfig, ): Promise> { if (!config.codeQLCmd) { return new Failure({ @@ -160,7 +166,7 @@ async function prepareFailedSarif( async function generateFailedSarif( logger: Logger, features: FeatureEnablement, - config: Config, + config: ActionConfig<"codeQLCmd" | "tempDir">, category: string | undefined, checkoutPath: string, sarifFile?: string, @@ -192,7 +198,7 @@ async function generateFailedSarif( * category for the workflow. */ async function maybeUploadFailedSarif( - config: Config, + config: FailedSarifConfig, repositoryNwo: RepositoryNwo, features: FeatureEnablement, logger: Logger, @@ -227,7 +233,7 @@ async function maybeUploadFailedSarif( /** Uploads a failed SARIF file as workflow artifact, if it can be generated. */ async function maybeUploadFailedSarifArtifact( - config: Config, + config: FailedSarifConfig, features: FeatureEnablement, logger: Logger, ): Promise { @@ -267,7 +273,7 @@ async function maybeUploadFailedSarifArtifact( * @returns The results of uploading the SARIF file for the failure. */ export async function tryUploadSarifIfRunFailed( - config: Config, + config: FailedSarifConfig, repositoryNwo: RepositoryNwo, features: FeatureEnablement, logger: Logger, From 5136039854019b40fed0b0413e52b6dcba4271d5 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 11:01:31 +0100 Subject: [PATCH 05/17] Store partial configuration when `init` is aborted --- lib/entry-points.js | 54 ++++++++++++++--- src/config-utils.ts | 116 +++++++++++++++++++++++++++++++----- src/config/action-config.ts | 11 ++++ src/init-action.ts | 12 ++++ 4 files changed, 171 insertions(+), 22 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 21c0ccd15f..ea86db9891 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -175053,20 +175053,45 @@ function getLocalConfig(logger2, configFile, validateConfig) { function getPathToParsedConfigFile(tempDir) { return path11.join(tempDir, "config"); } -async function saveConfig(config, logger2) { +function getPathToFailedConfigFile(tempDir) { + return path11.join(tempDir, "failed-config"); +} +function saveConfigFile(logger2, configFilePath, config) { const configString = JSON.stringify(config); - const configFile = getPathToParsedConfigFile(config.tempDir); - fs10.mkdirSync(path11.dirname(configFile), { recursive: true }); - fs10.writeFileSync(configFile, configString, "utf8"); + fs10.mkdirSync(path11.dirname(configFilePath), { recursive: true }); + fs10.writeFileSync(configFilePath, configString, "utf8"); logger2.debug("Saved config:"); logger2.debug(configString); } -async function getConfig(tempDir, logger2) { - const configFile = getPathToParsedConfigFile(tempDir); - if (!fs10.existsSync(configFile)) { +async function saveConfig(config, logger2) { + const configFile = getPathToParsedConfigFile(config.tempDir); + saveConfigFile(logger2, configFile, config); +} +function saveAbortedInitConfig(action, config) { + if (action.name !== "init" /* Init */) { + return; + } + const failedConfig = { + version: config.version, + tempDir: config.tempDir, + codeQLCmd: config.codeQLCmd, + debugMode: config.debugMode, + gitHubVersion: config.gitHubVersion, + languages: config.languages, + analysisKinds: config.analysisKinds + }; + action.logger.debug(`Saving config for failed init step.`); + saveConfigFile( + action.logger, + getPathToFailedConfigFile(config.tempDir), + failedConfig + ); +} +function readConfigFile(logger2, configFilePath) { + if (!fs10.existsSync(configFilePath)) { return void 0; } - const configString = fs10.readFileSync(configFile, "utf8"); + const configString = fs10.readFileSync(configFilePath, "utf8"); logger2.debug("Loaded config:"); logger2.debug(configString); const config = JSON.parse(configString); @@ -175087,6 +175112,10 @@ async function getConfig(tempDir, logger2) { } return config; } +async function getConfig(tempDir, logger2) { + const configFile = getPathToParsedConfigFile(tempDir); + return readConfigFile(logger2, configFile); +} async function generateRegistries(registriesInput, tempDir, logger2) { const registries = parseRegistries(registriesInput); let registriesAuthTokens; @@ -186495,6 +186524,15 @@ async function run3(actionState) { } await checkInstallPython311(config.languages, codeql); } catch (unwrappedError) { + if (config !== void 0) { + try { + saveAbortedInitConfig(actionState, config); + } catch (configSaveError) { + logger2.warning( + `Unable to save configuration for '${"init-post" /* InitPost */}' step: ${getErrorMessage(configSaveError)}` + ); + } + } const error3 = wrapError(unwrappedError); core19.setFailed(error3.message); const statusReportBase = await createStatusReportBase( diff --git a/src/config-utils.ts b/src/config-utils.ts index ebfabad36e..107f666661 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -21,7 +21,11 @@ import { import * as api from "./api-client"; import { getCachingKind } from "./caching-utils"; import { type CodeQL } from "./codeql"; -import { type ActionConfig, type Config } from "./config/action-config"; +import { + type AbortedInitConfig, + type ActionConfig, + type Config, +} from "./config/action-config"; import { calculateAugmentation, ExcludeQueryFilter, @@ -71,6 +75,7 @@ import { import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; import { shouldSkipOverlayAnalysis } from "./overlay/status"; import { RepositoryNwo } from "./repository"; +import { ActionName } from "./status-report"; import { ToolsFeature } from "./tools-features"; import { downloadTrapCaches } from "./trap-caching"; import { @@ -1379,31 +1384,84 @@ export function getPathToParsedConfigFile(tempDir: string): string { return path.join(tempDir, "config"); } +function getPathToFailedConfigFile(tempDir: string): string { + return path.join(tempDir, "failed-config"); +} + /** - * Store the given config to the path returned from getPathToParsedConfigFile. + * Writes {@link config} to {@link configFilePath}. + * + * This function is not intended to be called, other than by + * {@link saveConfig} and {@link saveFailedConfig} below. */ -export async function saveConfig(config: Config, logger: Logger) { +function saveConfigFile( + logger: Logger, + configFilePath: string, + config: Partial, +) { const configString = JSON.stringify(config); - const configFile = getPathToParsedConfigFile(config.tempDir); - fs.mkdirSync(path.dirname(configFile), { recursive: true }); - fs.writeFileSync(configFile, configString, "utf8"); + fs.mkdirSync(path.dirname(configFilePath), { recursive: true }); + fs.writeFileSync(configFilePath, configString, "utf8"); logger.debug("Saved config:"); logger.debug(configString); } /** - * Get the config that has been saved to the given temp dir. - * If the config could not be found then returns undefined. + * Store the given config to the path returned from getPathToParsedConfigFile. */ -export async function getConfig( - tempDir: string, +export async function saveConfig(config: Config, logger: Logger) { + const configFile = getPathToParsedConfigFile(config.tempDir); + saveConfigFile(logger, configFile, config); +} + +/** + * Store parts of {@link config} as a failed configuration file. + * + * The file will only contain parts of {@link config} that are relevant to + * the `init-post` step in the workflow. + */ +export function saveAbortedInitConfig( + action: ActionState<["Base", "Logger"]>, + config: Config, +) { + // Should only be called by init. + if (action.name !== ActionName.Init) { + return; + } + + // Create a copy of the input `Config` that only contains the keys we want. + const failedConfig: AbortedInitConfig = { + version: config.version, + tempDir: config.tempDir, + codeQLCmd: config.codeQLCmd, + debugMode: config.debugMode, + gitHubVersion: config.gitHubVersion, + languages: config.languages, + analysisKinds: config.analysisKinds, + }; + + action.logger.debug(`Saving config for failed init step.`); + saveConfigFile( + action.logger, + getPathToFailedConfigFile(config.tempDir), + failedConfig, + ); +} + +/** + * Attempts to read a {@link Config} from {@link configFilePath}. + * + * This function is not intended to be called, other than by + * {@link getConfig} and {@link getAbortedInitConfig} below. + */ +function readConfigFile( logger: Logger, -): Promise { - const configFile = getPathToParsedConfigFile(tempDir); - if (!fs.existsSync(configFile)) { + configFilePath: string, +): Config | undefined { + if (!fs.existsSync(configFilePath)) { return undefined; } - const configString = fs.readFileSync(configFile, "utf8"); + const configString = fs.readFileSync(configFilePath, "utf8"); logger.debug("Loaded config:"); logger.debug(configString); @@ -1428,6 +1486,36 @@ export async function getConfig( return config as Config; } +/** + * Get the config that has been saved to the given temp dir. + * If the config could not be found then returns undefined. + */ +export async function getConfig( + tempDir: string, + logger: Logger, +): Promise { + const configFile = getPathToParsedConfigFile(tempDir); + return readConfigFile(logger, configFile); +} + +/** + * Loads a {@link AbortedInitConfig} from a failed `init` step if it exists. + * + * The result will only contain parts of a {@link Config} that are relevant to + * the `init-post` step in the workflow. + */ +export function getAbortedInitConfig( + action: ActionState<["Base", "Logger"]>, + tempDir: string, +): AbortedInitConfig | undefined { + // Only intended to be called by the `init-post` action. + if (action.name !== ActionName.InitPost) { + return undefined; + } + + return readConfigFile(action.logger, getPathToFailedConfigFile(tempDir)); +} + /** * Generate a `qlconfig.yml` file from the `registries` input. * This file is used by the CodeQL CLI to list the registries to use for each diff --git a/src/config/action-config.ts b/src/config/action-config.ts index fcb6a9482a..1024c6fecc 100644 --- a/src/config/action-config.ts +++ b/src/config/action-config.ts @@ -142,3 +142,14 @@ export type ActionConfigProperties = keyof Config; export type ActionConfig< T extends ActionConfigProperties = ActionConfigProperties, > = Partial & Pick; + +/** A minimal configuration that we can make available even when `init` aborts. */ +export type AbortedInitConfig = ActionConfig< + | "version" + | "tempDir" + | "gitHubVersion" + | "debugMode" + | "languages" + | "analysisKinds" + | "codeQLCmd" +>; diff --git a/src/init-action.ts b/src/init-action.ts index 8723044da6..5376763b52 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -457,6 +457,18 @@ async function run( await checkInstallPython311(config.languages, codeql); } catch (unwrappedError) { + // If we have initialised the Action state, then try to save a subset of it for use by the `init-post` action + // so that it can try to create and upload additional diagnostic information. + if (config !== undefined) { + try { + configUtils.saveAbortedInitConfig(actionState, config); + } catch (configSaveError) { + logger.warning( + `Unable to save configuration for '${ActionName.InitPost}' step: ${getErrorMessage(configSaveError)}`, + ); + } + } + const error = wrapError(unwrappedError); core.setFailed(error.message); const statusReportBase = await createStatusReportBase( From 4fdbb71b548f7260858d8fe0eebbfcfa9d1f05b5 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Sat, 10 Oct 2026 00:30:06 +0100 Subject: [PATCH 06/17] Use `runInActions` for `init-post` --- lib/entry-points.js | 35 +++++++++++++++-------------------- src/init-action-post.ts | 38 ++++++++++++++++---------------------- src/status-report.ts | 2 ++ 3 files changed, 33 insertions(+), 42 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index ea86db9891..0bb4be3ddb 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -171404,6 +171404,8 @@ function getAddressString(address) { function getDisplayActionName(actionName) { if (actionName === "finish" /* Analyze */) { return "analyze"; + } else if (actionName === "init-post" /* InitPost */) { + return "init post"; } return actionName; } @@ -187108,15 +187110,16 @@ async function removeUploadedSarif(uploadFailedSarifResult, logger2) { } // src/init-action-post.ts -async function run4(startedAt) { - const logger2 = getActionsLogger(); +async function run4(action) { + const logger2 = action.logger; let config; let uploadFailedSarifResult; let dependencyCachingUsage; try { const jobStatus2 = getOptionalInput("job-status"); restoreInputs(logger2); - config = await getConfig(getTemporaryDirectory(), logger2); + const tmpDir = getTemporaryDirectory(); + config = await getConfig(tmpDir, logger2); const gitHubVersion = config?.gitHubVersion ?? await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger2); const repositoryNwo = getRepositoryNwo(); @@ -187151,9 +187154,9 @@ async function run4(startedAt) { const error3 = wrapApiConfigurationError(wrapError(unwrappedError)); core20.setFailed(error3.message); const statusReportBase2 = await createStatusReportBase( - "init-post" /* InitPost */, + action.name, getActionsStatus(error3), - startedAt, + action.startedAt, config, await checkDiskUsage(logger2), logger2, @@ -187168,9 +187171,9 @@ async function run4(startedAt) { const jobStatus = getFinalJobStatus(config); logger2.info(`CodeQL job status was ${getJobStatusDisplayName(jobStatus)}.`); const statusReportBase = await createStatusReportBase( - "init-post" /* InitPost */, + action.name, "success", - startedAt, + action.startedAt, config, await checkDiskUsage(logger2), logger2 @@ -187214,20 +187217,12 @@ function getJobStatusFromEnvironment() { } return void 0; } +var initPost = { + name: "init-post" /* InitPost */, + run: run4 +}; async function runWrapper5() { - const startedAt = /* @__PURE__ */ new Date(); - const logger2 = getActionsLogger(); - try { - await run4(startedAt); - } catch (error3) { - core20.setFailed(`init post action failed: ${wrapError(error3).message}`); - await sendUnhandledErrorStatusReport( - "init-post" /* InitPost */, - startedAt, - error3, - logger2 - ); - } + return await runInActions(initPost); } // src/resolve-environment-action.ts diff --git a/src/init-action-post.ts b/src/init-action-post.ts index a81c27810a..9e623a5794 100644 --- a/src/init-action-post.ts +++ b/src/init-action-post.ts @@ -6,6 +6,7 @@ import * as core from "@actions/core"; +import { runInActions, type Action, type ActionState } from "./action-common"; import { restoreInputs, getOptionalInput, @@ -25,12 +26,10 @@ import { EnvVar, getEnv, exportEnvVar } from "./environment"; import { initFeatures } from "./feature-flags"; import * as gitUtils from "./git-utils"; import * as initActionPostHelper from "./init-action-post-helper"; -import { getActionsLogger } from "./logging"; import { getRepositoryNwo } from "./repository"; import { StatusReportBase, sendStatusReport, - sendUnhandledErrorStatusReport, createStatusReportBase, getActionsStatus, ActionName, @@ -45,16 +44,17 @@ interface InitPostStatusReport initActionPostHelper.JobStatusReport, initActionPostHelper.DependencyCachingUsageReport {} -async function run(startedAt: Date) { +async function run(action: ActionState<["Base", "Logger"]>) { // To capture errors appropriately, keep as much code within the try-catch as // possible, and only use safe functions outside. + const logger = action.logger; - const logger = getActionsLogger(); let config: Config | undefined; let uploadFailedSarifResult: | initActionPostHelper.UploadFailedSarifResult | undefined; let dependencyCachingUsage: DependencyCachingUsageReport | undefined; + try { // Read the job status before restoring inputs, since it is provided by the Actions runtime // environment for this step and would otherwise be overwritten by the value that the `init` @@ -64,7 +64,8 @@ async function run(startedAt: Date) { // Restore inputs from `init` Action. restoreInputs(logger); - config = await getConfig(getTemporaryDirectory(), logger); + const tmpDir = getTemporaryDirectory(); + config = await getConfig(tmpDir, logger); const gitHubVersion = config?.gitHubVersion ?? (await getGitHubVersion()); checkGitHubVersionInRange(gitHubVersion, logger); @@ -112,9 +113,9 @@ async function run(startedAt: Date) { core.setFailed(error.message); const statusReportBase = await createStatusReportBase( - ActionName.InitPost, + action.name, getActionsStatus(error), - startedAt, + action.startedAt, config, await checkDiskUsage(logger), logger, @@ -130,9 +131,9 @@ async function run(startedAt: Date) { logger.info(`CodeQL job status was ${getJobStatusDisplayName(jobStatus)}.`); const statusReportBase = await createStatusReportBase( - ActionName.InitPost, + action.name, "success", - startedAt, + action.startedAt, config, await checkDiskUsage(logger), logger, @@ -216,18 +217,11 @@ function getJobStatusFromEnvironment(): JobStatus | undefined { return undefined; } +const initPost: Action = { + name: ActionName.InitPost, + run, +}; + export async function runWrapper() { - const startedAt = new Date(); - const logger = getActionsLogger(); - try { - await run(startedAt); - } catch (error) { - core.setFailed(`init post action failed: ${wrapError(error).message}`); - await sendUnhandledErrorStatusReport( - ActionName.InitPost, - startedAt, - error, - logger, - ); - } + return await runInActions(initPost); } diff --git a/src/status-report.ts b/src/status-report.ts index ae21beb307..3f3e1d884c 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -68,6 +68,8 @@ export enum ActionName { export function getDisplayActionName(actionName: ActionName): string { if (actionName === ActionName.Analyze) { return "analyze"; + } else if (actionName === ActionName.InitPost) { + return "init post"; } return actionName; } From f4a15a13fddaf2e467a34d7690be557e678fe505 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 11:25:00 +0100 Subject: [PATCH 07/17] Try to load failed config if normal config is unavailable Adapt relevant functions by narrowing their types or skipping steps that would otherwise have been skipped as well --- lib/entry-points.js | 27 ++++++++++++++++++++++----- src/actions-util.ts | 11 +++++++++-- src/analyze.ts | 3 ++- src/config/action-config.ts | 8 ++++++++ src/debug-artifacts.ts | 17 +++++++++++------ src/init-action-post-helper.ts | 22 ++++++++++++++-------- src/init-action-post.ts | 21 +++++++++++++++++---- src/util.ts | 12 +++++++++--- 8 files changed, 92 insertions(+), 29 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 0bb4be3ddb..7350b20656 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -166098,6 +166098,11 @@ var toolrunner = __toESM(require_toolrunner()); var github = __toESM(require_github()); var io2 = __toESM(require_io()); +// src/config/action-config.ts +function hasConfigKey(config, key) { + return config[key] !== void 0; +} + // src/environment.ts var core = __toESM(require_core()); function getRequiredEnvVar(env, paramName) { @@ -170208,6 +170213,9 @@ function getWorkflowEvent(env = getEnv()) { } } async function printDebugLogs(config) { + if (!hasConfigKey(config, "languages") || !hasConfigKey(config, "dbLocation")) { + return; + } for (const language of config.languages) { const databaseDirectory = getCodeQLDatabasePath(config, language); const logsDirectory = path2.join(databaseDirectory, "log"); @@ -175118,6 +175126,12 @@ async function getConfig(tempDir, logger2) { const configFile = getPathToParsedConfigFile(tempDir); return readConfigFile(logger2, configFile); } +function getAbortedInitConfig(action, tempDir) { + if (action.name !== "init-post" /* InitPost */) { + return void 0; + } + return readConfigFile(action.logger, getPathToFailedConfigFile(tempDir)); +} async function generateRegistries(registriesInput, tempDir, logger2) { const registries = parseRegistries(registriesInput); let registriesAuthTokens; @@ -186963,14 +186977,14 @@ async function uploadFailureInfo(uploadAllAvailableDebugArtifacts, printDebugLog ); } } - if (config.debugMode) { + if (config.debugMode && hasConfigKey(config, "dbLocation")) { logger2.info( "Debug mode is on. Uploading available database bundles and logs as Actions debugging artifacts..." ); await uploadAllAvailableDebugArtifacts(codeql, config, logger2); await printDebugLogs2(config); } - if (isSelfHostedRunner()) { + if (isSelfHostedRunner() && config.dbLocation) { try { fs30.rmSync(config.dbLocation, { recursive: true, @@ -187006,7 +187020,7 @@ function isConclusiveJobStatus(jobStatus) { } } async function recordOverlayStatus(codeql, config, features, jobStatus, env, logger2) { - if (config.overlayDatabaseMode !== "overlay-base" /* OverlayBase */ || env.getOptional("CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY" /* ANALYZE_DID_COMPLETE_SUCCESSFULLY */) === "true" || !await features.getValue("overlay_analysis_status_save" /* OverlayAnalysisStatusSave */)) { + if (config.overlayDatabaseMode !== "overlay-base" /* OverlayBase */ || env.getOptional("CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY" /* ANALYZE_DID_COMPLETE_SUCCESSFULLY */) === "true" || !await features.getValue("overlay_analysis_status_save" /* OverlayAnalysisStatusSave */) || config.languages === void 0) { return; } if (!isConclusiveJobStatus(jobStatus) && !didCodeQlReportError(env)) { @@ -187120,6 +187134,9 @@ async function run4(action) { restoreInputs(logger2); const tmpDir = getTemporaryDirectory(); config = await getConfig(tmpDir, logger2); + if (config === void 0) { + config = getAbortedInitConfig(action, tmpDir); + } const gitHubVersion = config?.gitHubVersion ?? await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger2); const repositoryNwo = getRepositoryNwo(); @@ -187129,7 +187146,7 @@ async function run4(action) { getTemporaryDirectory(), logger2 ); - if (config === void 0) { + if (config?.codeQLCmd === void 0) { logger2.warning( "Debugging artifacts are unavailable since the 'init' Action failed before it could produce any." ); @@ -187146,7 +187163,7 @@ async function run4(action) { getEnv(), logger2 ); - if (await isAnalyzingDefaultBranch() && config.dependencyCachingEnabled !== "none" /* None */) { + if (await isAnalyzingDefaultBranch() && hasConfigKey(config, "dependencyCachingEnabled") && config.dependencyCachingEnabled !== "none" /* None */) { dependencyCachingUsage = await getDependencyCacheUsage(logger2); } } diff --git a/src/actions-util.ts b/src/actions-util.ts index ea9a119ed4..e43abbb0b9 100644 --- a/src/actions-util.ts +++ b/src/actions-util.ts @@ -6,7 +6,7 @@ import * as toolrunner from "@actions/exec/lib/toolrunner"; import * as github from "@actions/github"; import * as io from "@actions/io"; -import type { Config } from "./config-utils"; +import { hasConfigKey, type Config } from "./config/action-config"; import { Env, EnvVar, ActionsEnvVars, ReadOnlyEnv } from "./environment"; import { Logger } from "./logging"; import { @@ -131,7 +131,14 @@ export function getWorkflowEvent(env: Env = getEnv()): any { } } -export async function printDebugLogs(config: Config) { +export async function printDebugLogs(config: Partial) { + if ( + !hasConfigKey(config, "languages") || + !hasConfigKey(config, "dbLocation") + ) { + return; + } + for (const language of config.languages) { const databaseDirectory = getCodeQLDatabasePath(config, language); const logsDirectory = path.join(databaseDirectory, "log"); diff --git a/src/analyze.ts b/src/analyze.ts index 4eeb447e18..4d5fd53543 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -9,6 +9,7 @@ import { getTemporaryDirectory } from "./actions-util"; import * as analyses from "./analyses"; import { setupCppAutobuild } from "./autobuild"; import { type CodeQL } from "./codeql"; +import type { ActionConfig } from "./config/action-config"; import { defaultSuites } from "./config/db-config"; import * as configUtils from "./config-utils"; import { @@ -171,7 +172,7 @@ async function shouldExtractLanguage( } export function dbIsFinalized( - config: configUtils.Config, + config: ActionConfig<"dbLocation">, language: Language, logger: Logger, ) { diff --git a/src/config/action-config.ts b/src/config/action-config.ts index 1024c6fecc..110b3665a7 100644 --- a/src/config/action-config.ts +++ b/src/config/action-config.ts @@ -143,6 +143,14 @@ export type ActionConfig< T extends ActionConfigProperties = ActionConfigProperties, > = Partial & Pick; +/** Helper function to check whether `config` has `key` to narrow the type of `config`. */ +export function hasConfigKey( + config: Partial, + key: Key, +): config is ActionConfig { + return config[key] !== undefined; +} + /** A minimal configuration that we can make available even when `init` aborts. */ export type AbortedInitConfig = ActionConfig< | "version" diff --git a/src/debug-artifacts.ts b/src/debug-artifacts.ts index 70c9a0ebd4..56497f14e8 100644 --- a/src/debug-artifacts.ts +++ b/src/debug-artifacts.ts @@ -10,7 +10,7 @@ import { getOptionalInput, getTemporaryDirectory } from "./actions-util"; import { dbIsFinalized } from "./analyze"; import { scanArtifactsForTokens } from "./artifact-scanner"; import { type CodeQL } from "./codeql"; -import { Config } from "./config-utils"; +import type { ActionConfig } from "./config/action-config"; import { EnvVar, exportEnvVar } from "./environment"; import * as json from "./json"; import { Language } from "./languages"; @@ -89,7 +89,7 @@ export async function uploadCombinedSarifArtifacts( * @return The path to that debug artifact, or undefined if an error occurs. */ function tryPrepareSarifDebugArtifact( - config: Config, + config: ActionConfig<"dbLocation">, language: Language, logger: Logger, ): string | undefined { @@ -131,7 +131,7 @@ function tryPrepareSarifDebugArtifact( */ async function tryBundleDatabase( codeql: CodeQL, - config: Config, + config: ActionConfig<"dbLocation">, language: Language, logger: Logger, ): Promise { @@ -157,6 +157,11 @@ async function tryBundleDatabase( } } +/** The minimal configuration needed for `tryUploadAllAvailableDebugArtifacts`. */ +export type UploadDebugArtifactsConfig = ActionConfig< + "languages" | "gitHubVersion" | "dbLocation" | "debugArtifactName" +>; + /** * Attempt to upload all available debug artifacts. * @@ -164,7 +169,7 @@ async function tryBundleDatabase( */ export async function tryUploadAllAvailableDebugArtifacts( codeql: CodeQL, - config: Config, + config: UploadDebugArtifactsConfig, logger: Logger, ) { const filesToUpload: string[] = []; @@ -353,7 +358,7 @@ export async function getArtifactUploaderClient( * all files in the database folder and return the path. */ async function createPartialDatabaseBundle( - config: Config, + config: ActionConfig<"dbLocation">, language: Language, ): Promise { const databasePath = getCodeQLDatabasePath(config, language); @@ -394,7 +399,7 @@ async function createPartialDatabaseBundle( */ async function createDatabaseBundleCli( codeql: CodeQL, - config: Config, + config: ActionConfig<"dbLocation">, language: Language, ): Promise { const databaseBundlePath = await bundleDb( diff --git a/src/init-action-post-helper.ts b/src/init-action-post-helper.ts index ce06e17191..270bc0f079 100644 --- a/src/init-action-post-helper.ts +++ b/src/init-action-post-helper.ts @@ -7,7 +7,11 @@ import * as actionsUtil from "./actions-util"; import { CodeScanning, RiskAssessment } from "./analyses"; import { getApiClient, getGitHubVersion } from "./api-client"; import { CodeQL, getCodeQL } from "./codeql"; -import type { ActionConfig } from "./config/action-config"; +import { + hasConfigKey, + type AbortedInitConfig, + type ActionConfig, +} from "./config/action-config"; import { Config, isCodeScanningEnabled, @@ -17,6 +21,7 @@ import { getArtifactSuffix, getArtifactUploaderClient, sanitizeArtifactName, + type UploadDebugArtifactsConfig, } from "./debug-artifacts"; import * as dependencyCaching from "./dependency-caching"; import { EnvVar, ReadOnlyEnv } from "./environment"; @@ -330,12 +335,12 @@ export async function tryUploadSarifIfRunFailed( export async function uploadFailureInfo( uploadAllAvailableDebugArtifacts: ( codeql: CodeQL, - config: Config, + config: UploadDebugArtifactsConfig, logger: Logger, ) => Promise, - printDebugLogs: (config: Config) => Promise, + printDebugLogs: (config: Partial) => Promise, codeql: CodeQL, - config: Config, + config: Config | AbortedInitConfig, repositoryNwo: RepositoryNwo, features: FeatureEnablement, jobStatus: string | undefined, @@ -382,7 +387,7 @@ export async function uploadFailureInfo( } // Upload appropriate Actions artifacts for debugging - if (config.debugMode) { + if (config.debugMode && hasConfigKey(config, "dbLocation")) { logger.info( "Debug mode is on. Uploading available database bundles and logs as Actions debugging artifacts...", ); @@ -390,7 +395,7 @@ export async function uploadFailureInfo( await printDebugLogs(config); } - if (actionsUtil.isSelfHostedRunner()) { + if (actionsUtil.isSelfHostedRunner() && config.dbLocation) { try { fs.rmSync(config.dbLocation, { recursive: true, @@ -453,7 +458,7 @@ function isConclusiveJobStatus(jobStatus: string | undefined): boolean { */ async function recordOverlayStatus( codeql: CodeQL, - config: Config, + config: Partial, features: FeatureEnablement, jobStatus: string | undefined, env: ReadOnlyEnv, @@ -462,7 +467,8 @@ async function recordOverlayStatus( if ( config.overlayDatabaseMode !== OverlayDatabaseMode.OverlayBase || env.getOptional(EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY) === "true" || - !(await features.getValue(Feature.OverlayAnalysisStatusSave)) + !(await features.getValue(Feature.OverlayAnalysisStatusSave)) || + config.languages === undefined ) { return; } diff --git a/src/init-action-post.ts b/src/init-action-post.ts index 9e623a5794..2325fc5d2d 100644 --- a/src/init-action-post.ts +++ b/src/init-action-post.ts @@ -16,7 +16,12 @@ import { import { getGitHubVersion, wrapApiConfigurationError } from "./api-client"; import { CachingKind } from "./caching-utils"; import { getCodeQL } from "./codeql"; -import { type Config, getConfig } from "./config-utils"; +import { + hasConfigKey, + type AbortedInitConfig, + type Config, +} from "./config/action-config"; +import { getAbortedInitConfig, getConfig } from "./config-utils"; import * as debugArtifacts from "./debug-artifacts"; import { DependencyCachingUsageReport, @@ -49,7 +54,7 @@ async function run(action: ActionState<["Base", "Logger"]>) { // possible, and only use safe functions outside. const logger = action.logger; - let config: Config | undefined; + let config: Config | AbortedInitConfig | undefined; let uploadFailedSarifResult: | initActionPostHelper.UploadFailedSarifResult | undefined; @@ -67,6 +72,13 @@ async function run(action: ActionState<["Base", "Logger"]>) { const tmpDir = getTemporaryDirectory(); config = await getConfig(tmpDir, logger); + // If we weren't able to read the normal configuration file, the `init` step failed before it could write it. + // Try to load the "failed" configuration file instead which may have been written by the `init` step + // with a minimal configuration for this post-action. + if (config === undefined) { + config = getAbortedInitConfig(action, tmpDir); + } + const gitHubVersion = config?.gitHubVersion ?? (await getGitHubVersion()); checkGitHubVersionInRange(gitHubVersion, logger); @@ -78,7 +90,7 @@ async function run(action: ActionState<["Base", "Logger"]>) { logger, ); - if (config === undefined) { + if (config?.codeQLCmd === undefined) { logger.warning( "Debugging artifacts are unavailable since the 'init' Action failed before it could produce any.", ); @@ -103,6 +115,7 @@ async function run(action: ActionState<["Base", "Logger"]>) { // and where caching may not be enabled. if ( (await gitUtils.isAnalyzingDefaultBranch()) && + hasConfigKey(config, "dependencyCachingEnabled") && config.dependencyCachingEnabled !== CachingKind.None ) { dependencyCachingUsage = await getDependencyCacheUsage(logger); @@ -158,7 +171,7 @@ async function run(action: ActionState<["Base", "Logger"]>) { * Otherwise, we determine the job status based on whether the analyze step * completed successfully and whether we have a valid CodeQL config. */ -function getFinalJobStatus(config: Config | undefined): JobStatus { +function getFinalJobStatus(config: Partial | undefined): JobStatus { const existingJobStatus = getJobStatusFromEnvironment(); if (existingJobStatus !== undefined) { return existingJobStatus; diff --git a/src/util.ts b/src/util.ts index 674e2a2eef..566386de62 100644 --- a/src/util.ts +++ b/src/util.ts @@ -12,6 +12,7 @@ import * as semver from "semver"; import * as apiCompatibility from "./api-compatibility.json"; import type { CodeQL } from "./codeql"; +import type { ActionConfig } from "./config/action-config"; import type { Pack } from "./config/db-config"; import type { Config } from "./config-utils"; import { @@ -445,7 +446,10 @@ export function getThreadsFlag( /** * Get the path where the CodeQL database for the given language lives. */ -export function getCodeQLDatabasePath(config: Config, language: Language) { +export function getCodeQLDatabasePath( + config: ActionConfig<"dbLocation">, + language: Language, +) { return path.resolve(config.dbLocation, language); } @@ -639,7 +643,9 @@ export async function codeQlVersionAtLeast( return semver.gte((await codeql.getVersion()).version, requiredVersion); } -export function getBaseDatabaseOidsFilePath(config: Config): string { +export function getBaseDatabaseOidsFilePath( + config: ActionConfig<"dbLocation">, +): string { return path.join(config.dbLocation, BASE_DATABASE_OIDS_FILE_NAME); } @@ -651,7 +657,7 @@ export function getBaseDatabaseOidsFilePath(config: Config): string { * given `includeDiagnostics` value. */ export async function bundleDb( - config: Config, + config: ActionConfig<"dbLocation">, language: Language, codeql: CodeQL, dbName: string, From 409be011364777ecfccd19c9c837565ee94628f5 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 11:29:29 +0100 Subject: [PATCH 08/17] Replace deprecated functions in `init-action.post.ts` --- lib/entry-points.js | 18 +++++++++--------- src/init-action-post.ts | 27 ++++++++++++++++----------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 7350b20656..1cee05cfb4 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -187160,7 +187160,7 @@ async function run4(action) { repositoryNwo, features, jobStatus2, - getEnv(), + action.env, logger2 ); if (await isAnalyzingDefaultBranch() && hasConfigKey(config, "dependencyCachingEnabled") && config.dependencyCachingEnabled !== "none" /* None */) { @@ -187185,7 +187185,7 @@ async function run4(action) { } return; } - const jobStatus = getFinalJobStatus(config); + const jobStatus = getFinalJobStatus(action, config); logger2.info(`CodeQL job status was ${getJobStatusDisplayName(jobStatus)}.`); const statusReportBase = await createStatusReportBase( action.name, @@ -187207,25 +187207,25 @@ async function run4(action) { logger2.info("Status report sent for init-post step."); } } -function getFinalJobStatus(config) { - const existingJobStatus = getJobStatusFromEnvironment(); +function getFinalJobStatus(action, config) { + const existingJobStatus = getJobStatusFromEnvironment(action.env); if (existingJobStatus !== void 0) { return existingJobStatus; } let jobStatus; - if (process.env["CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY" /* ANALYZE_DID_COMPLETE_SUCCESSFULLY */] === "true") { - exportEnvVar("CODEQL_ACTION_JOB_STATUS" /* JOB_STATUS */, "JOB_STATUS_SUCCESS" /* SuccessStatus */); + if (action.env.getOptional("CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY" /* ANALYZE_DID_COMPLETE_SUCCESSFULLY */) === "true") { + action.env.export("CODEQL_ACTION_JOB_STATUS" /* JOB_STATUS */, "JOB_STATUS_SUCCESS" /* SuccessStatus */); jobStatus = "JOB_STATUS_SUCCESS" /* SuccessStatus */; } else if (config !== void 0) { jobStatus = "JOB_STATUS_CONFIGURATION_ERROR" /* ConfigErrorStatus */; } else { jobStatus = "JOB_STATUS_UNKNOWN" /* UnknownStatus */; } - exportEnvVar("CODEQL_ACTION_JOB_STATUS" /* JOB_STATUS */, jobStatus); + action.env.export("CODEQL_ACTION_JOB_STATUS" /* JOB_STATUS */, jobStatus); return jobStatus; } -function getJobStatusFromEnvironment() { - const jobStatusFromEnvironment = process.env["CODEQL_ACTION_JOB_STATUS" /* JOB_STATUS */]; +function getJobStatusFromEnvironment(env) { + const jobStatusFromEnvironment = env.getOptional("CODEQL_ACTION_JOB_STATUS" /* JOB_STATUS */); if (jobStatusFromEnvironment !== void 0) { if (Object.values(JobStatus).includes(jobStatusFromEnvironment)) { return jobStatusFromEnvironment; diff --git a/src/init-action-post.ts b/src/init-action-post.ts index 2325fc5d2d..dd981c200a 100644 --- a/src/init-action-post.ts +++ b/src/init-action-post.ts @@ -27,7 +27,7 @@ import { DependencyCachingUsageReport, getDependencyCacheUsage, } from "./dependency-caching"; -import { EnvVar, getEnv, exportEnvVar } from "./environment"; +import { EnvVar, type ReadOnlyEnv } from "./environment"; import { initFeatures } from "./feature-flags"; import * as gitUtils from "./git-utils"; import * as initActionPostHelper from "./init-action-post-helper"; @@ -49,7 +49,7 @@ interface InitPostStatusReport initActionPostHelper.JobStatusReport, initActionPostHelper.DependencyCachingUsageReport {} -async function run(action: ActionState<["Base", "Logger"]>) { +async function run(action: ActionState<["Base", "Logger", "Env"]>) { // To capture errors appropriately, keep as much code within the try-catch as // possible, and only use safe functions outside. const logger = action.logger; @@ -105,7 +105,7 @@ async function run(action: ActionState<["Base", "Logger"]>) { repositoryNwo, features, jobStatus, - getEnv(), + action.env, logger, ); @@ -140,7 +140,7 @@ async function run(action: ActionState<["Base", "Logger"]>) { } return; } - const jobStatus = getFinalJobStatus(config); + const jobStatus = getFinalJobStatus(action, config); logger.info(`CodeQL job status was ${getJobStatusDisplayName(jobStatus)}.`); const statusReportBase = await createStatusReportBase( @@ -171,16 +171,21 @@ async function run(action: ActionState<["Base", "Logger"]>) { * Otherwise, we determine the job status based on whether the analyze step * completed successfully and whether we have a valid CodeQL config. */ -function getFinalJobStatus(config: Partial | undefined): JobStatus { - const existingJobStatus = getJobStatusFromEnvironment(); +function getFinalJobStatus( + action: ActionState<["Env"]>, + config: Partial | undefined, +): JobStatus { + const existingJobStatus = getJobStatusFromEnvironment(action.env); if (existingJobStatus !== undefined) { return existingJobStatus; } let jobStatus: JobStatus; - if (process.env[EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY] === "true") { - exportEnvVar(EnvVar.JOB_STATUS, JobStatus.SuccessStatus); + if ( + action.env.getOptional(EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY) === "true" + ) { + action.env.export(EnvVar.JOB_STATUS, JobStatus.SuccessStatus); jobStatus = JobStatus.SuccessStatus; } else if (config !== undefined) { // - We have computed a CodeQL config @@ -205,7 +210,7 @@ function getFinalJobStatus(config: Partial | undefined): JobStatus { // This shouldn't be necessary, but in the odd case that we run more than one // `init` post step, ensure the job status is consistent between them. - exportEnvVar(EnvVar.JOB_STATUS, jobStatus); + action.env.export(EnvVar.JOB_STATUS, jobStatus); return jobStatus; } @@ -214,8 +219,8 @@ function getFinalJobStatus(config: Partial | undefined): JobStatus { * * If the job status is invalid, return `UnknownStatus`. */ -function getJobStatusFromEnvironment(): JobStatus | undefined { - const jobStatusFromEnvironment = process.env[EnvVar.JOB_STATUS]; +function getJobStatusFromEnvironment(env: ReadOnlyEnv): JobStatus | undefined { + const jobStatusFromEnvironment = env.getOptional(EnvVar.JOB_STATUS); if (jobStatusFromEnvironment !== undefined) { // Validate the job status from the environment. If it is invalid, return unknown. From ecfc96e0a902c35a8a5d7c6c4e2c79f257469589 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 12:49:48 +0100 Subject: [PATCH 09/17] Add `getDisplayActionName` tests --- lib/entry-points.js | 11 +++++++---- src/action-common.test.ts | 18 ++++++++++++++++++ src/status-report.ts | 18 ++++++++++++------ 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 1cee05cfb4..44d94d1e08 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -171409,11 +171409,14 @@ function getAddressString(address) { } // src/status-report.ts +var ACTION_DISPLAY_NAMES = { + ["finish" /* Analyze */]: "analyze", + ["init-post" /* InitPost */]: "init post" +}; function getDisplayActionName(actionName) { - if (actionName === "finish" /* Analyze */) { - return "analyze"; - } else if (actionName === "init-post" /* InitPost */) { - return "init post"; + const displayName = ACTION_DISPLAY_NAMES[actionName]; + if (displayName !== void 0) { + return displayName; } return actionName; } diff --git a/src/action-common.test.ts b/src/action-common.test.ts index fc2e0a9aaa..449300a8d2 100644 --- a/src/action-common.test.ts +++ b/src/action-common.test.ts @@ -19,6 +19,24 @@ import { getErrorMessage } from "./util"; setupTests(test); +for (const [key, displayName] of Object.entries( + statusReport.ACTION_DISPLAY_NAMES, +)) { + test(`getDisplayActionName - '${displayName}' instead of '${key}'`, (t) => { + t.is(statusReport.getDisplayActionName(key as ActionName), displayName); + }); +} + +test("getDisplayActionName - returns action name", (t) => { + for (const actionName of Object.values(ActionName)) { + if (statusReport.ACTION_DISPLAY_NAMES[actionName]) { + continue; + } + + t.is(statusReport.getDisplayActionName(actionName), actionName); + } +}); + interface RunInActionsTestOpts { runFn?: () => Promise; expectedErrorMessage?: string; diff --git a/src/status-report.ts b/src/status-report.ts index 3f3e1d884c..fc45e82d67 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -61,15 +61,21 @@ export enum ActionName { UploadSarif = "upload-sarif", } +/** A partial mapping from `ActionName` to names for actions whose display name is not the `ActionName` value. */ +export const ACTION_DISPLAY_NAMES: Partial> = { + [ActionName.Analyze]: "analyze", + [ActionName.InitPost]: "init post", +}; + /** - * Maps an `ActionName` to its display name. Usually that is the same, except - * for `ActionName.Analyze` where it is `"analyze"` instead of `"finish"`. + * Maps an {@link ActionName} to its display name. Usually that is the same, except + * for actions which have a different display name in {@link ACTION_DISPLAY_NAMES}. */ export function getDisplayActionName(actionName: ActionName): string { - if (actionName === ActionName.Analyze) { - return "analyze"; - } else if (actionName === ActionName.InitPost) { - return "init post"; + const displayName = ACTION_DISPLAY_NAMES[actionName]; + + if (displayName !== undefined) { + return displayName; } return actionName; } From ac8654a8f263da108af61eb039965094ceaffdbb Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 12:50:01 +0100 Subject: [PATCH 10/17] Add `printDebugLogs` tests --- src/actions-util.test.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/actions-util.test.ts b/src/actions-util.test.ts index 3940cf7551..1217e831a0 100644 --- a/src/actions-util.test.ts +++ b/src/actions-util.test.ts @@ -7,12 +7,13 @@ import { isAnalyzingPullRequest, isDefaultSetup, isDynamicWorkflow, + printDebugLogs, } from "./actions-util"; import { computeAutomationID } from "./api-client"; import { EnvVar } from "./environment"; import { getRunnerLogger } from "./logging"; import { setupTests } from "./testing-utils"; -import { initializeEnvironment } from "./util"; +import { initializeEnvironment, withTmpDir } from "./util"; setupTests(test); @@ -100,6 +101,19 @@ test("computeAutomationID()", async (t) => { ); }); +test("printDebugLogs - doesn't throw when config doesn't have needed properties", async (t) => { + await t.notThrowsAsync(printDebugLogs({})); + await t.notThrowsAsync(printDebugLogs({ languages: [] })); + await t.notThrowsAsync(printDebugLogs({ dbLocation: "nowhere" })); +}); + +test("printDebugLogs - doesn't throw when language db doesn't exist", async (t) => + withTmpDir(async (tmpDir) => { + await t.notThrowsAsync( + printDebugLogs({ dbLocation: tmpDir, languages: ["javascript"] }), + ); + })); + test.serial("getPullRequestBranches() with pull request context", (t) => { withMockedContext( { From bed4cc0475d6e30e9748893fe473bf1f7a50ecad Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 22:39:08 +0100 Subject: [PATCH 11/17] Add log message for `!config.dbLocation` case --- lib/entry-points.js | 4 ++++ src/init-action-post-helper.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/entry-points.js b/lib/entry-points.js index 44d94d1e08..ece461a1ff 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -187002,6 +187002,10 @@ async function uploadFailureInfo(uploadAllAvailableDebugArtifacts, printDebugLog `Failed to clean up database cluster directory ${config.dbLocation}. Details: ${e}` ); } + } else if (config.dbLocation === void 0) { + logger2.debug( + "Skipping cleanup of database cluster directory since the configuration does not include a database path." + ); } else { logger2.debug( "Skipping cleanup of database cluster directory since we are running on a GitHub-hosted runner which will be automatically cleaned up." diff --git a/src/init-action-post-helper.ts b/src/init-action-post-helper.ts index 270bc0f079..3d3ffc5703 100644 --- a/src/init-action-post-helper.ts +++ b/src/init-action-post-helper.ts @@ -410,6 +410,10 @@ export async function uploadFailureInfo( `Failed to clean up database cluster directory ${config.dbLocation}. Details: ${e}`, ); } + } else if (config.dbLocation === undefined) { + logger.debug( + "Skipping cleanup of database cluster directory since the configuration does not include a database path.", + ); } else { logger.debug( "Skipping cleanup of database cluster directory since we are running on a GitHub-hosted " + From be78e02f2d2944ae3e88a618a0ef431e6a2125ce Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 22:39:34 +0100 Subject: [PATCH 12/17] Fix comment --- src/config-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config-utils.ts b/src/config-utils.ts index 107f666661..fea7bec018 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -1392,7 +1392,7 @@ function getPathToFailedConfigFile(tempDir: string): string { * Writes {@link config} to {@link configFilePath}. * * This function is not intended to be called, other than by - * {@link saveConfig} and {@link saveFailedConfig} below. + * {@link saveConfig} and {@link saveAbortedInitConfig} below. */ function saveConfigFile( logger: Logger, From e3aeabd8f1a1a8f097f23b77f7b92b3355f35b38 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 22:40:25 +0100 Subject: [PATCH 13/17] Fix example in comment --- src/config/action-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/action-config.ts b/src/config/action-config.ts index 110b3665a7..10f315028f 100644 --- a/src/config/action-config.ts +++ b/src/config/action-config.ts @@ -136,7 +136,7 @@ export type ActionConfigProperties = keyof Config; * * By default, this is equivalent to `Config`. By explicitly * providing required property names for `T` this can be restricted - * to requiring only those. For example, in `ActionConfig<"codeqlCmd">` + * to requiring only those. For example, in `ActionConfig<"codeQLCmd">` * all properties except `codeqlCmd` are optional. */ export type ActionConfig< From 26c6f91e6be5ab02622aad5c9776f7d5924d4147 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 22:42:38 +0100 Subject: [PATCH 14/17] Throw if aborted config functions are called incorrectly --- lib/entry-points.js | 8 ++++++-- src/config-utils.ts | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index ece461a1ff..3c5ee3bd27 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -175082,7 +175082,9 @@ async function saveConfig(config, logger2) { } function saveAbortedInitConfig(action, config) { if (action.name !== "init" /* Init */) { - return; + throw new Error( + `Internal error: 'saveAbortedInitConfig' should only be called by '${"init" /* Init */}', but was called by '${action.name}'.` + ); } const failedConfig = { version: config.version, @@ -175131,7 +175133,9 @@ async function getConfig(tempDir, logger2) { } function getAbortedInitConfig(action, tempDir) { if (action.name !== "init-post" /* InitPost */) { - return void 0; + throw new Error( + `Internal error: 'getAbortedInitConfig' should only be called by '${"init-post" /* InitPost */}', but was called by '${action.name}'.` + ); } return readConfigFile(action.logger, getPathToFailedConfigFile(tempDir)); } diff --git a/src/config-utils.ts b/src/config-utils.ts index fea7bec018..e0b005db78 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -1426,7 +1426,9 @@ export function saveAbortedInitConfig( ) { // Should only be called by init. if (action.name !== ActionName.Init) { - return; + throw new Error( + `Internal error: 'saveAbortedInitConfig' should only be called by '${ActionName.Init}', but was called by '${action.name}'.`, + ); } // Create a copy of the input `Config` that only contains the keys we want. @@ -1510,7 +1512,9 @@ export function getAbortedInitConfig( ): AbortedInitConfig | undefined { // Only intended to be called by the `init-post` action. if (action.name !== ActionName.InitPost) { - return undefined; + throw new Error( + `Internal error: 'getAbortedInitConfig' should only be called by '${ActionName.InitPost}', but was called by '${action.name}'.`, + ); } return readConfigFile(action.logger, getPathToFailedConfigFile(tempDir)); From 67f44bc916315c958f56c99b58ba434c49ec215d Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 22:48:21 +0100 Subject: [PATCH 15/17] Use `Logger` for `printDebugLogs` and log when returning early --- lib/entry-points.js | 41 ++++++++++++++++++---------------- src/actions-util.test.ts | 12 +++++----- src/actions-util.ts | 15 ++++++++----- src/init-action-post-helper.ts | 4 ++-- 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 3c5ee3bd27..6003234204 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -21656,8 +21656,8 @@ var require_core = __commonJS({ exports2.warning = warning14; exports2.notice = notice; exports2.info = info8; - exports2.startGroup = startGroup4; - exports2.endGroup = endGroup4; + exports2.startGroup = startGroup3; + exports2.endGroup = endGroup3; exports2.group = group; exports2.saveState = saveState3; exports2.getState = getState3; @@ -21755,20 +21755,20 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); function info8(message) { process.stdout.write(message + os7.EOL); } - function startGroup4(name) { + function startGroup3(name) { (0, command_1.issue)("group", name); } - function endGroup4() { + function endGroup3() { (0, command_1.issue)("endgroup"); } function group(name, fn) { return __awaiter2(this, void 0, void 0, function* () { - startGroup4(name); + startGroup3(name); let result; try { result = yield fn(); } finally { - endGroup4(); + endGroup3(); } return result; }); @@ -145686,22 +145686,22 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); process.stdout.write(message + os7.EOL); } exports2.info = info8; - function startGroup4(name) { + function startGroup3(name) { (0, command_1.issue)("group", name); } - exports2.startGroup = startGroup4; - function endGroup4() { + exports2.startGroup = startGroup3; + function endGroup3() { (0, command_1.issue)("endgroup"); } - exports2.endGroup = endGroup4; + exports2.endGroup = endGroup3; function group(name, fn) { return __awaiter2(this, void 0, void 0, function* () { - startGroup4(name); + startGroup3(name); let result; try { result = yield fn(); } finally { - endGroup4(); + endGroup3(); } return result; }); @@ -170212,30 +170212,33 @@ function getWorkflowEvent(env = getEnv()) { ); } } -async function printDebugLogs(config) { +async function printDebugLogs(logger2, config) { if (!hasConfigKey(config, "languages") || !hasConfigKey(config, "dbLocation")) { + logger2.debug( + `Not printing debug logs, because the configuration does not include the 'languages' or 'dbLocation' fields.` + ); return; } for (const language of config.languages) { const databaseDirectory = getCodeQLDatabasePath(config, language); const logsDirectory = path2.join(databaseDirectory, "log"); if (!doesDirectoryExist(logsDirectory)) { - core4.info(`Directory ${logsDirectory} does not exist.`); + logger2.info(`Directory ${logsDirectory} does not exist.`); continue; } const walkLogFiles = (dir) => { const entries = fs2.readdirSync(dir, { withFileTypes: true }); if (entries.length === 0) { - core4.info(`No debug logs found at directory ${logsDirectory}.`); + logger2.info(`No debug logs found at directory ${logsDirectory}.`); } for (const entry of entries) { if (entry.isFile()) { const absolutePath = path2.resolve(dir, entry.name); - core4.startGroup( + logger2.startGroup( `CodeQL Debug Logs - ${language} - ${entry.name} from file at path ${absolutePath}` ); - process.stdout.write(fs2.readFileSync(absolutePath)); - core4.endGroup(); + logger2.info(fs2.readFileSync(absolutePath, "utf-8")); + logger2.endGroup(); } else if (entry.isDirectory()) { walkLogFiles(path2.resolve(dir, entry.name)); } @@ -186989,7 +186992,7 @@ async function uploadFailureInfo(uploadAllAvailableDebugArtifacts, printDebugLog "Debug mode is on. Uploading available database bundles and logs as Actions debugging artifacts..." ); await uploadAllAvailableDebugArtifacts(codeql, config, logger2); - await printDebugLogs2(config); + await printDebugLogs2(logger2, config); } if (isSelfHostedRunner() && config.dbLocation) { try { diff --git a/src/actions-util.test.ts b/src/actions-util.test.ts index 1217e831a0..17cf96a4a5 100644 --- a/src/actions-util.test.ts +++ b/src/actions-util.test.ts @@ -12,7 +12,7 @@ import { import { computeAutomationID } from "./api-client"; import { EnvVar } from "./environment"; import { getRunnerLogger } from "./logging"; -import { setupTests } from "./testing-utils"; +import { RecordingLogger, setupTests } from "./testing-utils"; import { initializeEnvironment, withTmpDir } from "./util"; setupTests(test); @@ -102,15 +102,17 @@ test("computeAutomationID()", async (t) => { }); test("printDebugLogs - doesn't throw when config doesn't have needed properties", async (t) => { - await t.notThrowsAsync(printDebugLogs({})); - await t.notThrowsAsync(printDebugLogs({ languages: [] })); - await t.notThrowsAsync(printDebugLogs({ dbLocation: "nowhere" })); + const logger = new RecordingLogger(); + await t.notThrowsAsync(printDebugLogs(logger, {})); + await t.notThrowsAsync(printDebugLogs(logger, { languages: [] })); + await t.notThrowsAsync(printDebugLogs(logger, { dbLocation: "nowhere" })); }); test("printDebugLogs - doesn't throw when language db doesn't exist", async (t) => withTmpDir(async (tmpDir) => { + const logger = new RecordingLogger(); await t.notThrowsAsync( - printDebugLogs({ dbLocation: tmpDir, languages: ["javascript"] }), + printDebugLogs(logger, { dbLocation: tmpDir, languages: ["javascript"] }), ); })); diff --git a/src/actions-util.ts b/src/actions-util.ts index e43abbb0b9..62ab42440c 100644 --- a/src/actions-util.ts +++ b/src/actions-util.ts @@ -131,11 +131,14 @@ export function getWorkflowEvent(env: Env = getEnv()): any { } } -export async function printDebugLogs(config: Partial) { +export async function printDebugLogs(logger: Logger, config: Partial) { if ( !hasConfigKey(config, "languages") || !hasConfigKey(config, "dbLocation") ) { + logger.debug( + `Not printing debug logs, because the configuration does not include the 'languages' or 'dbLocation' fields.`, + ); return; } @@ -143,23 +146,23 @@ export async function printDebugLogs(config: Partial) { const databaseDirectory = getCodeQLDatabasePath(config, language); const logsDirectory = path.join(databaseDirectory, "log"); if (!doesDirectoryExist(logsDirectory)) { - core.info(`Directory ${logsDirectory} does not exist.`); + logger.info(`Directory ${logsDirectory} does not exist.`); continue; // Skip this language database. } const walkLogFiles = (dir: string) => { const entries = fs.readdirSync(dir, { withFileTypes: true }); if (entries.length === 0) { - core.info(`No debug logs found at directory ${logsDirectory}.`); + logger.info(`No debug logs found at directory ${logsDirectory}.`); } for (const entry of entries) { if (entry.isFile()) { const absolutePath = path.resolve(dir, entry.name); - core.startGroup( + logger.startGroup( `CodeQL Debug Logs - ${language} - ${entry.name} from file at path ${absolutePath}`, ); - process.stdout.write(fs.readFileSync(absolutePath)); - core.endGroup(); + logger.info(fs.readFileSync(absolutePath, "utf-8")); + logger.endGroup(); } else if (entry.isDirectory()) { walkLogFiles(path.resolve(dir, entry.name)); } diff --git a/src/init-action-post-helper.ts b/src/init-action-post-helper.ts index 3d3ffc5703..e9ebf93a3a 100644 --- a/src/init-action-post-helper.ts +++ b/src/init-action-post-helper.ts @@ -338,7 +338,7 @@ export async function uploadFailureInfo( config: UploadDebugArtifactsConfig, logger: Logger, ) => Promise, - printDebugLogs: (config: Partial) => Promise, + printDebugLogs: (logger: Logger, config: Partial) => Promise, codeql: CodeQL, config: Config | AbortedInitConfig, repositoryNwo: RepositoryNwo, @@ -392,7 +392,7 @@ export async function uploadFailureInfo( "Debug mode is on. Uploading available database bundles and logs as Actions debugging artifacts...", ); await uploadAllAvailableDebugArtifacts(codeql, config, logger); - await printDebugLogs(config); + await printDebugLogs(logger, config); } if (actionsUtil.isSelfHostedRunner() && config.dbLocation) { From 6a9f7e7fc3ed89bafa53a68079075ba34a2043f8 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 22:50:11 +0100 Subject: [PATCH 16/17] Log if returning early from `recordOverlayStatus` due to no languages --- lib/entry-points.js | 7 ++++++- src/init-action-post-helper.ts | 8 ++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 6003234204..8424aef54c 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -187034,7 +187034,12 @@ function isConclusiveJobStatus(jobStatus) { } } async function recordOverlayStatus(codeql, config, features, jobStatus, env, logger2) { - if (config.overlayDatabaseMode !== "overlay-base" /* OverlayBase */ || env.getOptional("CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY" /* ANALYZE_DID_COMPLETE_SUCCESSFULLY */) === "true" || !await features.getValue("overlay_analysis_status_save" /* OverlayAnalysisStatusSave */) || config.languages === void 0) { + if (config.overlayDatabaseMode !== "overlay-base" /* OverlayBase */ || env.getOptional("CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY" /* ANALYZE_DID_COMPLETE_SUCCESSFULLY */) === "true" || !await features.getValue("overlay_analysis_status_save" /* OverlayAnalysisStatusSave */)) { + return; + } else if (config.languages === void 0) { + logger2.debug( + "Not recording overlay status, because the configuration contains no 'languages'." + ); return; } if (!isConclusiveJobStatus(jobStatus) && !didCodeQlReportError(env)) { diff --git a/src/init-action-post-helper.ts b/src/init-action-post-helper.ts index e9ebf93a3a..b6aeb5bd34 100644 --- a/src/init-action-post-helper.ts +++ b/src/init-action-post-helper.ts @@ -471,10 +471,14 @@ async function recordOverlayStatus( if ( config.overlayDatabaseMode !== OverlayDatabaseMode.OverlayBase || env.getOptional(EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY) === "true" || - !(await features.getValue(Feature.OverlayAnalysisStatusSave)) || - config.languages === undefined + !(await features.getValue(Feature.OverlayAnalysisStatusSave)) ) { return; + } else if (config.languages === undefined) { + logger.debug( + "Not recording overlay status, because the configuration contains no 'languages'.", + ); + return; } // Only record a failure when the job outcome tells us something about the analysis. A cancelled From 9645acc2ffa222c2ee9640ed957226723f16e74f Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 22:54:47 +0100 Subject: [PATCH 17/17] Revert `config === undefined` condition --- lib/entry-points.js | 2 +- src/init-action-post.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 8424aef54c..373cdedc01 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -187165,7 +187165,7 @@ async function run4(action) { getTemporaryDirectory(), logger2 ); - if (config?.codeQLCmd === void 0) { + if (config === void 0) { logger2.warning( "Debugging artifacts are unavailable since the 'init' Action failed before it could produce any." ); diff --git a/src/init-action-post.ts b/src/init-action-post.ts index dd981c200a..689cfd4659 100644 --- a/src/init-action-post.ts +++ b/src/init-action-post.ts @@ -90,7 +90,7 @@ async function run(action: ActionState<["Base", "Logger", "Env"]>) { logger, ); - if (config?.codeQLCmd === undefined) { + if (config === undefined) { logger.warning( "Debugging artifacts are unavailable since the 'init' Action failed before it could produce any.", );