Bug description
Awaited Node *Immediate() calls resolve after failed HTTP requests, network failures, and timeouts. An error listener sees the failure, but the caller cannot handle it through the returned promise. Separately, core's successful-write response handling accepts terminal 3xx responses as successful writes.
This affects applications that keep events in a durable outbox and acknowledge them only after an awaited send succeeds: an unsuccessful request can cause the application to mark its event processed.
How to reproduce
Reproduced against current main at 910b0925650b83206a191ae7cdca3ce2eb761ede (posthog-node 5.52.1, @posthog/core 1.53.2), Node.js 24.14.1. This uses the supported injected fetch; no live endpoint is needed.
import { PostHog } from 'posthog-node'
for (const status of [200, 302, 503]) {
const client = new PostHog('test-key', {
fetch: async () => new Response(null, { status }),
flushInterval: 0,
fetchRetryCount: 0,
disableCompression: true,
})
client.on('error', error => console.log('error event:', error.message))
try {
await client.captureImmediate({ distinctId: 'repro', event: 'delivery-test' })
console.log(status, 'resolved')
} catch (error) {
console.log(status, 'rejected:', error.message)
}
await client.shutdown()
}
Actual: all three resolve; only 503 emits an error. Expected: 200 resolves; terminal 302 and 503 reject. Redirects followed by fetch to a final 2xx remain successful.
Regression tests also reproduce suppressed failures in identifyImmediate, aliasImmediate, groupIdentifyImmediate, captureExceptionImmediate, and captureAiImmediate. Core flush tests reproduce false success for final 300, 302, and 304.
Related sub-libraries
Additional context
PostHogCoreStateless.sendImmediate emits errors without rethrowing; Node _sendPreparedEvent catches and logs without rethrowing. Both layers must preserve the rejection for immediate calls. Normal queued capture should keep its existing background error handling. Required-response reads should retain their current status handling.
This is distinct from the missing-await bug in #3819: the request is awaited here, but its failure is suppressed.
Compatibility needs maintainer review: surfacing immediate transport failures changes observable behavior for callers that relied on suppression. The proposed fix uses existing promises without a new option, method, or exported type; no API-shape agreement is claimed. Intentional skips (disabled client, opt-out, or before_send dropping an event) remain separate from transport acknowledgement. HTTP 2xx does not prove downstream ingestion.
Report and reproduction prepared with GPT-6/Codex at a human contributor's direction.
Bug description
Awaited Node
*Immediate()calls resolve after failed HTTP requests, network failures, and timeouts. Anerrorlistener sees the failure, but the caller cannot handle it through the returned promise. Separately, core'ssuccessful-writeresponse handling accepts terminal 3xx responses as successful writes.This affects applications that keep events in a durable outbox and acknowledge them only after an awaited send succeeds: an unsuccessful request can cause the application to mark its event processed.
How to reproduce
Reproduced against current
mainat910b0925650b83206a191ae7cdca3ce2eb761ede(posthog-node5.52.1,@posthog/core1.53.2), Node.js 24.14.1. This uses the supported injected fetch; no live endpoint is needed.Actual: all three resolve; only 503 emits an error. Expected: 200 resolves; terminal 302 and 503 reject. Redirects followed by fetch to a final 2xx remain successful.
Regression tests also reproduce suppressed failures in
identifyImmediate,aliasImmediate,groupIdentifyImmediate,captureExceptionImmediate, andcaptureAiImmediate. Core flush tests reproduce false success for final 300, 302, and 304.Related sub-libraries
@posthog/coreAdditional context
PostHogCoreStateless.sendImmediateemits errors without rethrowing; Node_sendPreparedEventcatches and logs without rethrowing. Both layers must preserve the rejection for immediate calls. Normal queued capture should keep its existing background error handling. Required-response reads should retain their current status handling.This is distinct from the missing-await bug in #3819: the request is awaited here, but its failure is suppressed.
Compatibility needs maintainer review: surfacing immediate transport failures changes observable behavior for callers that relied on suppression. The proposed fix uses existing promises without a new option, method, or exported type; no API-shape agreement is claimed. Intentional skips (disabled client, opt-out, or
before_senddropping an event) remain separate from transport acknowledgement. HTTP 2xx does not prove downstream ingestion.Report and reproduction prepared with GPT-6/Codex at a human contributor's direction.