Describe the bug
extractSourcemapFromFile calls convertSourceMap.fromSource() without a try/catch:
// packages/utils/src/source-map.ts (dist/source-map/node.js)
function extractSourcemapFromFile(code, filePath) {
const map = (convertSourceMap.fromSource(code)
|| convertSourceMap.fromMapFileSource(code, createConvertSourceMapReadMap(filePath)))?.toObject();
return map ? { map } : undefined;
}
fromSource() throws when a file contains something that looks like an inline source map but is not one. Its regex matches sourceMappingURL=data:application/json;base64, anywhere at end-of-line, including inside a string literal in minified code. It then base64-decodes the remainder of the file and JSON.parses the result:
SyntaxError: Unexpected token '<binary>', "<binary>" is not valid JSON
at new Converter (convert-source-map/index.js:83:15)
at Object.exports.fromComment (convert-source-map/index.js:181:10)
at Object.exports.fromSource (convert-source-map/index.js:207:22)
at extractSourcemapFromFile (@vitest/utils/dist/source-map/node.js:8:32)
at Object.getSourceMap (vitest/dist/chunks/cli-api...js)
at parseStacktrace (@vitest/utils/dist/source-map.js:387:16)
at parseErrorStacktrace (@vitest/utils/dist/source-map.js:438:51)
This is reached from parseErrorStacktrace, so it fires while Vitest is formatting an error. Nothing awaits it, so it surfaces as an unhandled rejection and exits the run with code 1 — and it discards the error it was trying to print.
Impact
The failure mode is badly misleading:
- The run exits non-zero, so CI is red.
- Every test passed. Our JUnit output read
tests 1388, failures 0, errors 0, skipped 0, and the CI server's own test API agreed: total 1388, passed 1388, failed 0.
- The console shows
Errors 2 and two Test Run Error blocks whose stacks contain only Vitest internals. No test is ever named, because the error that would have named one is the thing that got swallowed.
- It only reproduces where the offending file ends up in a stack frame, so it can be environment-specific. Ours was green on macOS and red on Linux CI for days.
The arithmetic is the only real tell: 1384 passed (1388) next to failures 0. Those reconcile only if four tests never ran, i.e. the runner died rather than a test failing.
Reproduction
tsx triggers it. Its minified register bundles contain that literal string (unsurprisingly — it is a loader that writes source-map comments), so any project using tsx as a setupFile or loader can hit it as soon as an error is formatted with a tsx frame on the stack.
Minimal, two dependencies, no test runner needed:
npm init -y
npm i convert-source-map@2.0.0 tsx@4.23.11
const fs = require('fs'), path = require('path');
const conv = require('convert-source-map');
const dist = 'node_modules/tsx/dist';
for (const f of fs.readdirSync(dist)) {
if (!/\.(cjs|mjs|js)$/.test(f)) continue;
const code = fs.readFileSync(path.join(dist, f), 'utf8');
if (!/sourceMappingURL=data:/.test(code)) continue;
try { conv.fromSource(code); }
catch (e) { console.log('THROWS', f, '->', e.message); }
}
Output:
THROWS register-CeSc3Ioe.cjs -> Unexpected token '<binary>', "<binary>" is not valid JSON
THROWS register-DyKhpi9B.mjs -> Unexpected token '<binary>', "<binary>" is not valid JSON
Not specific to one release: reproduced on tsx 4.23.1, 4.23.6 and 4.23.11, under different content-hashed filenames each time.
Suggested fix
A source map is an optional nicety. Failing to read one should degrade to "no source map", never take down the run:
function extractSourcemapFromFile(code, filePath) {
try {
const map = (convertSourceMap.fromSource(code)
|| convertSourceMap.fromMapFileSource(code, createConvertSourceMapReadMap(filePath)))?.toObject();
return map ? { map } : undefined;
} catch {
return undefined;
}
}
That returns exactly what a file with no source map returns. createConvertSourceMapReadMap already has a comment about falling back to an empty source map "to avoid errors" for an invalid filename, so the surrounding code clearly intends this call to be non-fatal; it just does not cover a malformed payload.
Worth considering as well: onStackTrace looks like it should let a user filter these frames out, but it does not help here, because the source map is read before the filter is applied.
I have been running the try/catch above as a package patch for a day across a 1388-test suite with no ill effects, and am happy to open a PR if useful.
Version
vitest 4.1.10, @vitest/utils 4.1.10, convert-source-map 2.0.0, Node 24.
Describe the bug
extractSourcemapFromFilecallsconvertSourceMap.fromSource()without atry/catch:fromSource()throws when a file contains something that looks like an inline source map but is not one. Its regex matchessourceMappingURL=data:application/json;base64,anywhere at end-of-line, including inside a string literal in minified code. It then base64-decodes the remainder of the file andJSON.parses the result:This is reached from
parseErrorStacktrace, so it fires while Vitest is formatting an error. Nothing awaits it, so it surfaces as an unhandled rejection and exits the run with code 1 — and it discards the error it was trying to print.Impact
The failure mode is badly misleading:
tests 1388, failures 0, errors 0, skipped 0, and the CI server's own test API agreed:total 1388, passed 1388, failed 0.Errors 2and twoTest Run Errorblocks whose stacks contain only Vitest internals. No test is ever named, because the error that would have named one is the thing that got swallowed.The arithmetic is the only real tell:
1384 passed (1388)next tofailures 0. Those reconcile only if four tests never ran, i.e. the runner died rather than a test failing.Reproduction
tsxtriggers it. Its minified register bundles contain that literal string (unsurprisingly — it is a loader that writes source-map comments), so any project usingtsxas asetupFileor loader can hit it as soon as an error is formatted with atsxframe on the stack.Minimal, two dependencies, no test runner needed:
Output:
Not specific to one release: reproduced on tsx 4.23.1, 4.23.6 and 4.23.11, under different content-hashed filenames each time.
Suggested fix
A source map is an optional nicety. Failing to read one should degrade to "no source map", never take down the run:
That returns exactly what a file with no source map returns.
createConvertSourceMapReadMapalready has a comment about falling back to an empty source map "to avoid errors" for an invalid filename, so the surrounding code clearly intends this call to be non-fatal; it just does not cover a malformed payload.Worth considering as well:
onStackTracelooks like it should let a user filter these frames out, but it does not help here, because the source map is read before the filter is applied.I have been running the
try/catchabove as a package patch for a day across a 1388-test suite with no ill effects, and am happy to open a PR if useful.Version
vitest4.1.10,@vitest/utils4.1.10,convert-source-map2.0.0, Node 24.