Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

@sentry/cloudflare 11: only the first request per isolate reports errors; later captures emit no envelope

未关闭
#24,762 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
58/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
typescript
领域
backend, cloud

调研方向

Start at the withSentry entry point and inspect wrapRequestHandlerWithInit, especially the waitUntil?.(flushAndDispose(client)) lifecycle described in the report. Reproduce the issue with the provided concurrent burst and beforeEnvelope hook, then verify that reused isolates produce an envelope and transport request for every captured exception without an explicit flush.

由索引模型根据 Issue 内容生成。

描述

Bug Cloudflare Workers Waiting for: Product Owner
Version

@sentry/cloudflare 11.0.0 — regression from 10.75.3, which is unaffected.

Summary

In a Workers fetch handler wrapped with withSentry, only the first request an isolate serves reports errors. Every subsequent request on that same isolate captures an exception that never becomes an envelope — no transport request is made, nothing reaches Sentry, and nothing is logged.

The failure is silent in both directions. On a failing request the SDK still reports itself healthy:

{"initialized":true,"hasClient":true,"enabled":true,"transport":true}

captureException() returns an event id as usual. Only a beforeEnvelope hook reveals that no envelope is ever produced.

Under production traffic, where an isolate serves many requests, this drops the large majority of errors.

Repro

A fetch handler that captures an exception, plus a module-scope counter so each log line says which isolate served the request and how many it had already served:

import * as Sentry from '@sentry/cloudflare';

let isolate: string | undefined;
let served = 0;

const handler = {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    if (!isolate) isolate = crypto.randomUUID().slice(0, 8);
    const client = Sentry.getClient();
    console.log('diag', JSON.stringify({
      isolate, n: ++served,
      initialized: Sentry.isInitialized(),
      hasClient: Boolean(client),
      transport: Boolean(client?.getTransport()),
    }));
    client?.on('beforeEnvelope', () => console.log('envelope', isolate, served));
    client?.on('afterSendEvent', (e, r) => console.log('sent', e.event_id, r?.statusCode));

    Sentry.captureException(new Error('probe'));
    return new Response('ok', { status: 500 });
  },
} satisfies ExportedHandler<Env>;

export default Sentry.withSentry(
  (env: Env) => ({ dsn: env.SENTRY_DSN, tracesSampleRate: 0 }),
  handler,
);

Deploy, then drive it with concurrent bursts so isolates are reused (sequential curls each got a fresh isolate and all succeeded — reuse is required to see it):

for round in 1 2 3; do
  for i in $(seq 1 10); do curl -s -o /dev/null "$URL/probe?i=$round-$i" & done
  wait
done

Read the results with wrangler tail --env <env> --format json and group by isolate / n.

Results

Same Worker, same burst, same deploy pipeline — only the SDK version differs.

SDK n=1 n>1
10.75.3 8/8 delivered 5/5 delivered (n=2, n=3)
11.0.0 13/13 delivered 0/16 delivered (n=2…6)

One isolate under 11.0.0, verbatim:

isolate 809886ef  n=1  envelope ✅
                  n=2  none     ❌
                  n=3  none     ❌
                  n=4  none     ❌
                  n=5  none     ❌
                  n=6  none     ❌

29 invocations across 16 isolates on 11.0.0; the correlation with n has no exceptions.

Suspected cause

Not verified, offered as a starting point: wrapRequestHandlerWithInit ends the request with waitUntil?.(flushAndDispose(client)), and the client object demonstrably survives isolate reuse — registering a hook per request causes a later request to log earlier requests' hook callbacks, and the same event_id is reported under two different request labels. A client disposed by request n then appears to be reused by request n+1, where it still passes isInitialized() / getClient() / getTransport() but emits no envelope.

Two things that make this easy to miss
  1. An explicit await Sentry.flush() inside the handler masks it entirely — the event is sent during the request, before any dispose. A probe written with a flush shows 100% delivery and hides the bug.
  2. Every health signal stays green. isInitialized, getClient, getTransport, and the return value of captureException are all indistinguishable between a delivering and a non-delivering request.
Environment
  • @sentry/cloudflare 11.0.0 vs 10.75.3
  • Cloudflare Workers, compatibility_date 2026-08-15 (so nodejs_compat is on by default)
  • Hono 4.13.8; the capture happens in Hono's onError, but the route is an ordinary fetch handler under withSentry
  • tracesSampleRate: 0
  • Migration followed as documented: the only change for this usage was replacing sendDefaultPii with dataCollection. Nothing in the v10→v11 guide or the 11.0.0 release notes mentions client lifecycle, disposal, or isolate reuse.
主要语言
TypeScript
星标
8.7k
派生
1.9k
平均合并
1 天 15 小时
30 天内合并 PR
523

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

getsentry/sentry-javascript 的其他 Issue

查看 getsentry/sentry-javascript 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。