Skip to content

fix(attachments): prevent worker OOM on large event attachments - #1281

Merged
betegon merged 2 commits into
mainfrom
fix/attachment-memory-limit
Sep 3, 2026
Merged

betegon merged 2 commits into
mainfrom
fix/attachment-memory-limit

Conversation

@betegon

@betegon betegon commented Sep 2, 2026

Copy link
Copy Markdown
Member

Summary

get_event_attachment crashes the Cloudflare Worker with TypeError: Memory limit exceeded before EOF when downloading large attachments. This is live and ongoing — Sentry issue MCP-SERVER-F5W: 161 events, 10 users, first seen 2026-02-22, still firing (last 2026-09-01) across orgs and clients (codex, cursor).

Root cause: the download buffered the file ~4–5× in memory at once — downloadResponse.blob() → blob.arrayBuffer() (a copy) → base64 (+33%) → JSON-serialization of the base64 — blowing the 128 MB isolate on the tens-of-MB tail. Real download sizes (90d span telemetry): p50 ≈ 90 KB, p99 ≈ 16 MB, max ≈ 38 MB.

Refs #1267.

Changes

  • Halve peak memory: read the body once via arrayBuffer() and base64-encode from a Buffer view (bytesToBase64, no intermediate Blob/copy). Peak drops from ~4–5× to ~2× the payload.
  • Inline size cap (30 MB): gated on the already-fetched metadata size, so an oversized attachment never issues the doomed download. 30 MB → ~80 MB peak, safely under the isolate; covers p99 and all observed traffic inline.
  • Usable oversized fallback: the download URL is an authenticated API endpoint (bare fetch → 401), so a plain link is a dead end for agents. For objectstore attachments (the modern default) we resolve the presigned URL from the download endpoint's 302 Location — a self-authenticating, time-limited link that downloads with no credentials. Legacy filestore attachments (no redirect) fall back to authenticated-download instructions (sentry api …).
  • Empty/truncated guard: a zero-byte body when metadata reports a non-empty file is now surfaced instead of emitting empty content.

Verification

Confirmed end-to-end against a real attachment (created in a test org):

  1. Authenticated download → 302 to an objectstore presigned URL.
  2. That presigned URL fetched with no auth header → 200 + the correct bytes.

Also confirmed the download endpoint is auth-gated (401/403 unauthenticated) and validated the redirect vs. streaming behavior against getsentry/sentry source.

Testing

pnpm tsc, biome lint/format, ast-grep, and the full mcp-core suite pass. New coverage: text (.log) attachments, oversized objectstore (→ presigned URL), oversized legacy (→ auth instructions), and empty body.

🤖 Generated with Claude Code

@betegon
betegon marked this pull request as ready for review September 2, 2026 19:23
@betegon
betegon force-pushed the fix/attachment-memory-limit branch from 98e894b to 56a2bc0 Compare September 2, 2026 19:38
Comment thread packages/mcp-core/src/tools/catalog/get-event-attachment.ts Outdated
get_event_attachment crashed the Cloudflare Worker with
"TypeError: Memory limit exceeded before EOF" on large attachments
(Sentry issue MCP-SERVER-F5W: 161 events, 10 users, still firing across
orgs). The download buffered the file ~4-5x in memory at once
(Blob + arrayBuffer copy + base64 + JSON serialization), blowing the
128 MB isolate on the tens-of-MB tail (observed p99 ~16 MB, max ~38 MB).

- Read the body once via arrayBuffer() and base64-encode from a Buffer
  view (bytesToBase64), roughly halving peak memory for the download path.
- Cap inline downloads at 30 MB, gated on the already-fetched metadata
  size so the doomed request is never issued.
- Oversized fallback resolves the objectstore presigned URL (302 Location)
  so agents can download directly with no credentials; legacy filestore
  attachments fall back to authenticated-download instructions.
- Flag empty/truncated download bodies instead of emitting empty content.
- Add coverage for text, oversized (objectstore + legacy), and empty-body
  attachments.

Refs #1267

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@betegon
betegon force-pushed the fix/attachment-memory-limit branch from 56a2bc0 to c9a00c0 Compare September 2, 2026 20:24
@betegon
betegon requested a review from BYK September 3, 2026 10:44
Comment thread packages/mcp-core/src/api-client/client.ts
Comment thread packages/mcp-core/src/api-client/client.ts
Address review feedback: give getEventAttachment's maxInlineBytes a
default (DEFAULT_MAX_INLINE_ATTACHMENT_BYTES) so the size gate applies to
every caller by default, and make it the single source of truth for the
cap (the tool imports it for the message instead of redefining it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@betegon
betegon merged commit 64aa24e into main Sep 3, 2026
16 of 17 checks passed
@betegon
betegon deleted the fix/attachment-memory-limit branch September 3, 2026 14:40

This branch was previously deployed

1 inactive deployment
Actions — 5d027a6f Deployed Sep 3, 2026 by betegon via eval #1107
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants