You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(cli): align utils/dlx/ error messages with 4-ingredient strategy
Rewrites error messages across packages/cli/src/utils/dlx/ to follow
the What / Where / Saw vs. wanted / Fix strategy from CLAUDE.md.
Sources:
- spawn.mts: 27 messages
- 6x 'Unexpected resolution type for <tool>' — now name the resolver
function and the actual resolution.type seen
- Archive/platform errors name the supported formats/platforms
- Python DLX errors surface the lock file path and cache dir
- PyPI fetch errors include the URL that failed
- Security errors (zip-slip, symlink escape) tell user to delete
the cached asset and report upstream
- resolve-binary.mts: 4 messages (socket-patch, trivy, trufflehog,
opengrep platform support) — each now lists supported platforms
and suggests how to install the tool manually
- vfs-extract.mts: 5 messages (SEA VFS extraction failures) — each
names what went wrong with the bundle and how to recover
(usually: rebuild SEA)
Internal invariant errors stay as plain Error (not InputError) but
are informative enough that if they ever fire, the user can open
a useful bug report.
Tests updated: test/unit/utils/dlx/resolve-binary.test.mts (1
substring match switched to regex).
Follows strategy from #1254. Part of the multi-PR series started
by #1255 (commands/).
`socket-patch has no prebuilt binary for "${platformKey}" (supported: ${Object.keys(SOCKET_PATCH_ASSETS).join(', ')}); upgrade socket-cli, build socket-patch from source, or set SOCKET_CLI_SOCKET_PATCH_LOCAL_PATH to point at a local build`,
172
171
)
173
172
}
174
173
@@ -246,8 +245,7 @@ export function resolveTrivy(): BinaryResolution {
246
245
constplatform=os.platform()
247
246
constarch=os.arch()
248
247
thrownewError(
249
-
`Trivy is not available for platform ${platform}-${arch}. `+
`Trivy has no prebuilt binary for "${platform}-${arch}" (supported: darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-x64); run socket-cli on a supported platform or install Trivy manually and point \`trivy\` at it on PATH`,
251
249
)
252
250
}
253
251
@@ -310,8 +308,7 @@ export function resolveTrufflehog(): BinaryResolution {
310
308
constplatform=os.platform()
311
309
constarch=os.arch()
312
310
thrownewError(
313
-
`TruffleHog is not available for platform ${platform}-${arch}. `+
`TruffleHog has no prebuilt binary for "${platform}-${arch}" (supported: darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-arm64, win32-x64); run socket-cli on a supported platform or install TruffleHog manually and point \`trufflehog\` at it on PATH`,
315
312
)
316
313
}
317
314
@@ -363,8 +360,7 @@ export function resolveOpengrep(): BinaryResolution {
363
360
364
361
if(!assetName){
365
362
thrownewError(
366
-
`OpenGrep is not available for platform ${platformKey}. `+
`OpenGrep has no prebuilt binary for "${platformKey}" (supported: ${Object.keys(OPENGREP_ASSETS).join(', ')}); run socket-cli on a supported platform or install OpenGrep manually and point \`opengrep\` at it on PATH`,
Copy file name to clipboardExpand all lines: packages/cli/src/utils/dlx/spawn.mts
+50-34Lines changed: 50 additions & 34 deletions
Original file line number
Diff line number
Diff line change
@@ -116,14 +116,14 @@ function validatePackageName(name: string): void {
116
116
117
117
if(!validNamePattern.test(name)){
118
118
thrownewInputError(
119
-
`Invalid package name "${name}". Package names must contain only lowercase letters, numbers, hyphens, underscores, dots, and optionally a scope (@org/package).`,
119
+
`package name "${name}"must match /^(@scope\\/)?[a-z0-9-~][a-z0-9-._~]*$/ (lowercase letters, digits, -, _, ., ~, with optional @scope/); rename the package or check for typos`,
`package name "${name}" contains path traversal characters (".." or a "/" outside of @scope/); pass a plain name like "lodash" or "@org/pkg"`,
127
127
)
128
128
}
129
129
}
@@ -232,7 +232,7 @@ async function downloadGitHubReleaseBinary(
232
232
}
233
233
}
234
234
thrownewInputError(
235
-
'Timeout waiting for another process to download GitHub release',
235
+
`timed out waiting for another socket process to finish downloading ${owner}/${repo}@${version} (${assetName}); if no other socket process is running, remove stale lock files under ${path.dirname(binaryPath)} and retry`,
236
236
)
237
237
}
238
238
throwe
@@ -267,8 +267,7 @@ async function downloadGitHubReleaseBinary(
`archive entry "${entry.entryName}" resolves outside the cache dir (${normalizedCacheDir}) — this looks like a zip-slip attack; do NOT trust this release asset, report it to the upstream project, and delete ${result.binaryPath}`,
272
271
)
273
272
}
274
273
}
@@ -286,8 +285,7 @@ async function downloadGitHubReleaseBinary(
`extracted symlink ${file} targets ${resolvedTarget} which is outside the cache dir (${normalizedCacheDir}); do NOT trust this release asset, report it to the upstream project, and delete ${cacheDir}`,
291
289
)
292
290
}
293
291
}
@@ -298,19 +296,20 @@ async function downloadGitHubReleaseBinary(
`archive format of ${assetName} is not supported (expected .zip or .tar.gz / .tgz); check the asset name in bundle-tools.json and the release's actual asset list`,
306
+
)
307
307
}
308
308
309
309
// Verify binary was extracted.
310
310
if(!existsSync(binaryPath)){
311
311
thrownewInputError(
312
-
`Binary ${binaryFileName} not found after extracting ${assetName}. `+
313
-
`Expected at: ${binaryPath}`,
312
+
`archive ${assetName} extracted but ${binaryFileName} was not found inside (expected at ${binaryPath}); the release's archive layout may have changed — verify asset contents and update bundle-tools.json`,
314
313
)
315
314
}
316
315
@@ -408,7 +407,9 @@ export async function spawnCoanaDlx(
408
407
409
408
// Use dlx version (resolveCoana only returns 'local' or 'dlx' types).
410
409
if(resolution.type!=='dlx'){
411
-
thrownewError('Unexpected resolution type for coana')
410
+
thrownewError(
411
+
`internal: resolveCoana returned resolution.type="${resolution.type}" (expected "dlx"); this is a resolver contract bug — re-run with --debug and report the output`,
412
+
)
412
413
}
413
414
constresult=awaitspawnDlx(
414
415
{
@@ -484,7 +485,9 @@ export async function spawnCdxgenDlx(
484
485
485
486
// Use dlx version (resolveCdxgen only returns 'local' or 'dlx' types).
486
487
if(resolution.type!=='dlx'){
487
-
thrownewError('Unexpected resolution type for cdxgen')
488
+
thrownewError(
489
+
`internal: resolveCdxgen returned resolution.type="${resolution.type}" (expected "dlx"); this is a resolver contract bug — re-run with --debug and report the output`,
490
+
)
488
491
}
489
492
returnawaitspawnDlx(
490
493
resolution.details,
@@ -554,7 +557,9 @@ export async function spawnSfwDlx(
554
557
555
558
// Use dlx version (resolveSfw only returns 'local' or 'dlx' types).
556
559
if(resolution.type!=='dlx'){
557
-
thrownewError('Unexpected resolution type for sfw')
560
+
thrownewError(
561
+
`internal: resolveSfw returned resolution.type="${resolution.type}" (expected "dlx"); this is a resolver contract bug — re-run with --debug and report the output`,
562
+
)
558
563
}
559
564
returnawaitspawnDlx(
560
565
resolution.details,
@@ -675,21 +680,25 @@ async function spawnToolVfs(
675
680
): Promise<DlxSpawnResult>{
676
681
if(!areExternalToolsAvailable()){
677
682
thrownewError(
678
-
`Cannot spawn ${tool} from VFS - tools not available in SEA mode`,
683
+
`cannot spawn ${tool} from VFS: external tools were not bundled into this SEA binary; rebuild the SEA with INLINED_SOCKET_CLI_INCLUDE_EXTERNAL_TOOLS=1 or run the non-SEA CLI`,
679
684
)
680
685
}
681
686
682
687
// Extract tools from VFS (returns paths directly).
683
688
consttoolPaths=awaitextractExternalTools()
684
689
if(!toolPaths){
685
-
thrownewError(`Failed to extract ${tool} from VFS`)
690
+
thrownewError(
691
+
`failed to extract ${tool} from VFS (extractExternalTools returned null); the embedded tool archive may be corrupt — rebuild the SEA binary`,
692
+
)
686
693
}
687
694
688
695
// Get tool path.
689
696
consttoolPath=toolPaths[tool]
690
697
691
698
if(!toolPath){
692
-
thrownewError(`Tool path not found for ${tool}`)
699
+
thrownewError(
700
+
`VFS extraction succeeded but ${tool} was not in the output map (got: ${Object.keys(toolPaths).join(', ')||'empty'}); the SEA bundle is missing ${tool} — rebuild with it included`,
`python-build-standalone does not ship a prebuilt for os.platform()="${platform}" (supported: darwin, linux, win32); install Python manually and point socket at it via PATH`,
952
+
)
942
953
}
943
954
944
955
// Asset name format matches checksums in bundle-tools.json.
@@ -1000,7 +1011,7 @@ async function downloadPython(pythonDir: string): Promise<void> {
'tar is required to extract Python. Please install tar for your system.',
1014
+
`tar is required to extract the Python standalone archive but was not found on PATH; install tar (e.g. \`apt install tar\`, \`brew install gnu-tar\`) and re-run`,
`Failed to acquire Python installation lock after ${MAX_RETRIES} retries. `+
1050
-
'Please check for filesystem issues or competing processes.',
1060
+
`could not acquire the Python install lock after ${MAX_RETRIES} retries at ${lockFile}; another socket process may be stuck, or the lock file is stale — remove it manually and retry, or check that ${pythonDir} is writable`,
'Timeout waiting for Python download by another process',
1120
+
`timed out after 60s waiting for another socket process to finish downloading Python to ${pythonDir}; if no other socket process is running, remove ${lockFile} and retry`,
`Python binary not found after extraction: ${pythonBin}`,
1131
+
`Python archive extracted but ${pythonBin} does not exist; the standalone archive layout may have changed — check the asset contents under ${pythonDir} and update the bin-path logic in spawn.mts`,
1122
1132
)
1123
1133
}
1124
1134
@@ -1218,7 +1228,9 @@ async function downloadPyPiWheel(
1218
1228
try{
1219
1229
constresponse=awaitsocketHttpRequest(pypiUrl)
1220
1230
if(!response.ok){
1221
-
thrownewError(`PyPI API returned ${response.status}`)
1231
+
thrownewError(
1232
+
`PyPI returned HTTP ${response.status} for ${pypiUrl} (expected 200); check the package name and version, or retry if the registry is rate-limiting`,
1233
+
)
1222
1234
}
1223
1235
constdata=response.json()as{
1224
1236
urls?: Array<{filename: string;url: string}>
@@ -1235,14 +1247,13 @@ async function downloadPyPiWheel(
1235
1247
// If we can't fetch from API, construct URL directly (may not work for all packages).
1236
1248
// This is a fallback; the API approach is more reliable.
1237
1249
thrownewInputError(
1238
-
`Failed to fetch PyPI package info for ${packageName}@${version}: ${getErrorCause(e)}`,
1250
+
`could not fetch PyPI metadata for ${packageName}==${version} from ${pypiUrl} (${getErrorCause(e)}); check your network or proxy settings, or try again if PyPI is rate-limiting`,
1239
1251
)
1240
1252
}
1241
1253
1242
1254
if(!wheelUrl){
1243
1255
thrownewInputError(
1244
-
`No wheel found for ${packageName}@${version} on PyPI. `+
1245
-
'This package may only be available as a source distribution.',
1256
+
`${packageName}==${version} has no py3-none-any wheel on PyPI (only sdist available); pin to a version that ships a wheel or install from source manually`,
1246
1257
)
1247
1258
}
1248
1259
@@ -1275,8 +1286,7 @@ export async function ensureSocketPyCli(
1275
1286
1276
1287
if(retryCount>=MAX_RETRIES){
1277
1288
thrownewInputError(
1278
-
`Failed to acquire Socket Python CLI installation lock after ${MAX_RETRIES} retries. `+
1279
-
'Please check for filesystem issues or competing processes.',
1289
+
`could not acquire the Socket Python CLI install lock after ${MAX_RETRIES} retries; another socket process may be stuck, or the lock file is stale — check for stale lock files under the Python cache dir and retry`,
1280
1290
)
1281
1291
}
1282
1292
@@ -1386,7 +1396,7 @@ export async function ensureSocketPyCli(
1386
1396
})
1387
1397
}else{
1388
1398
thrownewInputError(
1389
-
`Failed to download verified socketsecurity wheel for version ${pyCliVersion}`,
1399
+
`could not download the verified socketsecurity==${pyCliVersion} wheel (downloadPyPiWheel returned null — likely a checksum mismatch or missing wheel asset); re-run with --debug for details, or bump the version in bundle-tools.json if the checksum needs refreshing`,
1390
1400
)
1391
1401
}
1392
1402
}else{
@@ -1454,7 +1464,7 @@ export async function spawnSocketPyCliVfs(
1454
1464
})
1455
1465
}else{
1456
1466
thrownewError(
1457
-
`Failed to download verified socketsecurity wheel for version ${pyCliVersion}`,
1467
+
`failed to download socketsecurity==${pyCliVersion} wheel from PyPI (downloadPyPiWheel returned null — likely a checksum mismatch or missing py3-none-any wheel); re-run with --debug for details`,
1458
1468
)
1459
1469
}
1460
1470
}else{
@@ -1673,7 +1683,9 @@ async function spawnTrivyDlx(
1673
1683
constresolution=resolveTrivy()
1674
1684
1675
1685
if(resolution.type!=='github-release'){
1676
-
thrownewError('Unexpected resolution type for trivy')
1686
+
thrownewError(
1687
+
`internal: resolveTrivy returned resolution.type="${resolution.type}" (expected "github-release"); this is a resolver contract bug — re-run with --debug and report the output`,
1688
+
)
1677
1689
}
1678
1690
1679
1691
const{env: spawnEnv, ...dlxOptions}={
@@ -1735,7 +1747,9 @@ async function spawnTrufflehogDlx(
1735
1747
constresolution=resolveTrufflehog()
1736
1748
1737
1749
if(resolution.type!=='github-release'){
1738
-
thrownewError('Unexpected resolution type for trufflehog')
1750
+
thrownewError(
1751
+
`internal: resolveTrufflehog returned resolution.type="${resolution.type}" (expected "github-release"); this is a resolver contract bug — re-run with --debug and report the output`,
1752
+
)
1739
1753
}
1740
1754
1741
1755
const{env: spawnEnv, ...dlxOptions}={
@@ -1797,7 +1811,9 @@ async function spawnOpengrepDlx(
1797
1811
constresolution=resolveOpengrep()
1798
1812
1799
1813
if(resolution.type!=='github-release'){
1800
-
thrownewError('Unexpected resolution type for opengrep')
1814
+
thrownewError(
1815
+
`internal: resolveOpengrep returned resolution.type="${resolution.type}" (expected "github-release"); this is a resolver contract bug — re-run with --debug and report the output`,
Copy file name to clipboardExpand all lines: packages/cli/src/utils/dlx/vfs-extract.mts
+9-5Lines changed: 9 additions & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -273,7 +273,7 @@ async function extractTool(tool: ExternalTool): Promise<string> {
273
273
274
274
if(!processWithSmol.smol?.mount){
275
275
thrownewError(
276
-
'process.smol.mount not available - not in node-smol SEA mode',
276
+
`process.smol.mount is undefined — extractTool("${tool}") requires a node-smol SEA build; this code path should only run inside the SEA. Check isSeaBinary() / areExternalToolsAvailable() upstream`,
277
277
)
278
278
}
279
279
@@ -340,12 +340,16 @@ async function extractTool(tool: ExternalTool): Promise<string> {
340
340
}
341
341
342
342
if(!existsSync(extractedPath)){
343
-
thrownewError(`Extracted tool not found at ${extractedPath}`)
343
+
thrownewError(
344
+
`process.smol.mount returned but ${extractedPath} does not exist; the VFS layout for ${tool} may have changed — check the SEA build config and the tool's expected path`,
345
+
)
344
346
}
345
347
346
348
returnextractedPath
347
349
}catch(e){
348
-
thrownewError(`Failed to extract ${tool} from VFS: ${e}`)
350
+
thrownewError(
351
+
`failed to extract ${tool} from the SEA VFS (${(easError).message}); the embedded tool archive may be corrupt — rebuild the SEA binary`,
352
+
)
349
353
}
350
354
}
351
355
@@ -550,7 +554,7 @@ export async function extractExternalTools(
550
554
}
551
555
}
552
556
thrownewError(
553
-
'Timeout waiting for another process to extract external tools',
557
+
`timed out waiting for another socket process to finish extracting external tools from the SEA VFS; if no other socket process is running, remove any stale lock files under the node-smol base dir and retry`,
554
558
)
555
559
}
556
560
throwe
@@ -641,7 +645,7 @@ export async function extractExternalTools(
`Failed to extract all external tools. Missing: ${missingTools.join(', ')}`,
648
+
`SEA VFS extraction returned ${Object.keys(toolPaths).length}/${EXTERNAL_TOOLS.length} tools (missing: ${missingTools.join(', ')}); the SEA bundle is incomplete — rebuild with all external tools included`,
0 commit comments