Designed for Claude Code, also compatible with Codex and OpenClaw
PostHog Incident Runbook
Overview
Rapid incident response for PostHog integration failures. PostHog Cloud has its own status page (status.posthog.com) — the first step is always determining whether the issue is PostHog-side or your integration.
Severity Levels
Level
Definition
Response Time
Examples
P1
Analytics completely down
< 15 min
All capture calls failing, feature flags returning defaults
P2
Degraded analytics
< 1 hour
High latency, partial event loss, slow flag eval
P3
Minor impact
< 4 hours
Webhook delays, specific event type missing
P4
No user impact
Next day
Monitoring gaps, dashboard stale data
Quick Triage (Run First)
set -euo pipefail
echo"=== PostHog Triage ==="echo""# 1. Is PostHog Cloud up?echo -n "PostHog US Cloud: "
curl -sf -o /dev/null -w "%{http_code}" https://us.i.posthog.com/healthz || echo"UNREACHABLE"echo""# 2. Can we capture events?echo -n "Event capture: "
curl -sf -o /dev/null -w "%{http_code}" -X POST 'https://us.i.posthog.com/capture/' \
-H 'Content-Type: application/json' \
-d "{\"api_key\":\"${NEXT_PUBLIC_POSTHOG_KEY}\",\"event\":\"triage_test\",\"distinct_id\":\"triage\"}" || echo"FAILED"echo""# 3. Can we evaluate flags?echo -n
curl -sf -o /dev/null -w -X POST \
-H \
-d ||
[ -n ];
-n
curl -sf -o /dev/null -w \
-H ||
-n
curl -sf -o /dev/null -w ||
Is PostHog Cloud healthy (status.posthog.com)?
├── NO → PostHog outage
│ ├── Enable graceful degradation (feature flags return defaults)
│ ├── Monitor status.posthog.com for resolution
│ └── Events will be lost during outage (capture is fire-and-forget)
│
└── YES → Our integration issue
├── Are we getting 401? → API key issue (see Error 401 below)
├── Are we getting 429? → Rate limited (see Error 429 below)
├── Are events just not appearing? → Check flush/shutdown (see below)
└── Are flags returning defaults? → Check personalApiKey (see below)
Immediate Actions by Error Type
401/403 — Authentication Failed
set -euo pipefail
# Verify API key type and validityecho"Project key prefix: $(echo "$NEXT_PUBLIC_POSTHOG_KEY" | head -c 4)"echo"Personal key prefix: $(echo "$POSTHOG_PERSONAL_API_KEY" | head -c 4)"# Test project key (should return HTTP 200)
curl -s -o /dev/null -w "Capture: %{http_code}\n" -X POST 'https://us.i.posthog.com/capture/' \
-H 'Content-Type: application/json' \
-d "{\"api_key\":\"$NEXT_PUBLIC_POSTHOG_KEY\",\"event\":\"test\",\"distinct_id\":\"test\"}"# Test personal key (should return project list)
curl -s -o /dev/null -w "Admin: %{http_code}\n""https://app.posthog.com/api/projects/" \
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY"# Fix: If key is invalid, rotate in PostHog dashboard and update secrets
429 — Rate Limited
set -euo pipefail
# PostHog rate limits (private API only):# - Analytics endpoints: 240/min, 1200/hour# - HogQL query: 1200/hour# - Local flag eval polling: 600/min# - Capture endpoints: NO LIMIT# Immediate: Cache API responses, reduce polling frequency# Long-term: See posthog-rate-limits skill
Events Not Appearing
set -euo pipefail
# Most common cause: not calling flush/shutdown in serverless# Check 1: Is capture endpoint reachable?
curl -s -X POST 'https://us.i.posthog.com/capture/' \
-H 'Content-Type: application/json' \
-d "{\"api_key\":\"$NEXT_PUBLIC_POSTHOG_KEY\",\"event\":\"debug_test\",\"distinct_id\":\"debug-$(date +%s)\"}" | jq .
# Expected: {"status": 1}# Check 2: Verify API host is correct (common mistake)# WRONG: https://app.posthog.com (this is the UI)# RIGHT: https://us.i.posthog.com (this is the ingest endpoint)
Feature Flags Returning Defaults
// Most common causes:// 1. No personalApiKey → falls back to remote eval which may fail// 2. Flags not loaded yet → check timing// 3. Wrong project key → flags from different project// Fix 1: Add personalApiKeyconst posthog = newPostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
personalApiKey: process.env.POSTHOG_PERSONAL_API_KEY, // Required for local eval
});
// Fix 2: Wait for flags in browser
posthog.onFeatureFlags(() => {
// Now flags are loadedconst value = posthog.isFeatureEnabled('my-flag');
});
Graceful Degradation Pattern
// PostHog should NEVER crash your appfunctionsafeCapture(distinctId: string, event: string, props?: Record<string, any>) {
try {
posthog.capture({ distinctId, event, properties: props });
} catch {
// Swallow error — analytics failure should never impact users
}
}
asyncfunctionsafeFlag(key: string, userId: string, fallback: boolean = false): Promise<boolean> {
try {
const result = await posthog.isFeatureEnabled(key, userId);
return result ?? fallback;
} catch {
return fallback; // Return safe default
}
}