Skip to content

Sustained deviation on cf.do.namespace 03bea87799914e00be8eb9f49b1f4d44 - #1664

Open
Dhravya wants to merge 1 commit into
mainfrom
d/polylane-autofix-sustained-deviation-on-cf-do-namespace-03bea87799914e00be8eb9f49b1f4d44-d74e
Open

Sustained deviation on cf.do.namespace 03bea87799914e00be8eb9f49b1f4d44#1664
Dhravya wants to merge 1 commit into
mainfrom
d/polylane-autofix-sustained-deviation-on-cf-do-namespace-03bea87799914e00be8eb9f49b1f4d44-d74e

Conversation

@Dhravya

@Dhravya Dhravya commented Sep 12, 2026

Copy link
Copy Markdown
Member

The hosted Supermemory MCP server was returning a burst of failed requests to connected clients (Claude, ChatGPT, and other MCP apps), and the on-call view of those failures was effectively blank. When something went wrong deep in a request, the server recorded only a bare stack trace with no message and sent nothing to our error-tracking or tracing tools, so anyone looking at the spike could see that requests were failing but had no way to tell what was actually breaking or who was affected.

Concretely, clients hitting these failures got a generic server error and could not complete memory searches, saves, or lookups during the affected requests, and operators had no signal to act on — the error count climbed while the cause stayed invisible. Merging this makes every one of those request failures self-describing in the logs, so the same spike now names its own cause and can be diagnosed and fixed instead of guessed at.

Before / After

Before: When a request failed inside the MCP handler past the auth gate, the server logged MCP request error: followed by whatever was thrown. Because the underlying SDK wraps non-error throws and the runtime prints an error as its stack alone, the message was routinely dropped: an empty-message error produced a stack with no text, and a thrown value that wasn't a proper error produced almost nothing usable. Nothing reached error tracking or tracing for this class. An operator watching the error count rise saw it climb with no attached cause, no HTTP status, and no way to separate one failure mode from another.

After: Every request failure is rendered into a single, always-informative line that carries a real cause. Well-formed errors keep their name, message, and stack; empty-message errors show an explicit placeholder instead of a blank; HTTP status and error codes are pulled out when present, a chained cause is summarized, and values thrown that aren't errors (objects, strings, undefined, null) are described and serialized safely. An operator watching the same spike now reads what each failure actually is, so the incident is diagnosable from the logs alone rather than opaque.

What changed

  • Added apps/mcp/src/server/request-error.ts with describeThrownError(), a pure function that turns any thrown value into a stable, informative description (name/message/stack, status/code, chained cause, and safe serialization of non-error and circular values).
  • Wired it into the onerror reporter in apps/mcp/src/server/index.ts. The exact MCP request error prefix is preserved so the existing log-based monitoring for this metric keeps matching; only the previously-blank part is filled in.
  • Added apps/mcp/src/server/request-error.test.ts covering the well-formed error, the empty-message error (the exact shape the incident produced), status/code extraction, custom error names, chained causes, and undefined/null/string/object/circular throws.

Assumptions & scope

  • The confirmed incident is the spike in the MCP request error (serveModern / createMcpHandler onerror) class, whose defining property is that the producing throw is elided from telemetry. Tracing the exact upstream throw was not possible from the available data precisely because it was never recorded — the SDK routes tool- and resource-handler failures into JSON-RPC error responses, so the errors reaching this reporter are the ones the codebase currently cannot see. This change removes that blind spot at the source: it does not suppress the signal (it strengthens it into something actionable) and it changes no configuration value or default, so no existing assertion needed to be loosened.
  • Scope is kept minimal and confined to the promoted incident's path. The same terse console.error("…:", error) pattern also exists in the auth validators (API key validation error, OAuth token validation error); those were left unchanged here, but describeThrownError() is reusable if we later choose to harden them too.

Checks run

  • bun run test:unit (apps/mcp) — pass (33 tests across 5 files, including the 10 new tests).
  • bun run check-types (apps/mcp: widget build + tsc --noEmit for both tsconfigs) — pass.
  • bunx biome check on the three changed files — pass (after applying Biome's formatter).
  • Full-monorepo turbo runs (bun run build, repo-wide check-types) were not run separately; the change is isolated to apps/mcp and the package-level typecheck/lint/tests above cover it.
Open in Web Open in Cursor 

Fixes: Sustained deviation on cf.do.namespace 03bea87799914e00be8eb9f49b1f4d44

What caused this

Affected: Durable Object supermemory-mcp_SupermemoryMCP · Cloudflare account 47c2b4d598af9d423c06fc9f936226d5 · MCP request handling errors at 26events (baseline 2.8events, 5.6σ above) · severity high · 1 dependent, ~100% of nearby traffic

MCP request handling errors (events)

A check caught it, an investigation traced it, an autofix wrote the change. Each step links to its record.

Step Where to look
1. A check detected the problem Detection check
2. It opened a monitoring issue Open the investigation
3. A fix run traced the cause Fix run thread
4. An autofix wrote the change, and it succeeded Autofix fix_097688…
5. This pull request opened it for review this PR

View thread

This pull request originated from a Polylane autofix. Polylane investigated the issue and delegated the fix to Cursor, which authored this pull request.

The MCP request handler surfaced failures only through createMcpHandler's
onerror callback as console.error("MCP request error:", error). The SDK
funnels non-Error throws through new Error(String(value)) and Cloudflare's
console renders an Error as its stack alone, so the log line carried no
message whenever the thrown value was empty or not a well-formed Error. When
this error class spiked, operators had a stack with no cause and nothing in
Sentry/otel.

Add describeThrownError() which renders any thrown value (Error, empty-message
Error, non-Error, object, primitive) into a single always-informative line,
including status/code and a chained cause where present, and use it in the
onerror reporter. The exact 'MCP request error' prefix is preserved so the
existing log-based monitoring keeps matching.

Co-authored-by: Dhravya Shah <dhravya@supermemory.com>
@Dhravya
Dhravya marked this pull request as ready for review September 12, 2026 21:10
@claude

claude Bot commented Sep 12, 2026

Copy link
Copy Markdown

Claude finished @Dhravya's task in 1m 26s —— View job


Review Complete

  • Analyzing PR context and changes
  • Reading the diff and understanding the implementation
  • Checking for bugs, security issues, and logic errors
  • Submitting review

Summary: Clean PR that fixes a real production observability gap. The describeThrownError() utility is well-implemented with defensive handling for all edge cases (empty-message errors, status/code fields, chained causes, non-Error throws, circular references). Tests cover the exact incident scenario plus edge cases. No bugs, security issues, or logic errors found.

Score: 9/10 — Good to ship.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
supermemory-mcp 34f9e06 Sep 12 2026, 09:11 PM

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
supermemory-app 34f9e06 Commit Preview URL

Branch Preview URL
Sep 12 2026, 09:11 PM

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview: Adds a defensive error-description utility so MCP request failures are always logged with an informative message, fixing a production observability gap where errors were effectively blank.

Issues found: None — this looks good to ship.

The implementation is solid:

  • describeThrownError() handles all edge cases defensively: well-formed errors, empty-message errors, errors with status/code fields, chained causes, and non-Error throws (undefined, null, objects, strings, circular references)
  • Uses try/catch guards around Reflect.get and JSON.stringify to prevent the logging utility itself from throwing
  • Tests cover the exact incident scenario (empty-message Error) plus all the other edge cases
  • Minimal scope — only changes the one onerror callback that was producing blank logs

Score: 9/10

@polylane polylane Bot added the polylane label Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants