Skip to content

Commit b316ec1

Browse files
authored
fix(core): surface clearer error when CNW hits SANDBOX_FAILED (#34724)
This PR surfaces install errors instead of silently failing without anything useful printed. It also records `needs_input` in the AX flow as `cancelled` so we account for cases where AI agents exit without recording either success, complete, or cancelled. BEFORE: <img width="1288" height="381" alt="image" src="https://github.com/user-attachments/assets/44fe9642-764e-452b-90be-f30c4a230be5" /> AFTER: <img width="1279" height="850" alt="Screenshot 2026-03-05 at 12 30 25 PM" src="https://github.com/user-attachments/assets/24c96584-2790-4dcb-8404-7e221c50876c" /> ## Notes 1. **Remove `--silent` from all PM install commands** — since `execAndWait` uses `exec()` (captures output in memory, never shown to terminal), `--silent` just suppressed error info for no benefit 2. **Increase `maxBuffer`** from default 1MB to 10MB to prevent process being killed when PMs emit verbose output 3. **Fallback error message** when both stderr and stdout are empty — includes exit code and log file path 4. **Structured sandbox error** with exit code, log file, and actionable hint 5. **Record telemetry stat** for AI agent `needs_input` flow (was previously missing) 6. **Migrate from deprecated `CreateNxWorkspaceError`** to `CnwError` in `execAndWait` ## Related Issue(s) Fixes NXC-4035
1 parent a3c5f27 commit b316ec1

File tree

4 files changed

+53
-9
lines changed

4 files changed

+53
-9
lines changed

packages/create-nx-workspace/bin/create-nx-workspace.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,16 @@ async function normalizeArgsMiddleware(
569569
if (!templateProvided && !presetProvided) {
570570
const workspaceName = (argv.name as string) || (argv._[0] as string);
571571
writeAiOutput(buildTemplateRequiredResult(workspaceName));
572+
await recordStat({
573+
nxVersion,
574+
command: 'create-nx-workspace',
575+
useCloud: false,
576+
meta: {
577+
type: 'cancel',
578+
flowVariant: getFlowVariant(),
579+
aiAgent: true,
580+
},
581+
});
572582
process.exit(0); // Exit 0 - JSON output has success: false, AI parses that
573583
}
574584

packages/create-nx-workspace/src/create-sandbox.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,29 @@ export async function createSandbox(packageManager: PackageManager) {
4747
installSpinner.succeed();
4848
} catch (e) {
4949
installSpinner.fail();
50+
const logFile = e instanceof CnwError ? e.logFile : undefined;
51+
const exitCode = e instanceof CnwError ? e.exitCode : undefined;
5052
const message = e instanceof Error ? e.message : String(e);
53+
54+
const lines = [`Failed to install dependencies`];
55+
if (message?.trim()) {
56+
lines.push(message.trim());
57+
}
58+
if (exitCode != null) {
59+
lines.push(`Exit code: ${exitCode}`);
60+
}
61+
if (logFile) {
62+
lines.push(`Log file: ${logFile}`);
63+
}
64+
lines.push(
65+
`\nPlease verify that "${install}" runs successfully in a temporary directory.`
66+
);
67+
5168
throw new CnwError(
5269
'SANDBOX_FAILED',
53-
`Failed to install dependencies: ${message}`
70+
lines.join('\n'),
71+
logFile,
72+
exitCode ?? undefined
5473
);
5574
} finally {
5675
installSpinner.stop();

packages/create-nx-workspace/src/utils/child-process-utils.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { spawn, exec } from 'child_process';
22
import { writeFileSync } from 'fs';
33
import { join } from 'path';
4-
import { CreateNxWorkspaceError } from './error-utils';
4+
import { CnwError } from './error-utils';
55

66
/**
77
* Use spawn only for interactive shells
@@ -47,16 +47,28 @@ export function execAndWait(
4747
return new Promise<{ code: number; stdout: string }>((res, rej) => {
4848
exec(
4949
command,
50-
{ cwd, env: { ...process.env, NX_DAEMON: 'false' }, windowsHide: false },
50+
{
51+
cwd,
52+
env: { ...process.env, NX_DAEMON: 'false' },
53+
windowsHide: false,
54+
maxBuffer: 1024 * 1024 * 10, // 10MB — default 1MB can be exceeded by verbose PM output
55+
},
5156
(error, stdout, stderr) => {
5257
if (error) {
5358
if (silenceErrors) {
5459
rej();
5560
} else {
5661
const logFile = join(cwd, 'error.log');
5762
writeFileSync(logFile, `${stdout}\n${stderr}`);
58-
const message = stderr && stderr.trim().length ? stderr : stdout;
59-
rej(new CreateNxWorkspaceError(message, error.code, logFile));
63+
const message =
64+
stderr && stderr.trim().length
65+
? stderr
66+
: stdout && stdout.trim().length
67+
? stdout
68+
: `Command failed with exit code ${error.code ?? 'unknown'}. See ${logFile} for details.`;
69+
rej(
70+
new CnwError('UNKNOWN', message, logFile, error.code ?? undefined)
71+
);
6072
}
6173
} else {
6274
res({ code: 0, stdout });

packages/create-nx-workspace/src/utils/package-manager.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ export function getPackageManagerCommand(
4949
switch (packageManager) {
5050
case 'yarn':
5151
const useBerry = +pmMajor >= 2;
52-
const installCommand = 'yarn install --silent';
52+
// Don't use --silent so that error output is captured on failure.
53+
// Since install is run via exec() (not spawn), output is captured
54+
// in memory and never shown to the terminal.
55+
const installCommand = 'yarn install';
5356
return {
5457
preInstall: `yarn set version ${pmVersion}`,
5558
install: useBerry
@@ -69,23 +72,23 @@ export function getPackageManagerCommand(
6972
useExec = true;
7073
}
7174
return {
72-
install: 'pnpm install --no-frozen-lockfile --silent --ignore-scripts',
75+
install: 'pnpm install --no-frozen-lockfile --ignore-scripts',
7376
exec: useExec ? 'pnpm exec' : 'pnpx',
7477
globalAdd: 'pnpm add -g',
7578
getRegistryUrl: 'pnpm config get registry',
7679
};
7780

7881
case 'npm':
7982
return {
80-
install: 'npm install --silent --ignore-scripts',
83+
install: 'npm install --ignore-scripts',
8184
exec: 'npx',
8285
globalAdd: 'npm i -g',
8386
getRegistryUrl: 'npm config get registry',
8487
};
8588
case 'bun':
8689
// bun doesn't current support programmatically reading config https://github.com/oven-sh/bun/issues/7140
8790
return {
88-
install: 'bun install --silent --ignore-scripts',
91+
install: 'bun install --ignore-scripts',
8992
exec: 'bunx',
9093
globalAdd: 'bun install -g',
9194
};

0 commit comments

Comments
 (0)