The wizard ends a run without warning. The terminal prints InformationalError: socket idle timeout and the process exits with code 1, leaving whatever the user was doing unfinished. On runs that stop at an interactive prompt, the prompt paints and then disappears within about a second, so it reads as the prompt itself crashing. Expected behavior is that a background transport error is logged or ignored, and the run continues.
The error is not raised by wizard code. It comes from a Node timer, and the wizard has no handler for it, so it terminates the process.
How to reproduce
- Run
npx @posthog/wizard@2.71.0 self-driving (or upload-source-maps) in a project.
- Let the run reach a point where it waits after a network call, such as an agent step or an interactive prompt.
- When Node's bundled undici raises
InformationalError: socket idle timeout on an idle HTTP/2 session, roughly 5 seconds after the last request completes, the process prints the error and exits.
Step 3 depends on the Node build, so it does not reproduce on every machine. The fatal handling does reproduce deterministically:
const { PostHog } = require('posthog-node')
new PostHog('<project key>', { host: '<host>', enableExceptionAutocapture: true })
process.stdin.resume()
setInterval(() => console.log('alive'), 400)
setTimeout(() => { throw new Error('boom') }, 600)
That exits with code 1 at 600ms. Adding any process.on('uncaughtException') listener, or turning autocapture off, keeps it alive.
Additional context
From https://us.posthog.com/project/2/support/tickets/70634
Why it happens
src/utils/analytics.ts constructs the PostHog client with enableExceptionAutocapture: true. In the published posthog-node build, the autocapture handler counts the uncaughtException listeners that are not its own, and when that count is zero it treats the exception as fatal:
async onFatalError(exception) {
console.error(exception)
await this.client.shutdown(SHUTDOWN_TIMEOUT)
globalThis.process.exit(1)
}
The wizard registers no uncaughtException listener anywhere in src/, so that count is always zero and every uncaught error is fatal, whatever raised it.
For this particular error the captured stack is process.processTimers to listOnTimeout to Timeout.onHttp2SessionIdleTimeout, in Node's bundled undici. undici's onHttp2SessionIdleTimeout constructs new InformationalError('socket idle timeout') and calls util.destroy(socket, err). With nothing listening for that error it surfaces on uncaughtException.
The await shutdown() before process.exit(1) leaves a short window where the Ink render loop can still paint. That is why the last thing a user sees can be a prompt that appears and vanishes, and why the exception is sometimes recorded just before the prompt it appears to have killed.
Where it shows up
Every release from 2.58.0 through 2.71.0, since early August. Overwhelmingly on the self-driving command, with a smaller share on upload-source-maps. The screen at the time of the exit varies, which fits an error timed off the last network call rather than off anything on screen.
Suggested approaches
- Register a
process.on('uncaughtException') handler in the wizard. That alone stops posthog-node from ending the process, and lets the wizard decide what is genuinely fatal and print something a user can act on.
- Decide separately whether a benign transport error should end a run at all.
- Capture the Node version in wizard telemetry. It is not recorded today, which makes it hard to confirm which Node builds raise this.
allowH2 is off by default in undici 7, which Node 24 bundles, and on by default in undici 8, so the exposure likely tracks the Node version.
Related work
The open PRs that filter transport errors at analytics.captureException (#1023, #1182) do not cover this. Autocapture reports through posthog-node directly and never passes through that wrapper, and the process exit happens regardless of whether the event is dropped.
Debug info
The wizard ends a run without warning. The terminal prints
InformationalError: socket idle timeoutand the process exits with code 1, leaving whatever the user was doing unfinished. On runs that stop at an interactive prompt, the prompt paints and then disappears within about a second, so it reads as the prompt itself crashing. Expected behavior is that a background transport error is logged or ignored, and the run continues.The error is not raised by wizard code. It comes from a Node timer, and the wizard has no handler for it, so it terminates the process.
How to reproduce
npx @posthog/wizard@2.71.0 self-driving(orupload-source-maps) in a project.InformationalError: socket idle timeouton an idle HTTP/2 session, roughly 5 seconds after the last request completes, the process prints the error and exits.Step 3 depends on the Node build, so it does not reproduce on every machine. The fatal handling does reproduce deterministically:
That exits with code 1 at 600ms. Adding any
process.on('uncaughtException')listener, or turning autocapture off, keeps it alive.Additional context
From https://us.posthog.com/project/2/support/tickets/70634
Why it happens
src/utils/analytics.tsconstructs the PostHog client withenableExceptionAutocapture: true. In the publishedposthog-nodebuild, the autocapture handler counts theuncaughtExceptionlisteners that are not its own, and when that count is zero it treats the exception as fatal:The wizard registers no
uncaughtExceptionlistener anywhere insrc/, so that count is always zero and every uncaught error is fatal, whatever raised it.For this particular error the captured stack is
process.processTimerstolistOnTimeouttoTimeout.onHttp2SessionIdleTimeout, in Node's bundled undici. undici'sonHttp2SessionIdleTimeoutconstructsnew InformationalError('socket idle timeout')and callsutil.destroy(socket, err). With nothing listening for that error it surfaces onuncaughtException.The
await shutdown()beforeprocess.exit(1)leaves a short window where the Ink render loop can still paint. That is why the last thing a user sees can be a prompt that appears and vanishes, and why the exception is sometimes recorded just before the prompt it appears to have killed.Where it shows up
Every release from 2.58.0 through 2.71.0, since early August. Overwhelmingly on the
self-drivingcommand, with a smaller share onupload-source-maps. The screen at the time of the exit varies, which fits an error timed off the last network call rather than off anything on screen.Suggested approaches
process.on('uncaughtException')handler in the wizard. That alone stopsposthog-nodefrom ending the process, and lets the wizard decide what is genuinely fatal and print something a user can act on.allowH2is off by default in undici 7, which Node 24 bundles, and on by default in undici 8, so the exposure likely tracks the Node version.Related work
The open PRs that filter transport errors at
analytics.captureException(#1023, #1182) do not cover this. Autocapture reports throughposthog-nodedirectly and never passes through that wrapper, and the process exit happens regardless of whether the event is dropped.Debug info
node -v): [please provide]