Skip to content

bug(mcp): PostHog MCP drops mid-session and never reconnects #3321

Description

@slshults

(Fable 5 wrote this...)

Bug description

In PostHog Code, the built-in posthog MCP server keeps dropping mid-session: tool calls work fine for a while, then the harness reports the MCP connection has dropped and it never comes back for the rest of the session. The same PostHog MCP stays connected fine in Claude Code and Codex on the same machine. Reported by a customer on the latest build (0.57.70, macOS) after weeks of the same behavior across auto-updates, so this is not stale-version cruft.

Two mechanisms in the current code combine to produce this:

  1. The first-party OAuth grant still uses rotating refresh tokens with reuse protection. PostHog Code signs in as a pre-registered first-party app, so every refresh rotates the token, and reusing a rotated-out token after the 2 minute grace period revokes the whole grant (all access + refresh tokens). A rotation race between concurrent sessions, or a rotated token that doesn't get persisted, kills the grant mid-session and forces a full re-login. Notably, DCR/CIMD clients (how Claude Code and Codex connect to mcp.posthog.com) were deliberately exempted from rotation in PostHog/posthog posthog/api/oauth/views.py (rotate_refresh_token) with a comment that MCP clients "don't reliably save the new refresh token returned during rotation, causing sessions to break", and they get 7-day tokens. PostHog Code has the exact exposure that exemption was created to avoid, which is why the same MCP only dies in PostHog Code.

  2. One failed forward permanently kills the MCP for the session. The local MCP proxy (packages/workspace-server/src/services/mcp-proxy/, added in fix(code): proxy MCP requests locally to keep OAuth tokens fresh #1713) retries once with a force-refreshed token when the response body contains authentication_failed, but any other forward failure returns a 502 to the embedded harness, which then treats the posthog server as disconnected for the remainder of the session. There is no reconnect path short of restarting the session/app.

Server-side telemetry corroborates the grant deaths: the affected user was forced through a full re-authorization of the PostHog Code grant every 2 to 4 days through June (a healthy grant has a 30 day refresh token and should essentially never force re-login), while their ChatGPT connection (DCR, non-rotating) authorized exactly once in the same window. The re-auth churn eased after PostHog/posthog#66919 (7 day first-party access tokens, merged Jun 30) cut refresh frequency, but the mid-session drops continue: on Jul 9 the user's client fired six fresh $mcp_initialize calls within 2 minutes while retrying, while their Claude Code and Codex sessions against the same MCP stayed healthy throughout.

Expected: the PostHog MCP connection survives a session in PostHog Code at least as reliably as it does in Claude Code and Codex, and a transient failure of one forwarded request doesn't take the server down for the whole session.

How to reproduce

Not deterministic, but the shape is:

  1. Sign in to PostHog Code and use the posthog MCP in a session.
  2. Have anything invalidate the token mid-session: a refresh race between two concurrent sessions/processes sharing the grant, a rotated refresh token that fails to persist, or a transient failure on /oauth/token.
  3. The next MCP call through the local proxy fails, the harness marks the posthog server disconnected, and it stays dead until the app is restarted (or, if the grant was revoked, until the user logs in again).

Running several PostHog Code sessions concurrently appears to increase the odds (more refresh contention on one grant).

Additional context

From support ticket 2694.

Related: #529 (closed, older refresh-loop symptom), #2582 (hung token refresh wedging bootstrap), #2812 (log out on rejected refresh token), PostHog/posthog#66919 (7 day first-party TTL, which reduced but did not eliminate this).

App: PostHog Code 0.57.70, macOS. Affects at least one customer continuously since late June across 0.55.x and 0.57.x builds.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions