Profiles·Public

posthog-node

semver>=3.0.0postconditions12functions10last verified2026-06-24coverage score100%

Postconditions: what we check

  • getFeatureFlag · network-error
    error
    WhenNetwork failure, DNS resolution failure, PostHog API unreachable, API key invalid, rate limit exceeded, or any server-side error (5xx)
    ThrowsError or unhandled Promise rejection with network/HTTP details. PostHog SDK does not define a typed error class for feature flag calls; the rejection is a generic Error with message describing the failure.
    Required handlingCaller MUST wrap in try-catch (or use .catch()). Unlike capture()/identify() which are fire-and-forget, getFeatureFlag() makes a live network call and WILL reject on failure. Uncaught rejections crash the request handler or produce unhandled rejection warnings in Node.js. Minimum handling: try { const flag = await posthog.getFeatureFlag('my-flag', userId); if (flag === true) { /* feature on */ } } catch (err) { console.error('Feature flag check failed:', err); // Fall back to default behavior }
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1][2]
  • isFeatureEnabled · network-error
    error
    WhenNetwork failure, DNS resolution failure, PostHog API unreachable, or server-side error
    ThrowsError or unhandled Promise rejection. Same error surface as getFeatureFlag().
    Required handlingCaller MUST wrap in try-catch. isFeatureEnabled() makes a live network call and rejects on failure. Missing error handling causes unhandled rejection. Minimum handling: try { const enabled = await posthog.isFeatureEnabled('my-flag', userId); if (enabled) { /* ... */ } } catch (err) { console.error('Feature flag check failed:', err); // Fall back to default (disabled) }
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1]
  • getAllFlags · network-error
    error
    WhenNetwork failure, PostHog API unreachable, or server-side error
    ThrowsError or unhandled Promise rejection.
    Required handlingCaller MUST wrap in try-catch. getAllFlags() makes a live network call and rejects on failure. This is the most common source of unhandled PostHog rejections in production (confirmed in n8n codebase). Minimum handling: try { const flags = await posthog.getAllFlags(userId); return flags; } catch (err) { console.error('Failed to fetch feature flags:', err); return {}; // Safe default: all flags disabled }
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1]
  • shutdown · shutdown-timeout-error
    warning
    WhenFlush does not complete within shutdownTimeoutMs (default 30 seconds) — all pending events cannot be delivered before the timeout fires.
    ThrowsString rejection: 'Timeout while shutting down PostHog. Some events may not have been sent.' Note: This is a string, not an Error instance — typeof err === 'string'. In Node.js, unhandled string rejections still trigger unhandledRejection events.
    Required handlingCaller SHOULD wrap in try-catch. Shutdown timeout means queued analytics events are lost — not a business-critical failure, but unhandled rejections interfere with process exit in Node.js and signal graceful shutdown success to orchestrators. Recommended handling: try { await posthog.shutdown(); } catch (err) { console.error('PostHog shutdown timed out, some events may be lost:', err); // Non-critical — continue with process exit }
    costlowin prodimmediate exceptionusers seedegraded performancevisibilitysilent
    Sources[1]
  • getAllFlagsAndPayloads · get-all-flags-and-payloads-network-error
    error
    WhenNetwork failure, DNS resolution failure, PostHog API unreachable, API key invalid, rate limit exceeded, or any server-side error (5xx) when fetching all flags and payloads in a single batch call.
    ThrowsPostHogFetchHttpError — thrown on HTTP error responses (non-2xx status codes). Properties: .response (Response object), .name === 'PostHogFetchHttpError'. PostHogFetchNetworkError — thrown on network/fetch failures. Properties: .error (underlying Error), .name === 'PostHogFetchNetworkError'. Both are exported from @posthog/core (dependency of posthog-node).
    Required handlingCaller MUST wrap in try-catch. getAllFlagsAndPayloads() makes a live network call and rejects on failure. Particularly dangerous in initialization code where flag payloads drive configuration — uncaught rejection crashes the entire startup flow or request handler. Minimum handling: try { const { featureFlags, featureFlagPayloads } = await posthog.getAllFlagsAndPayloads(userId); const config = featureFlagPayloads['ui-config']; return { flags: featureFlags, config }; } catch (err) { console.error('Failed to fetch flags and payloads:', err); return { flags: {}, config: null }; // Safe default }
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1]
  • getFeatureFlagPayload · get-feature-flag-payload-network-error
    error
    WhenNetwork failure, DNS resolution failure, PostHog API unreachable, API key invalid, rate limit exceeded, or server-side error during flag payload fetch.
    ThrowsPostHogFetchHttpError or PostHogFetchNetworkError (from @posthog/core). Same error types as getFeatureFlag(). Note: returns undefined (not throws) when the flag exists but has no payload — only network/auth failures throw.
    Required handlingCaller MUST wrap in try-catch. Payload failures are particularly dangerous because callers often use payloads for dynamic configuration — an uncaught rejection means configuration loading fails entirely, potentially crashing the service or leaving it with no configuration at all. Minimum handling: try { const payload = await posthog.getFeatureFlagPayload('ui-config', userId); const config = payload as { theme: string } ?? { theme: 'default' }; return config; } catch (err) { console.error('Failed to fetch flag payload:', err); return { theme: 'default' }; // Fall back to default config }
    costmediumin prodimmediate exceptionusers seedegraded performancevisibilitysilent
    Sources[1]
  • getFeatureFlagResult · get-feature-flag-result-network-error
    error
    WhenNetwork failure, PostHog API unreachable, API key invalid, rate limit exceeded, or server-side error during the combined flag+payload fetch.
    ThrowsPostHogFetchHttpError or PostHogFetchNetworkError (from @posthog/core). Same error types as getFeatureFlag() and getFeatureFlagPayload().
    Required handlingCaller MUST wrap in try-catch. getFeatureFlagResult() makes a live network call when local evaluation is unavailable. The combined nature of the result means both feature gating and configuration data are unavailable on failure. Minimum handling: try { const result = await posthog.getFeatureFlagResult('checkout-v2', userId); if (result?.enabled) { return result.payload as CheckoutConfig ?? defaultConfig; } return defaultConfig; } catch (err) { console.error('Feature flag result fetch failed:', err); return defaultConfig; // Fall back gracefully }
    costmediumin prodimmediate exceptionusers seedegraded performancevisibilityvisible
    Sources[1]
  • flush · flush-network-error
    warning
    WhenNetwork failure during batch send, HTTP error response from PostHog API (non-2xx), or event payload too large (413 — triggers automatic batch size reduction and retry, but ultimately throws if still failing). After retry exhaustion, error propagates.
    ThrowsPostHogFetchHttpError — on HTTP error responses from PostHog API. Properties: .response (Response), .name === 'PostHogFetchHttpError'. Note: 413 errors trigger batch size halving before finally propagating. PostHogFetchNetworkError — on network/fetch failures. Properties: .error (underlying Error), .name === 'PostHogFetchNetworkError'.
    Required handlingCaller SHOULD wrap in try-catch for serverless flush patterns. Flush failures mean analytics events are lost — not business-critical, but unhandled rejections crash serverless request handlers or interfere with graceful shutdown. Minimum handling: try { await posthog.flush(); } catch (err) { console.error('PostHog flush failed, events may be lost:', err); // Non-critical — continue normal execution }
    costlowin prodimmediate exceptionusers seedegraded performancevisibilitysilent
    Sources[1]
  • getRemoteConfigPayload · get-remote-config-missing-personal-api-key
    error
    WhenPostHog client is instantiated without a personalApiKey option (or with a project API key that starts with 'phc_' rather than a personal key starting with 'phx_'). getRemoteConfigPayload() throws synchronously before any network call.
    ThrowsError: 'Personal API key is required for remote config payload decryption' (synchronous throw — not a Promise rejection, but wrapping in try-catch handles both).
    Required handlingCaller MUST wrap in try-catch AND ensure personalApiKey is configured. Missing key configuration causes every getRemoteConfigPayload() call to throw, crashing the feature entirely on first use. Minimum handling: try { const config = await posthog.getRemoteConfigPayload('server-config'); return config as ServerConfig ?? defaultConfig; } catch (err) { if (err instanceof Error && err.message.includes('Personal API key')) { console.error('PostHog misconfiguration: personalApiKey required for remote config'); } else { console.error('Failed to fetch remote config:', err); } return defaultConfig; }
    costmediumin prodimmediate exceptionusers seedegraded performancevisibilitysilent
    Sources[1]
  • getRemoteConfigPayload · get-remote-config-network-error
    error
    WhenNetwork failure, DNS resolution failure, PostHog API unreachable, API key unauthorized (403), or server-side error during remote config fetch.
    ThrowsPostHogFetchHttpError or PostHogFetchNetworkError (from @posthog/core). Same network error types as other PostHog async methods.
    Required handlingCaller MUST wrap in try-catch. Remote config fetch failures mean the application runs with stale or default configuration — critical if the config controls behavior that affects all users. Same try-catch block as missing-personal-api-key handles both error cases.
    costmediumin prodimmediate exceptionusers seedegraded performancevisibilitysilent
    Sources[1]
  • evaluateFlags · evaluate-flags-network-error
    error
    WhenNetwork failure, DNS resolution failure, PostHog API unreachable, /flags endpoint timeout, or server-side 5xx error while fetching the consolidated flag evaluation response.
    ThrowsPostHogFetchHttpError or PostHogFetchNetworkError (from @posthog/core). Identical error surface to getFeatureFlag/getAllFlags — evaluateFlags shares the same underlying getFeatureFlagDetailsStateless() path.
    Required handlingCaller MUST wrap in try-catch (or use .catch()). evaluateFlags() is the canonical flag-eval entry point in PostHog v5.38+; failing to handle the rejection crashes the request handler that called it (since the typical pattern is to await evaluateFlags() early in a handler, then branch on flags.isEnabled() throughout). Minimum handling: try { const flags = await posthog.evaluateFlags(userId); if (flags.isEnabled('new-dashboard')) { renderNewDashboard(); } } catch (err) { console.error('Feature flag evaluation failed:', err); // Fall back to defaults — render conservative UI }
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1]
  • evaluateFlags · evaluate-flags-quota-limited-silent
    warning
    WhenPostHog project has exceeded its feature-flag quota — the /flags response comes back with quotaLimited: ['feature_flags'] and the snapshot's flags.isEnabled() returns the default-disabled answer for every flag. No exception is thrown — the failure is silent.
    ThrowsNothing — the call succeeds and returns a FeatureFlagEvaluations snapshot whose .quotaLimited property is true. Callers that branch on flags.isEnabled() without checking quotaLimited get the wrong answer.
    Required handlingCallers SHOULD check the returned snapshot's quotaLimited flag before trusting flag values. Alternatively, treat all flag evaluations as potentially default-only in production code (defensive branching). Defensive handling: const flags = await posthog.evaluateFlags(userId); if (flags.quotaLimited) { // Log + alert — flag evaluation is degraded; UI is showing defaults. logger.warn('PostHog flag quota exceeded — flags returning defaults'); } if (flags.isEnabled('new-dashboard')) { ... }
    costlowin prodsilent failureusers seedegraded performancevisibilitysilent
    Sources[1]

Sources

Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.

Official documentation

Research notes

Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.

Sources — posthog-node

All behavioral claims in contract.yaml are documented here with direct references.


Official Documentation

PostHog Node.js SDK Guide

URL: https://posthog.com/docs/libraries/node Covers:

  • All method signatures: capture, identify, getFeatureFlag, isFeatureEnabled, getAllFlags, shutdown
  • Fire-and-forget design of capture/identify: "The SDK does not throw errors for things happening in the background to ensure it doesn't affect your process."
  • Async nature of feature flag methods
  • shutdown() async behavior

PostHog SDK Reference

URL: https://posthog.com/docs/references/posthog-node Covers:

  • Full API reference with TypeScript signatures
  • Return types for all methods
  • Options parameters

Feature Flags Guide

URL: https://posthog.com/docs/feature-flags/bootstrapping-and-local-evaluation Covers:

  • getFeatureFlag, isFeatureEnabled, getAllFlags make network calls
  • Error handling recommendations for feature flag evaluation
  • Local evaluation option (still requires initial network call for flag definitions)

Error Tracking

URL: https://posthog.com/docs/error-tracking/capture Covers:

  • captureException() method behavior
  • Exception schema requirements

GitHub Repository

posthog-js-lite (Node SDK source)

URL: https://github.com/PostHog/posthog-js-lite/tree/main/posthog-node Evidence:

  • Source code confirms async nature of getFeatureFlag, isFeatureEnabled, getAllFlags
  • shutdown() implementation shows async flush behavior

Real-World Evidence

n8n — getAllFlags without try-catch (True Positive)

Stars: >90,000 GitHub stars File: packages/cli/src/posthog/index.ts Pattern:

return await this.postHog.getAllFlags(fullId, {
  personProperties: { created_at_timestamp: user.createdAt.getTime().toString() },
});

No try-catch. If PostHog API is unreachable, this rejects and the error propagates to callers. Evidence quality: partial (>1k stars, confirmed anti-pattern)


TODO: Additional Real-World Evidence Needed

To upgrade evidence_quality from partial to confirmed, find repos that use getFeatureFlag or isFeatureEnabled directly (not just capture/identify). The repos scanned so far mostly use posthog-node for telemetry events only — the feature flag surface has limited real-world coverage.

High-value targets to find:

  • SaaS apps with feature flag gating (A/B test rollouts, beta access)
  • Next.js apps using PostHog feature flags for server-side rendering decisions
  • Search GitHub for: posthog-node getFeatureFlag or isFeatureEnabled in TypeScript files

Confirmed repos using only capture/identify (no feature flag calls to analyze):

  • documenso, trigger.dev, hoppscotch, medusa — telemetry-only usage

Security Advisories

Shai-Hulud 2.0 Supply Chain Attack (November 2025)

URL: https://posthog.com/blog/nov-24-shai-hulud-attack-post-mortem Relevance: Package integrity (supply chain), not Nark profiles. Action: Use posthog-node >=5.25.0 or verify package integrity.

Need a different package?
Request a profile