From 0a8da47f401bc707f6b35e03e2614c3f2187abcd Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Sat, 29 Aug 2026 07:08:08 -0700 Subject: [PATCH] fix(ci): surface what Oxlint printed when the changed-code gate cannot parse it Oxlint writes configuration failures to stdout, and the slice from the first brace to the last one takes a wrapper's own warning for the report, so the gate died with "did not return Oxlint JSON output" and discarded the only explanation of why. --- config/scripts/check-changed-code-quality.mjs | 15 +++++++++++--- .../check-changed-code-quality.test.mjs | 20 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/config/scripts/check-changed-code-quality.mjs b/config/scripts/check-changed-code-quality.mjs index c2f9619ecfd..adf9c9f365a 100644 --- a/config/scripts/check-changed-code-quality.mjs +++ b/config/scripts/check-changed-code-quality.mjs @@ -115,10 +115,19 @@ export function collectAddedLineRanges(root, requestedBase) { function parseOxlintOutput(stdout, label) { const start = stdout.indexOf('{') const end = stdout.lastIndexOf('}') - if (start === -1 || end === -1) { - throw new Error(`${label} did not return Oxlint JSON output.`) + if (start !== -1 && end !== -1) { + try { + return JSON.parse(stdout.slice(start, end + 1)) + } catch { + // Fall through so the caller sees what Oxlint actually printed. + } } - return JSON.parse(stdout.slice(start, end + 1)) + // Why echo it: Oxlint writes configuration failures to stdout, and a wrapper's own + // warning can carry braces that this slice mistakes for the report, so discarding the + // output leaves the gate dying with no reason anywhere in the log. + throw new Error( + `${label} did not return Oxlint JSON output. Oxlint printed:\n${stdout.trim().slice(0, 2000)}` + ) } function normalizedDiagnosticPath(root, filename) { diff --git a/config/scripts/check-changed-code-quality.test.mjs b/config/scripts/check-changed-code-quality.test.mjs index c2a2c2004ce..e85ddc8f880 100644 --- a/config/scripts/check-changed-code-quality.test.mjs +++ b/config/scripts/check-changed-code-quality.test.mjs @@ -276,6 +276,26 @@ describe('diagnostic collection across batches', () => { ]) }) + // Why: Oxlint writes configuration failures to stdout, so swallowing it leaves the gate + // dying with no reason in the log. + it('surfaces what Oxlint printed when the output is not a report', () => { + const failure = 'Failed to parse oxlint configuration file.\n\n x Rule not found\n' + + expect(() => runOxlintScan('/repo', scan, ['src/a.ts'], () => failure)).toThrow( + /Rule not found/ + ) + }) + + // Why: the slice from the first brace to the last one takes a wrapper's own warning for + // the report, and the raw SyntaxError names neither the scan nor the warning. + it('surfaces a wrapper warning whose braces shadow the report', () => { + const polluted = ` WARN Unsupported engine: wanted: {"node":"24"}\n${JSON.stringify({ diagnostics: [] })}` + + expect(() => runOxlintScan('/repo', scan, ['src/a.ts'], () => polluted)).toThrow( + /Unsupported engine/ + ) + }) + // Why: the whole point is that no single invocation carries the full argument list. it('never hands the whole oversized set to one invocation', () => { const batchSizes = observeBatches().map((batch) => batch.length)