Compare commits

...

4 Commits

Author SHA1 Message Date
Jason Jean 4bf55583a8 Merge branch 'master' into fix/e2e-timeout-diagnostics 2026-08-05 17:22:22 -04:00
FrozenPandaz 0ef252c350 chore(repo): bust the cache to re-run e2e against the timeout diagnostics 2026-08-05 16:40:56 -04:00
FrozenPandaz ec20f26c6b fix(testing): reuse trimDaemonLog and honour a relocated daemon data dir
The timeout dump hand-rolled a 50-line tail, which on a daemon log is mostly
plugin-worker chatter and can push the restart/error/graph-recompute lines that
actually explain a stall out of view. log-utils already has trimDaemonLog for
exactly this — it keeps the signal lines plus a tail and marks what it dropped.

It also hardcoded .nx/workspace-data, so a test that relocates the daemon's data
dir via NX_WORKSPACE_DATA_DIRECTORY / NX_PROJECT_GRAPH_CACHE_DIRECTORY (watch.test.ts
does) would report the log as missing. Resolve the same way
workspaceDataDirectoryForWorkspace does, from the command's own env.
2026-08-05 16:38:49 -04:00
FrozenPandaz 64ce7afb45 chore(testing): report daemon state when an e2e command times out
An e2e command that stalls is killed at 300s with only its own (already
flushed) output, which never shows why the run stopped. Every sighting so far
stalls right after a task that writes file outputs exits, i.e. inside
postRunSteps' daemon round-trip, and sendMessageToDaemon waits 20 minutes on a
reply — far past the kill — so the client leaves no trace behind.

Append the tail of the workspace's daemon.log and the surviving nx processes to
the timeout message in runCLI and runLernaCLI. The timeout kills the shell only,
so the client, daemon and task processes are all still inspectable at that point.
2026-08-05 15:10:58 -04:00
2 changed files with 55 additions and 4 deletions
+54 -3
View File
@@ -7,6 +7,7 @@ import {
import { ChildProcess, exec, execSync, ExecSyncOptions } from 'child_process';
import { existsSync } from 'fs-extra';
import * as isCI from 'is-ci';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { stripVTControlCharacters } from 'node:util';
import { gte } from 'semver';
@@ -25,7 +26,7 @@ import {
getYarnMajorVersion,
isVerboseE2ERun,
} from './get-env-info';
import { logError, logInfo } from './log-utils';
import { logError, logInfo, trimDaemonLog } from './log-utils';
export interface RunCmdOpts {
silenceError?: boolean;
@@ -445,6 +446,50 @@ export function normalizePerformanceReport(output: string): string {
);
}
/**
* Extra context for a `Command timed out` failure. A wedged daemon outlives our kill —
* `sendMessageToDaemon` waits 20 minutes on a reply — and leaves no trace in the killed
* command's own output, so the daemon log and the surviving processes are the only
* evidence of where the run stopped. Best-effort: never throws.
*/
function timeoutDiagnostics(cwd: string, env: RunCmdOpts['env']): string {
const sections: string[] = [];
// Mirrors workspaceDataDirectoryForWorkspace: a test that relocates the daemon's
// data dir (e.g. watch.test.ts) puts daemon.log somewhere other than the default.
const dataDir =
env?.NX_WORKSPACE_DATA_DIRECTORY ??
env?.NX_PROJECT_GRAPH_CACHE_DIRECTORY ??
join(cwd, '.nx', 'workspace-data');
const daemonLog = join(dataDir, 'd', 'daemon.log');
try {
sections.push(
`Daemon log (trimmed, ${daemonLog}):\n${trimDaemonLog(
readFileSync(daemonLog, 'utf-8')
)}`
);
} catch (e) {
sections.push(`Daemon log unavailable (${daemonLog}): ${e.message}`);
}
try {
// The timeout kills the shell, not its descendants, so the nx client, the daemon and
// any task process are all still listed here.
const processes = execSync('ps -eo pid,ppid,etime,args', {
encoding: 'utf-8',
})
.split('\n')
.filter((line) => line.includes('nx'))
.slice(0, 40)
.join('\n');
sections.push(`Surviving nx processes:\n${processes}`);
} catch (e) {
sections.push(`Process list unavailable: ${e.message}`);
}
return sections.join('\n\n');
}
export function runCLI(
command: string,
opts: RunCmdOpts = {
@@ -500,7 +545,10 @@ export function runCLI(
const processOutput = stripVTControlCharacters(
`${e.stdout ?? ''}\n\n${e.stderr ?? ''}`
).trim();
const msg = `Command timed out after ${timeoutSec}s: ${command}\n\nProcess output:\n${processOutput}`;
const msg = `Command timed out after ${timeoutSec}s: ${command}\n\nProcess output:\n${processOutput}\n\n${timeoutDiagnostics(
opts.cwd || tmpProjPath(),
opts.env
)}`;
logError(`Command timed out`, msg);
throw new Error(msg);
}
@@ -556,7 +604,10 @@ export function runLernaCLI(
const processOutput = stripVTControlCharacters(
`${e.stdout ?? ''}\n\n${e.stderr ?? ''}`
).trim();
const msg = `Command timed out after ${timeoutSec}s: ${command}\n\nProcess output:\n${processOutput}`;
const msg = `Command timed out after ${timeoutSec}s: ${command}\n\nProcess output:\n${processOutput}\n\n${timeoutDiagnostics(
opts.cwd || tmpProjPath(),
opts.env
)}`;
logError(`Command timed out`, msg);
throw new Error(msg);
}
+1 -1
View File
@@ -388,7 +388,7 @@
"nxCloudId": "62d013ea0852fe0a2df74438",
"nxCloudUrl": "https://staging.nx.app",
"parallel": 1,
"bust": 3237,
"bust": 3238,
"defaultBase": "master",
"sync": {
"applyChanges": true