Skip to content

fix(mcp): local MCP CLI telemetry never awaits/flushes its PostHog client before the process can exit #8690

Description

@JSONbored

⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.

Context

packages/loopover-mcp/lib/telemetry.ts:33-62's recordMcpToolCall constructs a new PostHog
client per call, calls client.capture(...), and returns — synchronously, fire-and-forget, with no
flush()/shutdown() call and no client reuse.

The remote counterpart, src/mcp/telemetry.ts:60-86, does await client.flush() — added by commit
80f4aec99 ("fix(mcp): defer tool-call telemetry via waitUntil instead of firing it forgotten",
closing #7233) specifically because "capture() itself is fire-and-forget and returns before that
request lands," meaning without an awaited flush the event can be silently dropped if the runtime
tears down before the network POST completes.

recordStdioToolTelemetry (bin/loopover-mcp.ts:1715) is the single chokepoint every one of the
~37 registerStdioTool-registered MCP tools funnels through. If the local stdio server process
exits shortly after a tool call (the client disconnects, or the --stdio process is killed), the
in-flight PostHog network request for that call — and every earlier call whose client was never
shut down — can be silently dropped. Additionally, a new, un-closed PostHog client instance
accumulates in memory per call over a long-running session.

Requirements

  • Await client.flush() (or client.shutdown(), whichever the PostHog Node SDK recommends for a
    short-lived process) before recordMcpToolCall returns, mirroring the remote
    src/mcp/telemetry.ts fix exactly.
  • Consider reusing a single client instance across calls within the same process lifetime rather
    than constructing a new one per call, if that doesn't complicate the flush-on-exit guarantee (state
    the chosen approach explicitly in the PR description either way).

Deliverables

  • recordMcpToolCall awaits a flush/shutdown before returning, so its caller can be confident
    the telemetry event has actually been sent (or definitively failed) before the process exits.
  • A new test (mocking posthog-node, mirroring the pattern in
    test/unit/mcp-local-telemetry.test.ts) with a capture call that resolves on a delay, asserting
    recordMcpToolCall's returned promise does not resolve until the mocked flush/network call
    completes — currently it resolves synchronously regardless.
  • No regression in the case where telemetry is disabled/opted out (existing behavior for that
    path is unchanged).

All three Deliverables are required in the same PR.

Test Coverage Requirements

packages/loopover-mcp/** is measured by codecov/patch (99%+ target, branch-counted). The new
test must exercise the previously-synchronous, fire-and-forget path directly, proving the fix
actually awaits completion.

Expected Outcome

Local MCP CLI telemetry events are not silently dropped when the stdio process exits shortly after a
tool call, matching the guarantee already provided by the remote MCP telemetry path.

Links & Resources

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions