fix(dev): capture Windows output in .lt-dev/*.log, and say when a component dies on start - #121
Merged
Merged
Conversation
…ponent dies on start On Windows every `.lt-dev/*.log` stayed at 0 bytes, including the log of an app that was demonstrably running. The output went to an extra console window instead. On a laptop an API died on start with its error visible only there, and it took two hours to find. Cause, measured on windows-latest with a fake `pnpm.cmd` → node → node grandchild: `detached` starts `cmd.exe` without a console. The console program below it gets a new one, and Windows replaces the std handles `cmd.exe` only inherited, the log file among them. The result was 0 bytes, even when `cmd.exe` redirected with `>>` itself. Not the console window as such: a node child that gets its own console still writes to the file when node passes it the handles. Fix: on Windows a `node -e` trampoline sits in front of the command. libuv hands it the file explicitly, and it starts the command with `stdio: 'inherit'`, so `cmd.exe` gets a console of its own and its children inherit both. It exits with the command's code and turns an unstartable command into 127 with a readable line. The recorded pid is the trampoline's; `taskkill /T` from it reaches the tree. `__tests__/dev-log-capture.test.ts` spawns through a `.cmd` shim on Windows and needs child AND grandchild output in the file. On windows-latest it is green with this change and red against main (received ""). On POSIX it guards the sh wrapper the same way. Also: - `lt dev up` watches freshly started pids for 3 s. A component that is gone by then is an error (exit 1) with the last log lines, or the statement that the log is empty. It used to report "Started". - `lt dev status` shows the log tail of a component that is down, and warns when a component has been up for 15 s with its log still empty. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DKoenig9
marked this pull request as ready for review
September 25, 2026 10:30
kaihaase
added a commit
that referenced
this pull request
Sep 27, 2026
…rter's peer rules Releases #114–#121 (lt dev Windows port AP-1/2/3/5, absolute registry paths, up-to-date server README) and hoists peerDependencyRules into the workspace root, so the first `pnpm run check` of a generated project no longer fails at `check:peers`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
On a Windows laptop, every
.lt-dev/*.logstayed at 0 bytes, including the log of an app that was demonstrably running (port bound,nuxt devanswering). The files existed and were even rotated ("Rotated previous api log → api.log.1 (0B)"), but nothing was ever written to them. The output went to an extra console window thatlt dev upopened. In the same session an API died on start whileupreportedStarted. Its error sat in that window for two hours before anyone found it. A log that exists but stays empty is worse than no log, because it looks trustworthy.Cause (measured, not inferred)
The measurements ran on
windows-latestand reproduce the production path: a fakepnpm.cmdshim runs node throughcmd.exe, and that node starts a node grandchild. Both write to stdout and stderr. Temporary branchtmp/measure-windows-spawn, runs 35856960971 and 35857372014:spawnDetachedwindowsHidecmd.exeredirects itself with>>windowsHidedetachedstartscmd.exewithout a console. The console program below it gets a new one, and Windows then replaces the standard handles thatcmd.exeonly inherited, the log file among them. The console window itself is not the problem: a node child that gets its own console still writes to the file, because node passes the handles explicitly. Evencmd.exe's own>>redirect is lost.Fix
On Windows, a small
node -etrampoline (windowsTrampoline) sits in front of the command:stdio: 'inherit', which passes the handles explicitly again.cmd.exethen owns a console, and its children inherit both the console and the file.lt dev: could not start <cmd>: ….-eresolves modules from the user's project, not from the CLI.taskkill /Tfrom it reaches the whole tree (AP-5, fix(dev): stop Windows stacks with taskkill /T /F, and never turn a stored pid into a broadcast (AP-5) #118). The PID-identity test is POSIX-only for that reason, with a comment explaining why.windowsHide: trueon the spawn is meant to keep the console out of sight. Whether a window still shows up cannot be measured in CI (no interactive session). That is for the laptop.The test that goes red when the log stays empty
__tests__/dev-log-capture.test.tsruns the realspawnDetached, through a.cmdshim on Windows and through the sh wrapper on POSIX. It needs the child's and the grandchild's stdout and stderr in the file.main'sdev-process.ts"", i.e. exactly the laptop symptomThe run before that was worthless, and I discarded it. My test set
env.PATH, but on Windows the key isPath. In a copied (case-sensitive) env,env.PATHreadundefined, sonodedid not start in either run, and both logs contained onlycmd.exe's own error message. That also shows thatcmd.exeitself writes correctly and only the console programs below it lose their output.Also: saying when a component dies on start
lt dev upwatches the freshly started pids for 3 s. A component that is gone by then is an error (exit 1). It shows the last log lines, or the statement that the log is EMPTY and the reason is therefore not in it. It used to reportStarted. The limit: a crash under nodemon keeps the supervisor alive, so that case goes tostatus.lt dev statusshows the last 8 log lines of a component that is down. It warns when a component has been up for 15 s with its log still empty ("its output is not being captured"). That is the silent failure from the laptop, which is now visible.src/lib/dev-log-tail.ts(diagnoseLog,describeLog,isSilentLog,findEarlyExits), with tests.up/statusonly have a static wiring test, because the commands have no harness.Mutation check (reverted from a backup)
stdio: 'ignore')spawnDetachedwithout stdio (POSIX)dev-log-capture)upwiring removednpm test: 80 suites, 1224 tests. Lint and compile are clean.For the laptop
After this change, check:
.lt-dev\api.logand.lt-dev\app.logcontain the startup bannersNot in this PR: #118 (AP-5) and #120 (AP-7). They are finished and only wait for the laptop measurement.
🤖 Generated with Claude Code