Skip to content

feat: instrument Durable Objects and Workflows with Sentry (lazy import, isolate-local init)#1663

Merged
jaypatrick merged 10 commits intomainfrom
copilot/integrate-sentry-sdk-durable-objects-workflows
Apr 25, 2026
Merged

feat: instrument Durable Objects and Workflows with Sentry (lazy import, isolate-local init)#1663
jaypatrick merged 10 commits intomainfrom
copilot/integrate-sentry-sdk-durable-objects-workflows

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 24, 2026

Adds Sentry error capture to all three Durable Objects and all four Cloudflare Workflow entrypoints using a shared captureExceptionInIsolate(env, error) helper in a new worker/services/sentry-isolate-init.ts module. The @sentry/cloudflare SDK is only dynamically imported when SENTRY_DSN is present at runtime, and a CloudflareClient is initialized once per isolate before each capture — zero overhead and no test-environment brittleness when Sentry is disabled.

Description

@sentry/cloudflare was already wired for the main Worker fetch/queue/scheduled handlers via withSentryWorker. DOs and Workflows were uninstrumented — exceptions from those entrypoints were silently dropped by Sentry, because DOs and Workflows each run in their own isolates, separate from the main worker's Sentry context.

Rather than wrapping classes with instrumentDurableObjectWithSentry / instrumentWorkflowWithSentry (which requires satisfying the Cloudflare runtime's branded DurableObject<Env, {}> constraint and forces a static SDK import), each file now calls captureExceptionInIsolate(env, error) from a shared service module. The shared helper lazy-loads the SDK only when SENTRY_DSN is set, initializes a CloudflareClient once per isolate via new Sentry.CloudflareClient() + Sentry.setCurrentClient() (matching the DSN/release/environment config from sentry-init.ts), and forwards to captureException(). Import failures are logged as warnings and the cache is reset so the next invocation can retry, rather than permanently silencing Sentry for the isolate lifetime.

Changes

  • New worker/services/sentry-isolate-init.ts — shared captureExceptionInIsolate(env, error) helper that:

    • Lazy-loads @sentry/cloudflare only when SENTRY_DSN is present
    • Logs a warning and resets the promise cache on import failure so later invocations can retry
    • Initializes a CloudflareClient once per isolate using new Sentry.CloudflareClient() + Sentry.setCurrentClient() + client.init() with dsn, release, and environment matching the main worker config (avoids Sentry.init() which is not exported from @sentry/cloudflare's main index)
    • Calls captureException(error) to forward the exception to Sentry
  • Durable ObjectscaptureExceptionInIsolate added in key error paths:

    • worker/rate-limiter-do.tscaptureExceptionInIsolate in alarm() catch block
    • worker/compilation-coordinator.tscaptureExceptionInIsolate in fetch() catch block
    • worker/ws-hibernation-do.tscaptureExceptionInIsolate in webSocketError() via this.state.waitUntil(...) so WebSocket teardown is not blocked on async module load
  • WorkflowscaptureExceptionInIsolate added in run() catch blocks:

    • worker/workflows/CompilationWorkflow.ts
    • worker/workflows/BatchCompilationWorkflow.ts
    • worker/workflows/CacheWarmingWorkflow.ts
    • worker/workflows/HealthMonitoringWorkflow.ts

All classes remain export class Foo (not renamed or wrapped), so worker/worker.ts, worker/workflows/index.ts, and all existing tests require no changes.

Testing

  • Unit tests added/updated
  • Manual testing performed — existing DO/Workflow tests (rate-limiter-do.test.ts, compilation-coordinator.test.ts, ws-hibernation-do.test.ts) instantiate the exported classes directly and pass {} as env; no test changes needed
  • CI passes — lazy-import approach avoids all DurableObject<Env, {}> brand-constraint type errors and ban-types lint errors; CloudflareClient+setCurrentClient pattern resolves the TS2339 error from the missing init export

Zero Trust Architecture Checklist

Worker / Backend

  • Every handler verifies auth before executing business logic — N/A (no handler logic changed)
  • CORS origin allowlist enforced (not *) on write/authenticated endpoints — N/A
  • All secrets accessed via Worker Secret bindings (not [vars]) — SENTRY_DSN already a Worker Secret
  • All external inputs Zod-validated before use — N/A (no new inputs)
  • All D1 queries use parameterized .prepare().bind() (no string interpolation) — N/A
  • Security events emitted to Analytics Engine on auth failures — N/A

Frontend / Angular

  • Protected routes have functional CanActivateFn auth guards — N/A
  • Auth tokens managed via Clerk SDK (not localStorage) — N/A
  • HTTP interceptor attaches ****** (no manual token passing) — N/A
  • API responses validated with Zod schemas before consumption — N/A

API Shield / Vulnerability Scanner

  • New/changed endpoints have a unique operationId in openapi.yaml — N/A
  • Resource endpoints include a security: annotation — N/A
  • Resource queries are scoped to the authenticated user — N/A
  • Missing/unauthorized resources return 404 — N/A
  • cloudflare-schema.yaml regenerated if openapi.yaml changed — N/A
Original prompt

Goal

Integrate the Sentry Cloudflare SDK's instrumentDurableObjectWithSentry and instrumentWorkflowWithSentry helpers into the existing Durable Object and Workflow classes in the worker/ directory.

The SDK is already installed (@sentry/cloudflare 10.43.0) and the base withSentry wrapper on the main Worker fetch handler is already in place in worker/worker.ts + worker/services/sentry-init.ts. This task is only about wrapping the Durable Objects and Workflow entrypoints.


API reference (from Sentry docs)

Durable Objects

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

class MyDoBase extends DurableObject<Env> { /* ... */ }

export const MyDo = Sentry.instrumentDurableObjectWithSentry(
  (env: Env) => ({ dsn: env.SENTRY_DSN, tracesSampleRate: 0.1 }),
  MyDoBase,
);

Workflows

import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
import * as Sentry from '@sentry/cloudflare';

class MyWorkflowBase extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) { /* ... */ }
}

export const MyWorkflow = Sentry.instrumentWorkflowWithSentry(
  (env: Env) => ({ dsn: env.SENTRY_DSN, tracesSampleRate: 0.1 }),
  MyWorkflowBase,
);

Key notes:

  • The Sentry config callback receives env — use env.SENTRY_DSN and env.SENTRY_RELEASE just like the main worker wrapper does.
  • The wrapped export replaces the original class export in worker.ts. The base class stays in its own file, unexported (or exported as the base name for testing purposes).
  • tracesSampleRate: 0.1 and environment: env.ENVIRONMENT ?? 'production' to match the existing worker wrapper pattern.
  • Only wrap when env.SENTRY_DSN is set — the instrumentDurableObjectWithSentry and instrumentWorkflowWithSentry functions handle this gracefully (they are no-ops when DSN is absent), so you can always wrap unconditionally.

Durable Objects to wrap

There are three Durable Object classes exported from worker/worker.ts:

1. RateLimiterDOworker/rate-limiter-do.ts

Currently: export class RateLimiterDO implements DurableObject { ... }

Change to:

  • Rename the class to RateLimiterDOBase (keep implements DurableObject)
  • At the bottom of the file, add:
    import * as Sentry from '@sentry/cloudflare';
    import type { Env } from './types.ts';
    
    export const RateLimiterDO = Sentry.instrumentDurableObjectWithSentry(
      (env: Env) => ({
        dsn: env.SENTRY_DSN,
        release: env.SENTRY_RELEASE ?? env.COMPILER_VERSION,
        environment: env.ENVIRONMENT ?? 'production',
        tracesSampleRate: 0.1,
      }),
      RateLimiterDOBase,
    );
  • In worker/worker.ts, the RateLimiterDO re-export at the bottom stays as-is (it now exports the wrapped version automatically since it comes from the same file).

2. CompilationCoordinatorworker/compilation-coordinator.ts

Currently: export class CompilationCoordinator implements DurableObject { ... }

Change to:

  • Rename class to CompilationCoordinatorBase
  • Add at the bottom:
    import * as Sentry from '@sentry/cloudflare';
    import type { Env } from './types.ts';
    
    export const CompilationCoordinator = Sentry.instrumentDurableObjectWithSentry(
      (env: Env) => ({
        dsn: env.SENTRY_DSN,
        release: env.SENTRY_RELEASE ?? env.COMPILER_VERSION,
        environment: env.ENVIRONMENT ?? 'production',
        tracesSampleRate: 0.1,
      }),
      CompilationCoordinatorBase,
    );

3. WsHibernationDOworker/ws-hibernation-do.ts

Apply the same pattern: rename to WsHibernationDOBase, wrap with instrumentDurableObjectWithSentry.


Workflows to wrap

There are four Workflow classes in worker/workflows/:

  • CompilationWorkflowworker/workflows/CompilationWorkflow.ts
  • BatchCompilationWorkflowworker/workflows/BatchCompilationWorkflow.ts
  • CacheWarmingWorkflowworker/workflows/CacheWarmingWorkflow.ts
  • HealthMonitoringWorkflowworker/workflows/HealthMonitoringWorkflow.ts

For each workflow file:

  1. Read the file to understand the class signature and Params type it uses.
  2. Rename the class to <Name>Base (e.g. CompilationWorkflowBase).
  3. At the bottom of the file, add the instrumentWorkflowWithSentry wrapper and re-export the wrapped constant as the original name (e.g. export const CompilationWorkflow = Sentry.instrumentWorkflowWithSentry(..., CompilationWorkflowBase)).
  4. The Params type comes from worker/types.ts — use the correct one for each workflow (e.g. CompilationParams, BatchCompilationParams, CacheWarmingParams, HealthMonitoringParams).

The config callback for workflows:

(env: Env) => ({
  dsn: env.SENTRY_DSN,
  release: env.SENTRY_RELEASE ?? env.COMPILER_VERSION,
  environment: env.ENVIRONMENT ?? 'production',
  tracesSampleRate: 0.1,
})

worker/workflows/index.ts — no changes needed

The index re-exports by name, s...

This pull request was created from Copilot chat.

Copilot AI requested review from Copilot and removed request for Copilot April 24, 2026 23:33
@jaypatrick jaypatrick added enhancement New feature or request cloudflare Cloudflare Workers / CDN deployment Deployment tasks labels Apr 24, 2026
@jaypatrick jaypatrick modified the milestone: beta Apr 24, 2026
Copilot AI requested review from Copilot and removed request for Copilot April 24, 2026 23:40
Copilot AI requested review from Copilot and removed request for Copilot April 24, 2026 23:44
Copilot AI changed the title [WIP] Integrate Sentry SDK into Durable Object and Workflow classes feat: instrument Durable Objects and Workflows with Sentry Apr 24, 2026
Copilot AI requested a review from jaypatrick April 24, 2026 23:45
Copilot AI review requested due to automatic review settings April 25, 2026 00:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Extends existing Sentry integration in the Cloudflare Worker to also instrument Durable Object classes and Cloudflare Workflow entrypoints, so errors/traces from those runtime entrypoints are captured.

Changes:

  • Wrap the 3 Durable Objects with Sentry.instrumentDurableObjectWithSentry and export the wrapped constructors.
  • Wrap the 4 Workflows with Sentry.instrumentWorkflowWithSentry and export the wrapped constructors.
  • Rename the original exported classes to unexported *Base implementations to keep the wrapped export name stable.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
worker/rate-limiter-do.ts Renames DO class to RateLimiterDOBase and exports Sentry-instrumented RateLimiterDO.
worker/compilation-coordinator.ts Renames DO class to CompilationCoordinatorBase and exports Sentry-instrumented CompilationCoordinator.
worker/ws-hibernation-do.ts Renames DO class to WsHibernationDOBase and exports Sentry-instrumented WsHibernationDO.
worker/workflows/CompilationWorkflow.ts Renames workflow class to CompilationWorkflowBase and exports Sentry-instrumented CompilationWorkflow.
worker/workflows/BatchCompilationWorkflow.ts Renames workflow class to BatchCompilationWorkflowBase and exports Sentry-instrumented BatchCompilationWorkflow.
worker/workflows/CacheWarmingWorkflow.ts Renames workflow class to CacheWarmingWorkflowBase and exports Sentry-instrumented CacheWarmingWorkflow.
worker/workflows/HealthMonitoringWorkflow.ts Renames workflow class to HealthMonitoringWorkflowBase and exports Sentry-instrumented HealthMonitoringWorkflow.

Comment thread worker/ws-hibernation-do.ts Outdated
Comment thread worker/workflows/HealthMonitoringWorkflow.ts Outdated
Comment thread worker/workflows/CompilationWorkflow.ts Outdated
Comment thread worker/workflows/CacheWarmingWorkflow.ts Outdated
Comment thread worker/workflows/BatchCompilationWorkflow.ts Outdated
Comment thread worker/rate-limiter-do.ts Outdated
Comment thread worker/compilation-coordinator.ts Outdated
@jaypatrick
Copy link
Copy Markdown
Owner

@copilot Fix CI

…rrors

The Sentry SDK requires DO classes to satisfy `new(state, env: Env) =>
DurableObject<Env, {}>` (the runtime cloudflare:workers branded class),
but our `implements DurableObject` classes lack `ctx`, `env`, and
`[Rpc.__DURABLE_OBJECT_BRAND]`.  Use a double cast:
- Inner: `XyzBase as unknown as new(state, env: Env) => DurableObject<Env, {}>` satisfies Sentry's parameter constraint
- Outer: `as unknown as typeof XyzBase` restores original class type so tests see
  non-optional `fetch`/`alarm`/WebSocket methods and `_env: unknown` constructor
  (allowing `{}` in test instantiation)

Fixes 3 TypeCheck errors + 123 Test type errors in CI.

Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/50e6d538-3e45-4ef9-b5f5-f466c5308a64

Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot April 25, 2026 01:23
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 25, 2026

@copilot apply changes based on the comments in this thread

Applied in 37d1ebf. All 7 files (3 DOs + 4 Workflows) now use a lazy getSentryModule(env) helper (mirrors the pattern in sentry-init.ts) instead of a static import * as Sentry. Key changes:

  • Static import * as Sentry from '@sentry/cloudflare' removed from all 7 files
  • Per-file getSentryModule(env) helper added — dynamically imports the SDK only when SENTRY_DSN is present, falls back to null
  • *Base class renames reverted → back to export class Foo (resolves the TS2749: refers to a value not a type CI error that export const caused)
  • instrument*WithSentry wrapper exports removed (no more DurableObject<Env, {}> cast, fixing the ban-types lint error and TS2315 type errors)
  • captureException added in each key error path using the lazy helper

Copilot AI changed the title feat: instrument Durable Objects and Workflows with Sentry feat: instrument Durable Objects and Workflows with Sentry (lazy import) Apr 25, 2026
@jaypatrick jaypatrick marked this pull request as ready for review April 25, 2026 03:14
Copilot AI review requested due to automatic review settings April 25, 2026 03:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 10 comments.

Comment thread worker/ws-hibernation-do.ts
Comment thread worker/rate-limiter-do.ts Outdated
Comment thread worker/rate-limiter-do.ts
Comment thread worker/compilation-coordinator.ts
Comment thread worker/workflows/BatchCompilationWorkflow.ts
Comment thread worker/workflows/CacheWarmingWorkflow.ts
Comment thread worker/workflows/HealthMonitoringWorkflow.ts
Comment thread worker/compilation-coordinator.ts Outdated
Comment thread worker/ws-hibernation-do.ts
Comment thread worker/workflows/CompilationWorkflow.ts
@jaypatrick
Copy link
Copy Markdown
Owner

@copilot apply changes based on the comments in this thread

Copilot AI and others added 2 commits April 25, 2026 04:09
…th Sentry.init() per-isolate and waitUntil in webSocketError

Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/44660623-833c-40c1-a91d-4314b4a2420e

Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
…tting per code review

Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/44660623-833c-40c1-a91d-4314b4a2420e

Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot April 25, 2026 04:10
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 25, 2026

@copilot apply changes based on the comments in this thread

Applied in 43a4d45. Changes from the review thread:

  1. Shared helper — extracted to worker/services/sentry-isolate-init.ts (captureExceptionInIsolate) so the lazy-load logic lives in one place instead of 7 copies.
  2. Sentry.init() per isolate — the helper calls Sentry.init() once per isolate (DSN/release/environment matching withSentryWorker) so captureException() actually sends events from DO and Workflow isolates.
  3. Retry-on-failure caching — import failure clears the promise cache so the next invocation retries, rather than permanently returning null.
  4. waitUntil in webSocketError — Sentry capture is now fire-and-forget via this.state.waitUntil(...) so WebSocket teardown isn't blocked on the async module load.

Copilot AI changed the title feat: instrument Durable Objects and Workflows with Sentry (lazy import) feat: instrument Durable Objects and Workflows with Sentry (lazy import, isolate-local init) Apr 25, 2026
@jaypatrick
Copy link
Copy Markdown
Owner

@copilot Fix CI

…sentry-isolate-init.ts; fix double blank line in ws-hibernation-do.ts

Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/4c73bb50-efb0-4f19-91d8-8de5a64879d8

Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot April 25, 2026 06:45
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 25, 2026

@copilot Fix CI

Fixed in 97e07dc.

Two issues resolved:

  1. TS2339 Property 'init' does not exist@sentry/cloudflare exports init() only from its internal sdk.js sub-module, not from the main package index. Replaced Sentry.init({...}) with the equivalent using the exported APIs: new Sentry.CloudflareClient({...}) + Sentry.setCurrentClient(client) + client.init() (public method inherited from BaseClient). Also replaced the sentryInitialized flag with Sentry.isInitialized() (also exported). The custom transport replicates makeCloudflareTransport using the exported Sentry.createTransport.

  2. Format error — a stray double blank line at line 60 of ws-hibernation-do.ts (between the import block and the first comment section) caused deno fmt --check to fail.

@jaypatrick jaypatrick merged commit 9c97b6b into main Apr 25, 2026
29 checks passed
@jaypatrick jaypatrick deleted the copilot/integrate-sentry-sdk-durable-objects-workflows branch April 25, 2026 06:56
Copilot AI added a commit that referenced this pull request Apr 25, 2026
jaypatrick added a commit that referenced this pull request Apr 25, 2026
* Initial plan

* feat: add docs for PRs #1663/#1664 + automated schema sync pipeline

Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/5ca913d3-e1be-4411-8221-2f610ae6ddaf

Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>

* refactor: simplify upload step control flow in sync-api-assets

Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/5ca913d3-e1be-4411-8221-2f610ae6ddaf

Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>

* fix: regenerate canonical schema/postman artifacts, fix sync-api-assets string literals

Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/eed80bfb-95aa-42ab-ab14-449dca99d3b4

Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>

* fix: restore postman-collection.json to canonical pre-PR state (re-adds SSE stream test)

Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/cd693534-0d79-46b1-ac7b-6256200d301d

Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>

* fix: apply all PR review feedback from review thread #4175868887

Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/7bd8b89b-8b07-43b3-9224-bf37ca088a84

Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>

* docs: clarify destination_address is optional and add units comment to max_batch_timeout

Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/7bd8b89b-8b07-43b3-9224-bf37ca088a84

Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Jayson Knight <jayson.knight@jaysonknight.com>
Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cloudflare Cloudflare Workers / CDN deployment Deployment tasks enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants