@@ -22,6 +22,7 @@ import {
2222import type { RuntimeContext } from "@/schemas" ;
2323
2424const RPC_INIT_TIMEOUT_MS = 30_000 ;
25+ const WORKER_STDERR_TAIL_CHARS = 16_384 ;
2526
2627const logger = getClientLogger ( ) ;
2728
@@ -175,6 +176,19 @@ function bestEffortUnlinkSocket(socketPath: string | null) {
175176 }
176177}
177178
179+ function appendWorkerStderrTail ( current : string , chunk : string ) {
180+ const next = current + chunk ;
181+ if ( next . length <= WORKER_STDERR_TAIL_CHARS ) return next ;
182+ return next . slice ( next . length - WORKER_STDERR_TAIL_CHARS ) ;
183+ }
184+
185+ function createWorkerStartupError ( details : string , stderrTail : string ) {
186+ const stderr = stderrTail . trimEnd ( ) ;
187+ if ( ! stderr ) return new Error ( details ) ;
188+
189+ return new Error ( `${ details } \n\nWorker stderr:\n${ stderr } ` ) ;
190+ }
191+
178192function resetModuleState ( ) {
179193 rpcInstance = null ;
180194 rpcPromise = null ;
@@ -230,6 +244,14 @@ interface SpawnResources {
230244 socketPath : string ;
231245}
232246
247+ interface WorkerStderrStream {
248+ on ( event : "data" , listener : ( chunk : Buffer | string ) => void ) : void ;
249+ }
250+
251+ function getWorkerStderr ( proc : BareChildProcess ) : WorkerStderrStream | null {
252+ return ( proc as { stderr ?: WorkerStderrStream | null } ) . stderr ?? null ;
253+ }
254+
233255// `bare-runtime` resolves its platform binary with
234256// `require('bare-runtime-<platform>-<arch>')` and throws a terse
235257// `No binaries found for target '<platform>-<arch>'` whenever that package —
@@ -322,12 +344,19 @@ async function ensureRPC(): Promise<RPC> {
322344
323345 rpcPromise = new Promise ( ( resolve , reject ) => {
324346 let settled = false ;
347+ let workerStderrTail = "" ;
325348
326349 const timer = setTimeout ( ( ) => {
327350 if ( settled ) return ;
328351 settled = true ;
352+ const cause = workerStderrTail
353+ ? createWorkerStartupError (
354+ "Worker did not establish IPC before the RPC initialization timeout" ,
355+ workerStderrTail ,
356+ )
357+ : undefined ;
329358 teardownFailedInit ( ) ;
330- reject ( new RPCInitTimeoutError ( RPC_INIT_TIMEOUT_MS ) ) ;
359+ reject ( new RPCInitTimeoutError ( RPC_INIT_TIMEOUT_MS , cause ) ) ;
331360 } , RPC_INIT_TIMEOUT_MS ) ;
332361
333362 ipcServer = createServer ( ( socket ) => {
@@ -370,7 +399,7 @@ async function ensureRPC(): Promise<RPC> {
370399 ] ,
371400 platform : process . platform ,
372401 arch : process . arch ,
373- stdio : [ "inherit" , "inherit" , "inherit " ] ,
402+ stdio : [ "inherit" , "inherit" , "pipe " ] ,
374403 } ) ;
375404 } catch ( error ) {
376405 // `spawn` resolves the bare binary synchronously and can throw before
@@ -386,6 +415,12 @@ async function ensureRPC(): Promise<RPC> {
386415 }
387416
388417 if ( bareWorkerProc ) {
418+ getWorkerStderr ( bareWorkerProc ) ?. on ( "data" , ( chunk ) => {
419+ const text = chunk . toString ( ) ;
420+ workerStderrTail = appendWorkerStderrTail ( workerStderrTail , text ) ;
421+ process . stderr . write ( chunk ) ;
422+ } ) ;
423+
389424 bareWorkerProc . on (
390425 "exit" ,
391426 ( code : number | null , exitSignal : string | null ) => {
@@ -397,15 +432,29 @@ async function ensureRPC(): Promise<RPC> {
397432 ) ;
398433 return ;
399434 }
400- // Worker died before handshake — reject the init promise.
435+ // Pre-handshake failures are rejected from "close" so stderr has
436+ // drained before we assemble the startup error cause.
437+ } ,
438+ ) ;
439+
440+ bareWorkerProc . on (
441+ "close" ,
442+ ( ...args : unknown [ ] ) => {
443+ if ( settled ) return ;
444+ const code = typeof args [ 0 ] === "number" ? args [ 0 ] : null ;
445+ const exitSignal = typeof args [ 1 ] === "string" ? args [ 1 ] : null ;
446+
447+ // Worker died before handshake. Use close, not exit, so piped
448+ // stderr has drained before we build the error cause.
401449 settled = true ;
402450 clearTimeout ( timer ) ;
403451 teardownFailedInit ( ) ;
404452 reject (
405453 new RPCInitTimeoutError (
406454 RPC_INIT_TIMEOUT_MS ,
407- new Error (
408- `Worker process exited with code ${ code } before IPC connection was established` ,
455+ createWorkerStartupError (
456+ `Worker process exited with code ${ code } , signal ${ exitSignal } before IPC connection was established` ,
457+ workerStderrTail ,
409458 ) ,
410459 ) ,
411460 ) ;
0 commit comments