freebuff 0.0.142 → 0.0.145
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/launcher.js +231 -13
- package/package.json +1 -1
package/launcher.js
CHANGED
|
@@ -72,6 +72,16 @@ function createLauncher(productConfig) {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* How long a binary has to survive before a crash stops looking like a
|
|
77
|
+
* startup failure. Used to bound the STATUS_STACK_BUFFER_OVERRUN heuristic
|
|
78
|
+
* below, which is only trustworthy for deaths during startup.
|
|
79
|
+
*/
|
|
80
|
+
const STARTUP_CRASH_WINDOW_MS = 10000
|
|
81
|
+
|
|
82
|
+
/** Bytes of the binary's stderr kept for the crash report. */
|
|
83
|
+
const STDERR_TAIL_BYTES = 8192
|
|
84
|
+
|
|
75
85
|
function getUnsignedExitCode(code) {
|
|
76
86
|
return code != null && code < 0 ? code >>> 0 : code
|
|
77
87
|
}
|
|
@@ -98,6 +108,34 @@ function createLauncher(productConfig) {
|
|
|
98
108
|
)
|
|
99
109
|
}
|
|
100
110
|
|
|
111
|
+
/**
|
|
112
|
+
* A startup death that is probably this machine failing to run the optimized
|
|
113
|
+
* build, reported under the *other* Windows spelling.
|
|
114
|
+
*
|
|
115
|
+
* Bun is written in Zig, and a Zig panic on Windows reports itself with
|
|
116
|
+
* __fastfail(FAST_FAIL_FATAL_APP_EXIT) — NTSTATUS 0xC0000409
|
|
117
|
+
* (STATUS_STACK_BUFFER_OVERRUN, exit code 3221226505), not
|
|
118
|
+
* STATUS_ILLEGAL_INSTRUCTION. So a CPU without AVX2 does not reliably die on
|
|
119
|
+
* the illegal instruction itself: Bun detects the missing feature, then
|
|
120
|
+
* panics while starting up anyway (oven-sh/bun#28399), and the crash arrives
|
|
121
|
+
* as 0xC0000409. Only the SIGILL spelling was wired to the baseline
|
|
122
|
+
* fallback, which is why these machines crash-looped forever.
|
|
123
|
+
*
|
|
124
|
+
* On its own 0xC0000409 only means "the binary aborted", so this is bounded
|
|
125
|
+
* to deaths during startup. A panic ten minutes into a session is an
|
|
126
|
+
* ordinary bug, and reading it as a missing instruction set would send a
|
|
127
|
+
* perfectly capable machine to the slower build.
|
|
128
|
+
*/
|
|
129
|
+
function isStartupCpuFeatureCrash(code, signal, msAlive) {
|
|
130
|
+
return (
|
|
131
|
+
process.platform === 'win32' &&
|
|
132
|
+
getUnsignedExitCode(code) === 0xc0000409 &&
|
|
133
|
+
!signal &&
|
|
134
|
+
typeof msAlive === 'number' &&
|
|
135
|
+
msAlive < STARTUP_CRASH_WINDOW_MS
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
101
139
|
function createConfig(packageName) {
|
|
102
140
|
const homeDir = os.homedir()
|
|
103
141
|
const configDir =
|
|
@@ -933,6 +971,16 @@ function createLauncher(productConfig) {
|
|
|
933
971
|
}
|
|
934
972
|
|
|
935
973
|
async function checkForUpdates(runningProcess, exitListener) {
|
|
974
|
+
// main() schedules this 100ms after launch, so the binary it was handed can
|
|
975
|
+
// already be dead — a startup crash that handed off to the baseline
|
|
976
|
+
// fallback, most of all. Updating around a corpse would race that
|
|
977
|
+
// relaunch's download for the shared temp directory (prepareTempDownloadDir
|
|
978
|
+
// rmSyncs it) and then spend six seconds SIGKILLing a process that has
|
|
979
|
+
// already exited.
|
|
980
|
+
if (runningProcess.exitCode !== null || runningProcess.signalCode !== null) {
|
|
981
|
+
return
|
|
982
|
+
}
|
|
983
|
+
|
|
936
984
|
let stoppedForUpdate = false
|
|
937
985
|
|
|
938
986
|
try {
|
|
@@ -991,7 +1039,32 @@ function createLauncher(productConfig) {
|
|
|
991
1039
|
}
|
|
992
1040
|
}
|
|
993
1041
|
|
|
994
|
-
|
|
1042
|
+
/**
|
|
1043
|
+
* Make captured output safe to print back to the terminal.
|
|
1044
|
+
*
|
|
1045
|
+
* The tail is replayed *after* resetTerminal() has put the terminal back in
|
|
1046
|
+
* order, so it must not be able to undo that: a stray \x1b[?1049h or
|
|
1047
|
+
* \x1b[?1003h in what the binary printed would re-enter the alternate screen
|
|
1048
|
+
* or re-enable mouse reporting, hiding the very report it is part of. Strip
|
|
1049
|
+
* the escapes and keep the words.
|
|
1050
|
+
*/
|
|
1051
|
+
function sanitizeForReplay(text) {
|
|
1052
|
+
if (!text) return ''
|
|
1053
|
+
return (
|
|
1054
|
+
text
|
|
1055
|
+
.replace(/\r\n?/g, '\n')
|
|
1056
|
+
// CSI sequences — the ones that actually change terminal state.
|
|
1057
|
+
.replace(/\x1b\[[0-9;:?<>=]*[ -/]*[@-~]/g, '')
|
|
1058
|
+
// Everything else: strip the control bytes and keep the text. An OSC
|
|
1059
|
+
// or a lone escape loses its introducer and degrades to inert
|
|
1060
|
+
// characters, which is all a crash report needs it to be.
|
|
1061
|
+
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '')
|
|
1062
|
+
.replace(/\s+$/, '')
|
|
1063
|
+
)
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function printCrashDiagnostics(code, signal, context = {}) {
|
|
1067
|
+
const { msAlive = null, stderrTail = '' } = context
|
|
995
1068
|
// Windows NTSTATUS codes (unsigned DWORD)
|
|
996
1069
|
const unsignedCode = getUnsignedExitCode(code)
|
|
997
1070
|
const isIllegalInstruction = isIllegalInstructionExit(code, signal)
|
|
@@ -1026,11 +1099,46 @@ function createLauncher(productConfig) {
|
|
|
1026
1099
|
console.error('This may indicate a platform compatibility issue.')
|
|
1027
1100
|
console.error('')
|
|
1028
1101
|
} else if (isAbort) {
|
|
1029
|
-
|
|
1102
|
+
const startupCpuCrash = isStartupCpuFeatureCrash(code, signal, msAlive)
|
|
1103
|
+
console.error(
|
|
1104
|
+
startupCpuCrash
|
|
1105
|
+
? 'The binary aborted while starting up.'
|
|
1106
|
+
: 'The binary crashed with an abort signal.',
|
|
1107
|
+
)
|
|
1030
1108
|
console.error('')
|
|
1109
|
+
// Reached only once the baseline fallback has declined or failed, so name
|
|
1110
|
+
// the case the user can still act on instead of leaving them with an
|
|
1111
|
+
// abort code and a bug-tracker link.
|
|
1112
|
+
if (startupCpuCrash) {
|
|
1113
|
+
if (getCurrentMetadata()?.target === getBaselineFallbackTargetKey()) {
|
|
1114
|
+
console.error(
|
|
1115
|
+
'This is already the older-CPU (baseline) build, so a missing AVX2',
|
|
1116
|
+
)
|
|
1117
|
+
console.error(
|
|
1118
|
+
'instruction set is not the whole story — please include the output',
|
|
1119
|
+
)
|
|
1120
|
+
console.error('below in your report.')
|
|
1121
|
+
console.error('')
|
|
1122
|
+
} else {
|
|
1123
|
+
console.error(
|
|
1124
|
+
'On x64 Windows this is usually a CPU without AVX2 support, which',
|
|
1125
|
+
)
|
|
1126
|
+
console.error('the standard build requires.')
|
|
1127
|
+
console.error('')
|
|
1128
|
+
printBaselineOverrideHint()
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1031
1131
|
}
|
|
1032
1132
|
|
|
1033
1133
|
printSystemInfo()
|
|
1134
|
+
const tailText = sanitizeForReplay(stderrTail)
|
|
1135
|
+
if (tailText) {
|
|
1136
|
+
console.error('')
|
|
1137
|
+
console.error(`Last output from ${packageName}:`)
|
|
1138
|
+
for (const line of tailText.split('\n')) {
|
|
1139
|
+
console.error(` ${line}`)
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1034
1142
|
console.error('')
|
|
1035
1143
|
console.error('Please report this issue at:')
|
|
1036
1144
|
console.error(' https://github.com/CodebuffAI/codebuff/issues')
|
|
@@ -1047,13 +1155,28 @@ function createLauncher(productConfig) {
|
|
|
1047
1155
|
console.error('')
|
|
1048
1156
|
}
|
|
1049
1157
|
|
|
1158
|
+
/**
|
|
1159
|
+
* What we actually know about AVX2, not what we assume.
|
|
1160
|
+
*
|
|
1161
|
+
* The old line printed a flat "yes" from machineHasAvx2(), which on Windows
|
|
1162
|
+
* is an optimistic default and not a measurement (see detectMachineHasAvx2).
|
|
1163
|
+
* Every crash report that reached us therefore claimed AVX2 was present on
|
|
1164
|
+
* machines we had never asked, which is exactly the evidence that would have
|
|
1165
|
+
* pointed at the CPU.
|
|
1166
|
+
*/
|
|
1167
|
+
function describeAvx2Support() {
|
|
1168
|
+
if (readCachedAvx2() === false) return 'no (recorded crash)'
|
|
1169
|
+
if (process.platform === 'linux') return machineHasAvx2() ? 'yes' : 'no'
|
|
1170
|
+
return 'not checked (assumed present)'
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1050
1173
|
function printSystemInfo() {
|
|
1051
1174
|
const metadata = getCurrentMetadata()
|
|
1052
1175
|
console.error('System info:')
|
|
1053
1176
|
console.error(` Platform: ${process.platform} ${process.arch}`)
|
|
1054
1177
|
console.error(` Node: ${process.version}`)
|
|
1055
1178
|
if (process.arch === 'x64') {
|
|
1056
|
-
console.error(` AVX2: ${
|
|
1179
|
+
console.error(` AVX2: ${describeAvx2Support()}`)
|
|
1057
1180
|
}
|
|
1058
1181
|
console.error(` Target: ${metadata?.target || getDefaultTargetKey()}`)
|
|
1059
1182
|
console.error(` Binary: ${CONFIG.binaryPath}`)
|
|
@@ -1124,7 +1247,12 @@ function createLauncher(productConfig) {
|
|
|
1124
1247
|
try {
|
|
1125
1248
|
const { env: optionEnv, ...spawnOptions } = options
|
|
1126
1249
|
child = spawn(CONFIG.binaryPath, process.argv.slice(2), {
|
|
1127
|
-
|
|
1250
|
+
// stdin/stdout stay inherited — the TUI owns the terminal and must see
|
|
1251
|
+
// a real tty. stderr is teed on Windows only; see watchLaunch.
|
|
1252
|
+
stdio:
|
|
1253
|
+
process.platform === 'win32'
|
|
1254
|
+
? ['inherit', 'inherit', 'pipe']
|
|
1255
|
+
: ['inherit', 'inherit', 'inherit'],
|
|
1128
1256
|
...spawnOptions,
|
|
1129
1257
|
env: {
|
|
1130
1258
|
...process.env,
|
|
@@ -1137,12 +1265,72 @@ function createLauncher(productConfig) {
|
|
|
1137
1265
|
}
|
|
1138
1266
|
|
|
1139
1267
|
child.on('error', exitOnSpawnFailure)
|
|
1268
|
+
child.launch = watchLaunch(child)
|
|
1140
1269
|
|
|
1141
1270
|
return child
|
|
1142
1271
|
}
|
|
1143
1272
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1273
|
+
/**
|
|
1274
|
+
* Watch a launch so its death can be explained: how long the binary lived,
|
|
1275
|
+
* and what it printed on the way out.
|
|
1276
|
+
*
|
|
1277
|
+
* The stderr tee is what makes a crash report self-contained. When the binary
|
|
1278
|
+
* dies natively we reset the terminal, and that reset leaves the alternate
|
|
1279
|
+
* screen with \x1b[?1049l — discarding its contents, including any panic Bun
|
|
1280
|
+
* printed there. (The CLI's own terminal watchdog sends the same sequence, so
|
|
1281
|
+
* dropping it here wouldn't help.) Every report of codebuff#792 is a launcher
|
|
1282
|
+
* message with no panic text above it; keeping a copy means the next one
|
|
1283
|
+
* arrives with Bun's own words in it.
|
|
1284
|
+
*
|
|
1285
|
+
* Only Windows pipes stderr — that's where the reports come from, and
|
|
1286
|
+
* everywhere else stderr stays a real tty.
|
|
1287
|
+
*/
|
|
1288
|
+
function watchLaunch(child) {
|
|
1289
|
+
const startedAt = Date.now()
|
|
1290
|
+
const chunks = []
|
|
1291
|
+
let bufferedBytes = 0
|
|
1292
|
+
|
|
1293
|
+
child.stderr?.on('data', (chunk) => {
|
|
1294
|
+
try {
|
|
1295
|
+
// Straight through: as far as the binary and the user are concerned,
|
|
1296
|
+
// this is still its terminal.
|
|
1297
|
+
process.stderr.write(chunk)
|
|
1298
|
+
} catch {
|
|
1299
|
+
// stderr may be closed
|
|
1300
|
+
}
|
|
1301
|
+
chunks.push(chunk)
|
|
1302
|
+
bufferedBytes += chunk.length
|
|
1303
|
+
while (bufferedBytes > STDERR_TAIL_BYTES && chunks.length > 1) {
|
|
1304
|
+
bufferedBytes -= chunks.shift().length
|
|
1305
|
+
}
|
|
1306
|
+
})
|
|
1307
|
+
|
|
1308
|
+
return {
|
|
1309
|
+
msAlive: () => Date.now() - startedAt,
|
|
1310
|
+
stderrTail: () =>
|
|
1311
|
+
Buffer.concat(chunks).toString('utf8').slice(-STDERR_TAIL_BYTES),
|
|
1312
|
+
// 'exit' can beat the last bytes through the pipe; 'close' is the event
|
|
1313
|
+
// that means the stdio is drained too. The timeout is a bound on a stuck
|
|
1314
|
+
// pipe and is deliberately NOT unref'd — an unref'd timer lets the
|
|
1315
|
+
// process exit before it fires, which would swallow the crash report
|
|
1316
|
+
// entirely. Already-drained streams resolve without waiting for it.
|
|
1317
|
+
drained: () =>
|
|
1318
|
+
child.stderr && !child.stderr.readableEnded
|
|
1319
|
+
? new Promise((resolve) => {
|
|
1320
|
+
child.once('close', resolve)
|
|
1321
|
+
child.stderr.once('end', resolve)
|
|
1322
|
+
setTimeout(resolve, 250)
|
|
1323
|
+
})
|
|
1324
|
+
: Promise.resolve(),
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
async function tryFallbackToBaseline(code, signal, msAlive) {
|
|
1329
|
+
// Two spellings of one failure, at two levels of certainty. SIGILL /
|
|
1330
|
+
// STATUS_ILLEGAL_INSTRUCTION is proof this CPU cannot run this build; a
|
|
1331
|
+
// Windows startup abort is a strong suspicion (see isStartupCpuFeatureCrash).
|
|
1332
|
+
const confirmed = isIllegalInstructionExit(code, signal)
|
|
1333
|
+
if (!confirmed && !isStartupCpuFeatureCrash(code, signal, msAlive)) {
|
|
1146
1334
|
return false
|
|
1147
1335
|
}
|
|
1148
1336
|
|
|
@@ -1151,16 +1339,27 @@ function createLauncher(productConfig) {
|
|
|
1151
1339
|
return false
|
|
1152
1340
|
}
|
|
1153
1341
|
|
|
1342
|
+
// An explicit target is the user's decision; don't download over it.
|
|
1343
|
+
if (getTargetOverride()) {
|
|
1344
|
+
return false
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1154
1347
|
const metadata = getCurrentMetadata()
|
|
1155
1348
|
const currentTarget = metadata?.target || getDefaultTargetKey()
|
|
1156
1349
|
if (currentTarget === fallbackTarget) {
|
|
1157
1350
|
return false
|
|
1158
1351
|
}
|
|
1159
1352
|
|
|
1160
|
-
//
|
|
1161
|
-
//
|
|
1162
|
-
// or the relaunch fails we never optimistically pick the AVX2
|
|
1163
|
-
|
|
1353
|
+
// Only a confirmed illegal instruction gets written down. Persisting it
|
|
1354
|
+
// before the download is what caps the cost at one crash: even if the
|
|
1355
|
+
// download or the relaunch fails, we never optimistically pick the AVX2
|
|
1356
|
+
// build again. A suspected crash records nothing — installing the baseline
|
|
1357
|
+
// already keeps this machine on it, so a guess that turns out to be wrong
|
|
1358
|
+
// costs the slower build instead of leaving cpu-features.json asserting a
|
|
1359
|
+
// CPU limitation we never observed.
|
|
1360
|
+
if (confirmed) {
|
|
1361
|
+
recordMachineLacksAvx2()
|
|
1362
|
+
}
|
|
1164
1363
|
|
|
1165
1364
|
const version = metadata?.version || (await getLatestVersion())
|
|
1166
1365
|
if (!version) {
|
|
@@ -1172,7 +1371,9 @@ function createLauncher(productConfig) {
|
|
|
1172
1371
|
})
|
|
1173
1372
|
console.error('')
|
|
1174
1373
|
console.error(
|
|
1175
|
-
|
|
1374
|
+
confirmed
|
|
1375
|
+
? `${packageName} is switching to the older-CPU binary for this machine.`
|
|
1376
|
+
: `${packageName} crashed on startup; trying the older-CPU binary.`,
|
|
1176
1377
|
)
|
|
1177
1378
|
|
|
1178
1379
|
try {
|
|
@@ -1190,9 +1391,20 @@ function createLauncher(productConfig) {
|
|
|
1190
1391
|
|
|
1191
1392
|
function attachExitHandler(child, allowBaselineFallback = true) {
|
|
1192
1393
|
const exitListener = async (code, signal) => {
|
|
1394
|
+
// A child we never watched (only reachable from a test) reports no age
|
|
1395
|
+
// rather than a suspiciously young one: absent evidence must not be read
|
|
1396
|
+
// as a startup crash and trigger a download.
|
|
1397
|
+
const msAlive = child.launch ? child.launch.msAlive() : Infinity
|
|
1398
|
+
|
|
1399
|
+
let stderrTail = ''
|
|
1400
|
+
if (child.launch && (isWindowsNativeCrashCode(code) || signal)) {
|
|
1401
|
+
await child.launch.drained()
|
|
1402
|
+
stderrTail = child.launch.stderrTail()
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1193
1405
|
if (
|
|
1194
1406
|
allowBaselineFallback &&
|
|
1195
|
-
(await tryFallbackToBaseline(code, signal))
|
|
1407
|
+
(await tryFallbackToBaseline(code, signal, msAlive))
|
|
1196
1408
|
) {
|
|
1197
1409
|
return
|
|
1198
1410
|
}
|
|
@@ -1200,7 +1412,7 @@ function createLauncher(productConfig) {
|
|
|
1200
1412
|
resetTerminal({
|
|
1201
1413
|
exitAlternateScreen: shouldExitAlternateScreen(code, signal),
|
|
1202
1414
|
})
|
|
1203
|
-
printCrashDiagnostics(code, signal)
|
|
1415
|
+
printCrashDiagnostics(code, signal, { msAlive, stderrTail })
|
|
1204
1416
|
process.exit(signal ? 1 : code || 0)
|
|
1205
1417
|
}
|
|
1206
1418
|
|
|
@@ -1236,6 +1448,12 @@ function createLauncher(productConfig) {
|
|
|
1236
1448
|
recordMachineLacksAvx2,
|
|
1237
1449
|
readCachedAvx2,
|
|
1238
1450
|
isIllegalInstructionExit,
|
|
1451
|
+
isStartupCpuFeatureCrash,
|
|
1452
|
+
tryFallbackToBaseline,
|
|
1453
|
+
printCrashDiagnostics,
|
|
1454
|
+
checkForUpdates,
|
|
1455
|
+
spawnInstalledBinary,
|
|
1456
|
+
attachExitHandler,
|
|
1239
1457
|
getDefaultTargetKey,
|
|
1240
1458
|
getCpuFeatureCachePath,
|
|
1241
1459
|
getCurrentVersion,
|