From cdb655d6d4cbc2686e6e01bc971017d495693931 Mon Sep 17 00:00:00 2001 From: Henry Mercer Date: Tue, 28 Apr 2026 11:29:32 +0100 Subject: [PATCH 1/4] Add random suffix when writing diagnostics to avoid filename collisions --- lib/analyze-action.js | 3 ++- lib/init-action-post.js | 3 ++- lib/init-action.js | 3 ++- lib/setup-codeql-action.js | 3 ++- lib/upload-lib.js | 3 ++- lib/upload-sarif-action.js | 3 ++- src/diagnostics.ts | 8 +++++++- 7 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/analyze-action.js b/lib/analyze-action.js index 712b2b62bd..020c41d205 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -107892,10 +107892,11 @@ function writeDiagnostic(config, language, diagnostic) { ); try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); + const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); const jsonPath = import_path.default.resolve( diagnosticsPath, // Remove colons from the timestamp as these are not allowed in Windows filenames. - `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}.json` + `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}-${uniqueSuffix}.json` ); (0, import_fs.writeFileSync)(jsonPath, JSON.stringify(diagnostic)); } catch (err) { diff --git a/lib/init-action-post.js b/lib/init-action-post.js index 59a13f6284..e401797e95 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -165811,10 +165811,11 @@ function writeDiagnostic(config, language, diagnostic) { ); try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); + const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); const jsonPath = import_path.default.resolve( diagnosticsPath, // Remove colons from the timestamp as these are not allowed in Windows filenames. - `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}.json` + `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}-${uniqueSuffix}.json` ); (0, import_fs.writeFileSync)(jsonPath, JSON.stringify(diagnostic)); } catch (err) { diff --git a/lib/init-action.js b/lib/init-action.js index 51f1eef91d..977240f215 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -105397,10 +105397,11 @@ function writeDiagnostic(config, language, diagnostic) { ); try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); + const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); const jsonPath = import_path.default.resolve( diagnosticsPath, // Remove colons from the timestamp as these are not allowed in Windows filenames. - `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}.json` + `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}-${uniqueSuffix}.json` ); (0, import_fs.writeFileSync)(jsonPath, JSON.stringify(diagnostic)); } catch (err) { diff --git a/lib/setup-codeql-action.js b/lib/setup-codeql-action.js index 58431548c9..48a206e8a1 100644 --- a/lib/setup-codeql-action.js +++ b/lib/setup-codeql-action.js @@ -105467,10 +105467,11 @@ function writeDiagnostic(config, language, diagnostic) { ); try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); + const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); const jsonPath = import_path.default.resolve( diagnosticsPath, // Remove colons from the timestamp as these are not allowed in Windows filenames. - `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}.json` + `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}-${uniqueSuffix}.json` ); (0, import_fs.writeFileSync)(jsonPath, JSON.stringify(diagnostic)); } catch (err) { diff --git a/lib/upload-lib.js b/lib/upload-lib.js index 60cd5fe570..faca0370ab 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -107502,10 +107502,11 @@ function writeDiagnostic(config, language, diagnostic) { ); try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); + const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); const jsonPath = import_path.default.resolve( diagnosticsPath, // Remove colons from the timestamp as these are not allowed in Windows filenames. - `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}.json` + `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}-${uniqueSuffix}.json` ); (0, import_fs.writeFileSync)(jsonPath, JSON.stringify(diagnostic)); } catch (err) { diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index aeaf1e7c63..d412f3547a 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -108258,10 +108258,11 @@ function writeDiagnostic(config, language, diagnostic) { ); try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); + const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); const jsonPath = import_path.default.resolve( diagnosticsPath, // Remove colons from the timestamp as these are not allowed in Windows filenames. - `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}.json` + `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}-${uniqueSuffix}.json` ); (0, import_fs.writeFileSync)(jsonPath, JSON.stringify(diagnostic)); } catch (err) { diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 4d8fc87b58..6b1911d05e 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -167,10 +167,16 @@ function writeDiagnostic( // Create the directory if it doesn't exist yet. mkdirSync(diagnosticsPath, { recursive: true }); + // Include a random suffix to avoid filename collisions between diagnostics + // produced within the same millisecond. This doesn't need to be + // cryptographically secure, so `Math.random` is fine. + const uniqueSuffix = Math.floor(Math.random() * 0x100000000) + .toString(16) + .padStart(8, "0"); const jsonPath = path.resolve( diagnosticsPath, // Remove colons from the timestamp as these are not allowed in Windows filenames. - `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}.json`, + `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}-${uniqueSuffix}.json`, ); writeFileSync(jsonPath, JSON.stringify(diagnostic)); From e73c940c9bfd79008a4b792d6fb46a19273cb59a Mon Sep 17 00:00:00 2001 From: Henry Mercer Date: Tue, 28 Apr 2026 11:33:48 +0100 Subject: [PATCH 2/4] Defensively sanitize timestamp --- lib/analyze-action.js | 7 +++++-- lib/init-action-post.js | 7 +++++-- lib/init-action.js | 7 +++++-- lib/setup-codeql-action.js | 7 +++++-- lib/upload-lib.js | 7 +++++-- lib/upload-sarif-action.js | 7 +++++-- src/diagnostics.ts | 9 +++++++-- 7 files changed, 37 insertions(+), 14 deletions(-) diff --git a/lib/analyze-action.js b/lib/analyze-action.js index 020c41d205..72da44c727 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -107893,10 +107893,13 @@ function writeDiagnostic(config, language, diagnostic) { try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); + const sanitizedTimestamp = diagnostic.timestamp.replace( + /[^a-zA-Z0-9.-]/g, + "" + ); const jsonPath = import_path.default.resolve( diagnosticsPath, - // Remove colons from the timestamp as these are not allowed in Windows filenames. - `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}-${uniqueSuffix}.json` + `codeql-action-${sanitizedTimestamp}-${uniqueSuffix}.json` ); (0, import_fs.writeFileSync)(jsonPath, JSON.stringify(diagnostic)); } catch (err) { diff --git a/lib/init-action-post.js b/lib/init-action-post.js index e401797e95..9a8363372a 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -165812,10 +165812,13 @@ function writeDiagnostic(config, language, diagnostic) { try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); + const sanitizedTimestamp = diagnostic.timestamp.replace( + /[^a-zA-Z0-9.-]/g, + "" + ); const jsonPath = import_path.default.resolve( diagnosticsPath, - // Remove colons from the timestamp as these are not allowed in Windows filenames. - `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}-${uniqueSuffix}.json` + `codeql-action-${sanitizedTimestamp}-${uniqueSuffix}.json` ); (0, import_fs.writeFileSync)(jsonPath, JSON.stringify(diagnostic)); } catch (err) { diff --git a/lib/init-action.js b/lib/init-action.js index 977240f215..3e779fe798 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -105398,10 +105398,13 @@ function writeDiagnostic(config, language, diagnostic) { try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); + const sanitizedTimestamp = diagnostic.timestamp.replace( + /[^a-zA-Z0-9.-]/g, + "" + ); const jsonPath = import_path.default.resolve( diagnosticsPath, - // Remove colons from the timestamp as these are not allowed in Windows filenames. - `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}-${uniqueSuffix}.json` + `codeql-action-${sanitizedTimestamp}-${uniqueSuffix}.json` ); (0, import_fs.writeFileSync)(jsonPath, JSON.stringify(diagnostic)); } catch (err) { diff --git a/lib/setup-codeql-action.js b/lib/setup-codeql-action.js index 48a206e8a1..0c492ed453 100644 --- a/lib/setup-codeql-action.js +++ b/lib/setup-codeql-action.js @@ -105468,10 +105468,13 @@ function writeDiagnostic(config, language, diagnostic) { try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); + const sanitizedTimestamp = diagnostic.timestamp.replace( + /[^a-zA-Z0-9.-]/g, + "" + ); const jsonPath = import_path.default.resolve( diagnosticsPath, - // Remove colons from the timestamp as these are not allowed in Windows filenames. - `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}-${uniqueSuffix}.json` + `codeql-action-${sanitizedTimestamp}-${uniqueSuffix}.json` ); (0, import_fs.writeFileSync)(jsonPath, JSON.stringify(diagnostic)); } catch (err) { diff --git a/lib/upload-lib.js b/lib/upload-lib.js index faca0370ab..c34c46ec77 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -107503,10 +107503,13 @@ function writeDiagnostic(config, language, diagnostic) { try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); + const sanitizedTimestamp = diagnostic.timestamp.replace( + /[^a-zA-Z0-9.-]/g, + "" + ); const jsonPath = import_path.default.resolve( diagnosticsPath, - // Remove colons from the timestamp as these are not allowed in Windows filenames. - `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}-${uniqueSuffix}.json` + `codeql-action-${sanitizedTimestamp}-${uniqueSuffix}.json` ); (0, import_fs.writeFileSync)(jsonPath, JSON.stringify(diagnostic)); } catch (err) { diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index d412f3547a..e4b8dbde70 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -108259,10 +108259,13 @@ function writeDiagnostic(config, language, diagnostic) { try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); + const sanitizedTimestamp = diagnostic.timestamp.replace( + /[^a-zA-Z0-9.-]/g, + "" + ); const jsonPath = import_path.default.resolve( diagnosticsPath, - // Remove colons from the timestamp as these are not allowed in Windows filenames. - `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}-${uniqueSuffix}.json` + `codeql-action-${sanitizedTimestamp}-${uniqueSuffix}.json` ); (0, import_fs.writeFileSync)(jsonPath, JSON.stringify(diagnostic)); } catch (err) { diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 6b1911d05e..ab9ca4a7a3 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -173,10 +173,15 @@ function writeDiagnostic( const uniqueSuffix = Math.floor(Math.random() * 0x100000000) .toString(16) .padStart(8, "0"); + // We should only need to remove colons, but to be defensive, only allow a restricted set of + // characters. + const sanitizedTimestamp = diagnostic.timestamp.replace( + /[^a-zA-Z0-9.-]/g, + "", + ); const jsonPath = path.resolve( diagnosticsPath, - // Remove colons from the timestamp as these are not allowed in Windows filenames. - `codeql-action-${diagnostic.timestamp.replaceAll(":", "")}-${uniqueSuffix}.json`, + `codeql-action-${sanitizedTimestamp}-${uniqueSuffix}.json`, ); writeFileSync(jsonPath, JSON.stringify(diagnostic)); From c109008fac92e5836f4c5914b9e93744216d279d Mon Sep 17 00:00:00 2001 From: Henry Mercer Date: Tue, 28 Apr 2026 11:35:30 +0100 Subject: [PATCH 3/4] Add changelog note --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d831f31be8..ff4e2d3bf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th ## [UNRELEASED] -No user facing changes. +- Fixed a bug where two diagnostics produced within the same millisecond could overwrite each other on disk, causing one of them to be lost. [#3852](https://github.com/github/codeql-action/pull/3852) ## 4.35.2 - 15 Apr 2026 From 245f6828c4b868031d4f50d96c64c536f031e265 Mon Sep 17 00:00:00 2001 From: Henry Mercer Date: Tue, 28 Apr 2026 12:42:42 +0100 Subject: [PATCH 4/4] Use a counter instead of Math.random for diagnostic filename suffix --- lib/analyze-action.js | 3 ++- lib/init-action-post.js | 3 ++- lib/init-action.js | 3 ++- lib/setup-codeql-action.js | 3 ++- lib/upload-lib.js | 3 ++- lib/upload-sarif-action.js | 3 ++- src/diagnostics.ts | 16 ++++++++++------ 7 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/analyze-action.js b/lib/analyze-action.js index 72da44c727..750a0e52de 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -107850,6 +107850,7 @@ function formatDuration(durationMs) { // src/diagnostics.ts var unwrittenDiagnostics = []; var unwrittenDefaultLanguageDiagnostics = []; +var diagnosticCounter = 0; function makeDiagnostic(id, name, data = void 0) { return { ...data, @@ -107892,7 +107893,7 @@ function writeDiagnostic(config, language, diagnostic) { ); try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); - const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); + const uniqueSuffix = (diagnosticCounter++).toString(); const sanitizedTimestamp = diagnostic.timestamp.replace( /[^a-zA-Z0-9.-]/g, "" diff --git a/lib/init-action-post.js b/lib/init-action-post.js index 9a8363372a..3f44bd4d33 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -165769,6 +165769,7 @@ function formatDuration(durationMs) { // src/diagnostics.ts var unwrittenDiagnostics = []; var unwrittenDefaultLanguageDiagnostics = []; +var diagnosticCounter = 0; function makeDiagnostic(id, name, data = void 0) { return { ...data, @@ -165811,7 +165812,7 @@ function writeDiagnostic(config, language, diagnostic) { ); try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); - const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); + const uniqueSuffix = (diagnosticCounter++).toString(); const sanitizedTimestamp = diagnostic.timestamp.replace( /[^a-zA-Z0-9.-]/g, "" diff --git a/lib/init-action.js b/lib/init-action.js index 3e779fe798..a3c7ab96bb 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -105355,6 +105355,7 @@ function formatDuration(durationMs) { // src/diagnostics.ts var unwrittenDiagnostics = []; var unwrittenDefaultLanguageDiagnostics = []; +var diagnosticCounter = 0; function makeDiagnostic(id, name, data = void 0) { return { ...data, @@ -105397,7 +105398,7 @@ function writeDiagnostic(config, language, diagnostic) { ); try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); - const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); + const uniqueSuffix = (diagnosticCounter++).toString(); const sanitizedTimestamp = diagnostic.timestamp.replace( /[^a-zA-Z0-9.-]/g, "" diff --git a/lib/setup-codeql-action.js b/lib/setup-codeql-action.js index 0c492ed453..64e6a317c4 100644 --- a/lib/setup-codeql-action.js +++ b/lib/setup-codeql-action.js @@ -105425,6 +105425,7 @@ function formatDuration(durationMs) { // src/diagnostics.ts var unwrittenDiagnostics = []; var unwrittenDefaultLanguageDiagnostics = []; +var diagnosticCounter = 0; function makeDiagnostic(id, name, data = void 0) { return { ...data, @@ -105467,7 +105468,7 @@ function writeDiagnostic(config, language, diagnostic) { ); try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); - const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); + const uniqueSuffix = (diagnosticCounter++).toString(); const sanitizedTimestamp = diagnostic.timestamp.replace( /[^a-zA-Z0-9.-]/g, "" diff --git a/lib/upload-lib.js b/lib/upload-lib.js index c34c46ec77..4bd41931bd 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -107460,6 +107460,7 @@ function formatDuration(durationMs) { // src/diagnostics.ts var unwrittenDiagnostics = []; var unwrittenDefaultLanguageDiagnostics = []; +var diagnosticCounter = 0; function makeDiagnostic(id, name, data = void 0) { return { ...data, @@ -107502,7 +107503,7 @@ function writeDiagnostic(config, language, diagnostic) { ); try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); - const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); + const uniqueSuffix = (diagnosticCounter++).toString(); const sanitizedTimestamp = diagnostic.timestamp.replace( /[^a-zA-Z0-9.-]/g, "" diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index e4b8dbde70..e6ca76b230 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -108216,6 +108216,7 @@ var import_fs = require("fs"); var import_path = __toESM(require("path")); var unwrittenDiagnostics = []; var unwrittenDefaultLanguageDiagnostics = []; +var diagnosticCounter = 0; function makeDiagnostic(id, name, data = void 0) { return { ...data, @@ -108258,7 +108259,7 @@ function writeDiagnostic(config, language, diagnostic) { ); try { (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); - const uniqueSuffix = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0"); + const uniqueSuffix = (diagnosticCounter++).toString(); const sanitizedTimestamp = diagnostic.timestamp.replace( /[^a-zA-Z0-9.-]/g, "" diff --git a/src/diagnostics.ts b/src/diagnostics.ts index ab9ca4a7a3..65e82ce1af 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -72,6 +72,13 @@ let unwrittenDiagnostics: UnwrittenDiagnostic[] = []; */ let unwrittenDefaultLanguageDiagnostics: DiagnosticMessage[] = []; +/** + * Counter used to generate a unique suffix for each diagnostic filename, so that + * two diagnostics produced within the same millisecond do not overwrite each + * other on disk. + */ +let diagnosticCounter = 0; + /** * Constructs a new diagnostic message with the specified id and name, as well as optional additional data. * @@ -167,12 +174,9 @@ function writeDiagnostic( // Create the directory if it doesn't exist yet. mkdirSync(diagnosticsPath, { recursive: true }); - // Include a random suffix to avoid filename collisions between diagnostics - // produced within the same millisecond. This doesn't need to be - // cryptographically secure, so `Math.random` is fine. - const uniqueSuffix = Math.floor(Math.random() * 0x100000000) - .toString(16) - .padStart(8, "0"); + // Include a monotonically increasing suffix to avoid filename collisions + // between diagnostics produced within the same millisecond. + const uniqueSuffix = (diagnosticCounter++).toString(); // We should only need to remove colons, but to be defensive, only allow a restricted set of // characters. const sanitizedTimestamp = diagnostic.timestamp.replace(