Classify Kernel API failures on tool errors - #133
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
dcruzeneil2
left a comment
There was a problem hiding this comment.
Looks good to me. The new error classification makes the analytics much more useful while preserving the existing error messages agents receive and keeping sensitive data out of PostHog. It would be helpful to add regression tests for HTTP status classification, timeout errors, and the unchanged MCP response behavior in a follow-up, but this does not need to block the PR. Approved.
…inified classes The transport branch read error.constructor.name, which the production bundle minifies to a mangled identifier, so a timeout would have been recorded as a one-letter error type. Classify by instance instead, and fall back to error.name rather than the constructor's. Adds bun test to CI.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Took the regression tests here rather than as a follow-up, and they earned it immediately.
The timeout case failed against what was on the branch. Client-visible behaviour and messages are unchanged, which is now asserted rather than asserted-by-me. |
@types/bun only re-exports bun-types. Depending on bun-types removes the extra hop and takes the types from the package Bun's own team publishes. Referenced from the test file rather than tsconfig's types array, which would drop the automatic @types/* includes.
|
On Socket's flag of
Runtime never needed it — |
Summary
Every failed MCP tool call currently records
$mcp_error_type = "Error", so a stale session id, an org hitting its concurrency limit, and a fault on our side are indistinguishable in analytics. This names them.toolErrorResponsebecomesthrowToolError, which throws instead of returning anisErrorresult. The thrown error'snamecarries the Kernel API status —KernelApiError404,KernelApiError429,KernelApiError502— or a fixed name for transport failures that never got a response (KernelApiTimeout,KernelApiConnectionError,KernelApiAborted).Why this works
The analytics SDK reads the error category from a thrown error's
name. A returnedisErrorresult has no error object, so it coerces from the result text and always lands as a genericError. That's why the existing data has exactly one error type.Because
throwToolErroris the single funnel for caught API errors, the split falls out with no per-call-site logic:KernelApiError<status>— the Kernel API rejected the request.Error— the tool itself rejected the call (missingsession_id, conflicting arguments, invalid config), which never reaches the API.Agents see no change
The MCP SDK converts a thrown error back into the same
isErrortext result, and the message string is byte-identical to what the helper produced before. Verified against the real API:manage_browsersgetwith an unknown session id →Error in manage_browsers (get): 404 browser session '...' not found,isError: truemanage_browsersgetwith no session id →Error: session_id is required for get action.,isError: truePrivacy
Status codes only. No message, parameters, or response are captured — confirmed on the captured events — and no
$exceptionevents are emitted.$mcp_error_typeis already on the send allow-list, so nothing new is added to what leaves the server.Verification
Ran the route locally against the production API and checked the captured events:
$mcp_error_typeKernelApiError404session_idErrortsc --noEmitandprettier --checkpass on every touched file.Tests
bun test(no new runner, bun is already the package manager) covering the classification table, message preservation, and what the client actually receives over an in-memory MCP transport. Wired into CI as a step after the type check -- this is the repo's first test, so the harness comes with it.Writing the transport case found a bug in the first version of this PR.
errorNamefell back toerror.constructor.name, and the SDK's error classes are bundled and minified in the production build (APIConnectionTimeoutErroris emitted asfN), so a timeout would have been recorded as$mcp_error_type = "fN". Transport failures are now classified byinstanceofand the last-resort fallback readserror.name, which can never be a mangled identifier. The status path was always safe -- it readserror.status, not a class name.Follow-up, deliberately not here
The generic
Errorbucket still mixes several kinds of tool-side rejection (missing argument vs conflicting arguments vs invalid config). Naming those too is mechanical but touches ~95 call sites, so it belongs in its own change if the split turns out to be worth it.Note
Medium Risk
Wide change to error handling on every MCP tool path; behavior for agents should be unchanged but analytics and throw semantics differ from returned errors.
Overview
Replaces
toolErrorResponsewiththrowToolError, which throws aToolCallErrorwhosenameencodes the failure kind (KernelApiError404,KernelApiError429, transport types viainstanceof, etc.) so PostHog$mcp_error_typeis no longer always genericError. MCP clients still get the sameisErrortext—the SDK maps the throw back to the prior shape.All Kernel API tool
catchblocks now callthrowToolErrorinstead of returning an error result. Input validation keepserrorResponse.Adds
bun test(responses.test.ts),bun-types, atestscript, and a CI step after typecheck.Reviewed by Cursor Bugbot for commit 4d70afc. Bugbot is set up for automated code reviews on this repo. Configure here.