feat(cloudflare): Instrument Agents automatically - #22727

Merged
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically
Jul 28, 2026
Merged

feat(cloudflare): Instrument Agents automatically#22727
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically

Conversation

@JPeer264

Copy link
Copy Markdown
Member

Since instrumentAgentWithSentry is here, it also needs to be supported within the Vite plugin and its autoInstrument flag. The issue here is that we need to parse through the imports in order to understand where the Agent import is coming from. (The majority of the changes are tests again)

It could also be that external libraries are extending from Agent, such as McpAgent, and these need to be wrapped too. It is super hacky but it works.

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

@JPeer264JPeer264 self-assigned this Jul 28, 2026
@JPeer264JPeer264 changed the title feat(cloudflare): Add instrumentAgentWithSentry for Cloudflare Agentsfeat(cloudflare): Instrument Agents automaticallyJul 28, 2026
@github-actions

github-actionsBot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser29.85 kB+0.04%+9 B 🔺
@sentry/browser - with treeshaking flags28.05 kB+0.03%+8 B 🔺
@sentry/browser (incl. Tracing)47.42 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing + Span Streaming)47.43 kB+0.03%+10 B 🔺
@sentry/browser (incl. Tracing, Profiling)52.16 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing, Replay)86.74 kB+0.01%+6 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags76.16 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)91.48 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)104.11 kB+0.01%+9 B 🔺
@sentry/browser (incl. Feedback)47.16 kB+0.02%+8 B 🔺
@sentry/browser (incl. sendFeedback)34.69 kB+0.03%+9 B 🔺
@sentry/browser (incl. FeedbackAsync)39.78 kB+0.03%+8 B 🔺
@sentry/browser (incl. Metrics)30.92 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)31.14 kB+0.02%+6 B 🔺
@sentry/browser (incl. Metrics & Logs)31.83 kB+0.02%+6 B 🔺
@sentry/react31.64 kB+0.03%+7 B 🔺
@sentry/react (incl. Tracing)49.64 kB+0.02%+5 B 🔺
@sentry/vue34.77 kB+0.03%+8 B 🔺
@sentry/vue (incl. Tracing)49.37 kB+0.02%+9 B 🔺
@sentry/svelte29.88 kB+0.04%+9 B 🔺
CDN Bundle31.89 kB+0.02%+5 B 🔺
CDN Bundle (incl. Tracing)47.76 kB+0.01%+2 B 🔺
CDN Bundle (incl. Logs, Metrics)33.44 kB+0.02%+4 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)49.14 kB--
CDN Bundle (incl. Replay, Logs, Metrics)72.8 kB+0.01%+7 B 🔺
CDN Bundle (incl. Tracing, Replay)85.4 kB+0.01%+6 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.68 kB+0.01%+4 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)91.17 kB+0.01%+8 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.47 kB+0.01%+8 B 🔺
CDN Bundle - uncompressed95.12 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing) - uncompressed143.26 kB+0.03%+41 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed99.83 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed147.25 kB+0.03%+41 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed224.6 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed262.52 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed266.49 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed276.23 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed280.18 kB+0.02%+41 B 🔺
@sentry/nextjs (client)52.23 kB+0.01%+5 B 🔺
@sentry/sveltekit (client)47.84 kB+0.03%+10 B 🔺
@sentry/core/server79.73 kB-0.06%-47 B 🔽
@sentry/core/browser51.61 kB+0.02%+7 B 🔺
@sentry/node122.35 kB+1.07%+1.29 kB 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection)166 B--
@sentry/node - without tracing85.75 kB+1.51%+1.27 kB 🔺
@sentry/aws-serverless93.78 kB+1.16%+1.07 kB 🔺
@sentry/cloudflare (withSentry) - minified197.53 kB+0.01%+11 B 🔺
@sentry/cloudflare (withSentry)485.64 kB-0.03%-124 B 🔽

View base workflow run

@JPeer264
JPeer264 marked this pull request as ready for review July 28, 2026 11:05
@JPeer264
JPeer264 requested a review from a team as a code ownerJuly 28, 2026 11:05
@JPeer264
JPeer264 requested review from andreiborza, isaacs, mydea and timfish and removed request for a teamJuly 28, 2026 11:05
@andreiborza

Copy link
Copy Markdown
Member

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

@JPeer264

Copy link
Copy Markdown
MemberAuthor

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

Because I need to backport that PR. We can change that anytime now ofc

@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from cc88548 to 62f5ca2CompareJuly 28, 2026 13:36
Comment threadpackages/cloudflare/test/vite/autoInstrument.test.ts
@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from 62f5ca2 to 8d6a511CompareJuly 28, 2026 14:05
Comment on lines +94 to +95
// its base-class chain tells the two apart. Detection walks the module graph (base classes
// usually live in their own file), so it is limited to the configured DO classes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

q: Does that constraint really hold true? I wouldn't be surprised if someone has several classes in one file. Or am I misunderstanding?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

By definition all the classes are in one file, it is just the question if they are defined in the main entrypoint or in other files.

const code = stripComments(rawCode);
const shape = emptyShape();

// `class X extends Y {` / `class X<T> extends Y<T> {`, optionally exported and/or abstract.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about cases where generics extend, e.g. class X<T extends S> extends Y<T extends Z>?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

True. I'm sure there are more edge cases which we didn't think of just yet.
Added in facc163

Comment on lines +213 to +214
const CLASS_RE = /\b(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)([^{]*)\{/g;
for (const match of code.matchAll(CLASS_RE)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The CLASS_RE regex in shapeFromSource() fails to detect anonymous classes like export default class extends Agent {}, causing agent detection to fail for those modules.
Severity: MEDIUM

Suggested Fix

Modify the CLASS_RE regex to make the class name capture group optional. This will allow it to correctly match and parse anonymous default exported classes.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: packages/cloudflare/src/vite/moduleShape.ts#L213-L214
Potential issue: The regex `CLASS_RE` used in `shapeFromSource()` to parse module shapes
requires a class name to be present after the `class` keyword. This means it will not
match an anonymous default export, such as `export default class extends Agent {}`. When
this pattern is used in a module, `defaultExportIsClass` is not set on the module's
shape. Consequently, when `isAgentBinding()` later checks the module, it incorrectly
determines that the default export is not an agent, leading to a failure in agent
detection. The code seems to anticipate this pattern, as `shapeFromAst()` handles it,
but the regex-based fallback does not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh this seems relevant too, speaking of edge-cases @JPeer264

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

You can't export default a DurableObject.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

At least what I'm aware of*

@JPeer264
JPeer264 merged commit 550b2d8 into developJul 28, 2026
81 checks passed
@JPeer264
JPeer264 deleted the jp/cloudflare-instrument-agent-automatically branch July 28, 2026 17:15
JPeer264 added a commit that referenced this pull request Jul 28, 2026
JPeer264 added a commit that referenced this pull request Jul 29, 2026
Sign up for freeto 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

@JPeer264@andreiborza
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(cloudflare): Instrument Agents automatically - #22727

Merged
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically
Jul 28, 2026
Merged

feat(cloudflare): Instrument Agents automatically#22727
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically

Conversation

@JPeer264

Copy link
Copy Markdown
Member

Since instrumentAgentWithSentry is here, it also needs to be supported within the Vite plugin and its autoInstrument flag. The issue here is that we need to parse through the imports in order to understand where the Agent import is coming from. (The majority of the changes are tests again)

It could also be that external libraries are extending from Agent, such as McpAgent, and these need to be wrapped too. It is super hacky but it works.

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

@JPeer264JPeer264 self-assigned this Jul 28, 2026
@JPeer264JPeer264 changed the title feat(cloudflare): Add instrumentAgentWithSentry for Cloudflare Agentsfeat(cloudflare): Instrument Agents automaticallyJul 28, 2026
@github-actions

github-actionsBot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser29.85 kB+0.04%+9 B 🔺
@sentry/browser - with treeshaking flags28.05 kB+0.03%+8 B 🔺
@sentry/browser (incl. Tracing)47.42 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing + Span Streaming)47.43 kB+0.03%+10 B 🔺
@sentry/browser (incl. Tracing, Profiling)52.16 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing, Replay)86.74 kB+0.01%+6 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags76.16 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)91.48 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)104.11 kB+0.01%+9 B 🔺
@sentry/browser (incl. Feedback)47.16 kB+0.02%+8 B 🔺
@sentry/browser (incl. sendFeedback)34.69 kB+0.03%+9 B 🔺
@sentry/browser (incl. FeedbackAsync)39.78 kB+0.03%+8 B 🔺
@sentry/browser (incl. Metrics)30.92 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)31.14 kB+0.02%+6 B 🔺
@sentry/browser (incl. Metrics & Logs)31.83 kB+0.02%+6 B 🔺
@sentry/react31.64 kB+0.03%+7 B 🔺
@sentry/react (incl. Tracing)49.64 kB+0.02%+5 B 🔺
@sentry/vue34.77 kB+0.03%+8 B 🔺
@sentry/vue (incl. Tracing)49.37 kB+0.02%+9 B 🔺
@sentry/svelte29.88 kB+0.04%+9 B 🔺
CDN Bundle31.89 kB+0.02%+5 B 🔺
CDN Bundle (incl. Tracing)47.76 kB+0.01%+2 B 🔺
CDN Bundle (incl. Logs, Metrics)33.44 kB+0.02%+4 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)49.14 kB--
CDN Bundle (incl. Replay, Logs, Metrics)72.8 kB+0.01%+7 B 🔺
CDN Bundle (incl. Tracing, Replay)85.4 kB+0.01%+6 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.68 kB+0.01%+4 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)91.17 kB+0.01%+8 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.47 kB+0.01%+8 B 🔺
CDN Bundle - uncompressed95.12 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing) - uncompressed143.26 kB+0.03%+41 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed99.83 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed147.25 kB+0.03%+41 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed224.6 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed262.52 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed266.49 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed276.23 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed280.18 kB+0.02%+41 B 🔺
@sentry/nextjs (client)52.23 kB+0.01%+5 B 🔺
@sentry/sveltekit (client)47.84 kB+0.03%+10 B 🔺
@sentry/core/server79.73 kB-0.06%-47 B 🔽
@sentry/core/browser51.61 kB+0.02%+7 B 🔺
@sentry/node122.35 kB+1.07%+1.29 kB 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection)166 B--
@sentry/node - without tracing85.75 kB+1.51%+1.27 kB 🔺
@sentry/aws-serverless93.78 kB+1.16%+1.07 kB 🔺
@sentry/cloudflare (withSentry) - minified197.53 kB+0.01%+11 B 🔺
@sentry/cloudflare (withSentry)485.64 kB-0.03%-124 B 🔽

View base workflow run

@JPeer264
JPeer264 marked this pull request as ready for review July 28, 2026 11:05
@JPeer264
JPeer264 requested a review from a team as a code ownerJuly 28, 2026 11:05
@JPeer264
JPeer264 requested review from andreiborza, isaacs, mydea and timfish and removed request for a teamJuly 28, 2026 11:05
@andreiborza

Copy link
Copy Markdown
Member

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

@JPeer264

Copy link
Copy Markdown
MemberAuthor

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

Because I need to backport that PR. We can change that anytime now ofc

@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from cc88548 to 62f5ca2CompareJuly 28, 2026 13:36
Comment threadpackages/cloudflare/test/vite/autoInstrument.test.ts
@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from 62f5ca2 to 8d6a511CompareJuly 28, 2026 14:05
Comment on lines +94 to +95
// its base-class chain tells the two apart. Detection walks the module graph (base classes
// usually live in their own file), so it is limited to the configured DO classes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

q: Does that constraint really hold true? I wouldn't be surprised if someone has several classes in one file. Or am I misunderstanding?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

By definition all the classes are in one file, it is just the question if they are defined in the main entrypoint or in other files.

const code = stripComments(rawCode);
const shape = emptyShape();

// `class X extends Y {` / `class X<T> extends Y<T> {`, optionally exported and/or abstract.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about cases where generics extend, e.g. class X<T extends S> extends Y<T extends Z>?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

True. I'm sure there are more edge cases which we didn't think of just yet.
Added in facc163

Comment on lines +213 to +214
const CLASS_RE = /\b(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)([^{]*)\{/g;
for (const match of code.matchAll(CLASS_RE)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The CLASS_RE regex in shapeFromSource() fails to detect anonymous classes like export default class extends Agent {}, causing agent detection to fail for those modules.
Severity: MEDIUM

Suggested Fix

Modify the CLASS_RE regex to make the class name capture group optional. This will allow it to correctly match and parse anonymous default exported classes.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: packages/cloudflare/src/vite/moduleShape.ts#L213-L214
Potential issue: The regex `CLASS_RE` used in `shapeFromSource()` to parse module shapes
requires a class name to be present after the `class` keyword. This means it will not
match an anonymous default export, such as `export default class extends Agent {}`. When
this pattern is used in a module, `defaultExportIsClass` is not set on the module's
shape. Consequently, when `isAgentBinding()` later checks the module, it incorrectly
determines that the default export is not an agent, leading to a failure in agent
detection. The code seems to anticipate this pattern, as `shapeFromAst()` handles it,
but the regex-based fallback does not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh this seems relevant too, speaking of edge-cases @JPeer264

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

You can't export default a DurableObject.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

At least what I'm aware of*

@JPeer264
JPeer264 merged commit 550b2d8 into developJul 28, 2026
81 checks passed
@JPeer264
JPeer264 deleted the jp/cloudflare-instrument-agent-automatically branch July 28, 2026 17:15
JPeer264 added a commit that referenced this pull request Jul 28, 2026
JPeer264 added a commit that referenced this pull request Jul 29, 2026
Sign up for freeto 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

@JPeer264@andreiborza
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(cloudflare): Instrument Agents automatically - #22727

Merged
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically
Jul 28, 2026
Merged

feat(cloudflare): Instrument Agents automatically#22727
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically

Conversation

@JPeer264

Copy link
Copy Markdown
Member

Since instrumentAgentWithSentry is here, it also needs to be supported within the Vite plugin and its autoInstrument flag. The issue here is that we need to parse through the imports in order to understand where the Agent import is coming from. (The majority of the changes are tests again)

It could also be that external libraries are extending from Agent, such as McpAgent, and these need to be wrapped too. It is super hacky but it works.

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

@JPeer264JPeer264 self-assigned this Jul 28, 2026
@JPeer264JPeer264 changed the title feat(cloudflare): Add instrumentAgentWithSentry for Cloudflare Agentsfeat(cloudflare): Instrument Agents automaticallyJul 28, 2026
@github-actions

github-actionsBot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser29.85 kB+0.04%+9 B 🔺
@sentry/browser - with treeshaking flags28.05 kB+0.03%+8 B 🔺
@sentry/browser (incl. Tracing)47.42 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing + Span Streaming)47.43 kB+0.03%+10 B 🔺
@sentry/browser (incl. Tracing, Profiling)52.16 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing, Replay)86.74 kB+0.01%+6 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags76.16 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)91.48 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)104.11 kB+0.01%+9 B 🔺
@sentry/browser (incl. Feedback)47.16 kB+0.02%+8 B 🔺
@sentry/browser (incl. sendFeedback)34.69 kB+0.03%+9 B 🔺
@sentry/browser (incl. FeedbackAsync)39.78 kB+0.03%+8 B 🔺
@sentry/browser (incl. Metrics)30.92 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)31.14 kB+0.02%+6 B 🔺
@sentry/browser (incl. Metrics & Logs)31.83 kB+0.02%+6 B 🔺
@sentry/react31.64 kB+0.03%+7 B 🔺
@sentry/react (incl. Tracing)49.64 kB+0.02%+5 B 🔺
@sentry/vue34.77 kB+0.03%+8 B 🔺
@sentry/vue (incl. Tracing)49.37 kB+0.02%+9 B 🔺
@sentry/svelte29.88 kB+0.04%+9 B 🔺
CDN Bundle31.89 kB+0.02%+5 B 🔺
CDN Bundle (incl. Tracing)47.76 kB+0.01%+2 B 🔺
CDN Bundle (incl. Logs, Metrics)33.44 kB+0.02%+4 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)49.14 kB--
CDN Bundle (incl. Replay, Logs, Metrics)72.8 kB+0.01%+7 B 🔺
CDN Bundle (incl. Tracing, Replay)85.4 kB+0.01%+6 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.68 kB+0.01%+4 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)91.17 kB+0.01%+8 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.47 kB+0.01%+8 B 🔺
CDN Bundle - uncompressed95.12 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing) - uncompressed143.26 kB+0.03%+41 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed99.83 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed147.25 kB+0.03%+41 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed224.6 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed262.52 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed266.49 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed276.23 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed280.18 kB+0.02%+41 B 🔺
@sentry/nextjs (client)52.23 kB+0.01%+5 B 🔺
@sentry/sveltekit (client)47.84 kB+0.03%+10 B 🔺
@sentry/core/server79.73 kB-0.06%-47 B 🔽
@sentry/core/browser51.61 kB+0.02%+7 B 🔺
@sentry/node122.35 kB+1.07%+1.29 kB 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection)166 B--
@sentry/node - without tracing85.75 kB+1.51%+1.27 kB 🔺
@sentry/aws-serverless93.78 kB+1.16%+1.07 kB 🔺
@sentry/cloudflare (withSentry) - minified197.53 kB+0.01%+11 B 🔺
@sentry/cloudflare (withSentry)485.64 kB-0.03%-124 B 🔽

View base workflow run

@JPeer264
JPeer264 marked this pull request as ready for review July 28, 2026 11:05
@JPeer264
JPeer264 requested a review from a team as a code ownerJuly 28, 2026 11:05
@JPeer264
JPeer264 requested review from andreiborza, isaacs, mydea and timfish and removed request for a teamJuly 28, 2026 11:05
@andreiborza

Copy link
Copy Markdown
Member

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

@JPeer264

Copy link
Copy Markdown
MemberAuthor

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

Because I need to backport that PR. We can change that anytime now ofc

@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from cc88548 to 62f5ca2CompareJuly 28, 2026 13:36
Comment threadpackages/cloudflare/test/vite/autoInstrument.test.ts
@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from 62f5ca2 to 8d6a511CompareJuly 28, 2026 14:05
Comment on lines +94 to +95
// its base-class chain tells the two apart. Detection walks the module graph (base classes
// usually live in their own file), so it is limited to the configured DO classes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

q: Does that constraint really hold true? I wouldn't be surprised if someone has several classes in one file. Or am I misunderstanding?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

By definition all the classes are in one file, it is just the question if they are defined in the main entrypoint or in other files.

const code = stripComments(rawCode);
const shape = emptyShape();

// `class X extends Y {` / `class X<T> extends Y<T> {`, optionally exported and/or abstract.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about cases where generics extend, e.g. class X<T extends S> extends Y<T extends Z>?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

True. I'm sure there are more edge cases which we didn't think of just yet.
Added in facc163

Comment on lines +213 to +214
const CLASS_RE = /\b(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)([^{]*)\{/g;
for (const match of code.matchAll(CLASS_RE)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The CLASS_RE regex in shapeFromSource() fails to detect anonymous classes like export default class extends Agent {}, causing agent detection to fail for those modules.
Severity: MEDIUM

Suggested Fix

Modify the CLASS_RE regex to make the class name capture group optional. This will allow it to correctly match and parse anonymous default exported classes.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: packages/cloudflare/src/vite/moduleShape.ts#L213-L214
Potential issue: The regex `CLASS_RE` used in `shapeFromSource()` to parse module shapes
requires a class name to be present after the `class` keyword. This means it will not
match an anonymous default export, such as `export default class extends Agent {}`. When
this pattern is used in a module, `defaultExportIsClass` is not set on the module's
shape. Consequently, when `isAgentBinding()` later checks the module, it incorrectly
determines that the default export is not an agent, leading to a failure in agent
detection. The code seems to anticipate this pattern, as `shapeFromAst()` handles it,
but the regex-based fallback does not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh this seems relevant too, speaking of edge-cases @JPeer264

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

You can't export default a DurableObject.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

At least what I'm aware of*

@JPeer264
JPeer264 merged commit 550b2d8 into developJul 28, 2026
81 checks passed
@JPeer264
JPeer264 deleted the jp/cloudflare-instrument-agent-automatically branch July 28, 2026 17:15
JPeer264 added a commit that referenced this pull request Jul 28, 2026
JPeer264 added a commit that referenced this pull request Jul 29, 2026
Sign up for freeto 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

@JPeer264@andreiborza
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(cloudflare): Instrument Agents automatically - #22727

Merged
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically
Jul 28, 2026
Merged

feat(cloudflare): Instrument Agents automatically#22727
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically

Conversation

@JPeer264

Copy link
Copy Markdown
Member

Since instrumentAgentWithSentry is here, it also needs to be supported within the Vite plugin and its autoInstrument flag. The issue here is that we need to parse through the imports in order to understand where the Agent import is coming from. (The majority of the changes are tests again)

It could also be that external libraries are extending from Agent, such as McpAgent, and these need to be wrapped too. It is super hacky but it works.

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

@JPeer264JPeer264 self-assigned this Jul 28, 2026
@JPeer264JPeer264 changed the title feat(cloudflare): Add instrumentAgentWithSentry for Cloudflare Agentsfeat(cloudflare): Instrument Agents automaticallyJul 28, 2026
@github-actions

github-actionsBot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser29.85 kB+0.04%+9 B 🔺
@sentry/browser - with treeshaking flags28.05 kB+0.03%+8 B 🔺
@sentry/browser (incl. Tracing)47.42 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing + Span Streaming)47.43 kB+0.03%+10 B 🔺
@sentry/browser (incl. Tracing, Profiling)52.16 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing, Replay)86.74 kB+0.01%+6 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags76.16 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)91.48 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)104.11 kB+0.01%+9 B 🔺
@sentry/browser (incl. Feedback)47.16 kB+0.02%+8 B 🔺
@sentry/browser (incl. sendFeedback)34.69 kB+0.03%+9 B 🔺
@sentry/browser (incl. FeedbackAsync)39.78 kB+0.03%+8 B 🔺
@sentry/browser (incl. Metrics)30.92 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)31.14 kB+0.02%+6 B 🔺
@sentry/browser (incl. Metrics & Logs)31.83 kB+0.02%+6 B 🔺
@sentry/react31.64 kB+0.03%+7 B 🔺
@sentry/react (incl. Tracing)49.64 kB+0.02%+5 B 🔺
@sentry/vue34.77 kB+0.03%+8 B 🔺
@sentry/vue (incl. Tracing)49.37 kB+0.02%+9 B 🔺
@sentry/svelte29.88 kB+0.04%+9 B 🔺
CDN Bundle31.89 kB+0.02%+5 B 🔺
CDN Bundle (incl. Tracing)47.76 kB+0.01%+2 B 🔺
CDN Bundle (incl. Logs, Metrics)33.44 kB+0.02%+4 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)49.14 kB--
CDN Bundle (incl. Replay, Logs, Metrics)72.8 kB+0.01%+7 B 🔺
CDN Bundle (incl. Tracing, Replay)85.4 kB+0.01%+6 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.68 kB+0.01%+4 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)91.17 kB+0.01%+8 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.47 kB+0.01%+8 B 🔺
CDN Bundle - uncompressed95.12 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing) - uncompressed143.26 kB+0.03%+41 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed99.83 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed147.25 kB+0.03%+41 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed224.6 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed262.52 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed266.49 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed276.23 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed280.18 kB+0.02%+41 B 🔺
@sentry/nextjs (client)52.23 kB+0.01%+5 B 🔺
@sentry/sveltekit (client)47.84 kB+0.03%+10 B 🔺
@sentry/core/server79.73 kB-0.06%-47 B 🔽
@sentry/core/browser51.61 kB+0.02%+7 B 🔺
@sentry/node122.35 kB+1.07%+1.29 kB 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection)166 B--
@sentry/node - without tracing85.75 kB+1.51%+1.27 kB 🔺
@sentry/aws-serverless93.78 kB+1.16%+1.07 kB 🔺
@sentry/cloudflare (withSentry) - minified197.53 kB+0.01%+11 B 🔺
@sentry/cloudflare (withSentry)485.64 kB-0.03%-124 B 🔽

View base workflow run

@JPeer264
JPeer264 marked this pull request as ready for review July 28, 2026 11:05
@JPeer264
JPeer264 requested a review from a team as a code ownerJuly 28, 2026 11:05
@JPeer264
JPeer264 requested review from andreiborza, isaacs, mydea and timfish and removed request for a teamJuly 28, 2026 11:05
@andreiborza

Copy link
Copy Markdown
Member

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

@JPeer264

Copy link
Copy Markdown
MemberAuthor

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

Because I need to backport that PR. We can change that anytime now ofc

@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from cc88548 to 62f5ca2CompareJuly 28, 2026 13:36
Comment threadpackages/cloudflare/test/vite/autoInstrument.test.ts
@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from 62f5ca2 to 8d6a511CompareJuly 28, 2026 14:05
Comment on lines +94 to +95
// its base-class chain tells the two apart. Detection walks the module graph (base classes
// usually live in their own file), so it is limited to the configured DO classes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

q: Does that constraint really hold true? I wouldn't be surprised if someone has several classes in one file. Or am I misunderstanding?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

By definition all the classes are in one file, it is just the question if they are defined in the main entrypoint or in other files.

const code = stripComments(rawCode);
const shape = emptyShape();

// `class X extends Y {` / `class X<T> extends Y<T> {`, optionally exported and/or abstract.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about cases where generics extend, e.g. class X<T extends S> extends Y<T extends Z>?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

True. I'm sure there are more edge cases which we didn't think of just yet.
Added in facc163

Comment on lines +213 to +214
const CLASS_RE = /\b(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)([^{]*)\{/g;
for (const match of code.matchAll(CLASS_RE)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The CLASS_RE regex in shapeFromSource() fails to detect anonymous classes like export default class extends Agent {}, causing agent detection to fail for those modules.
Severity: MEDIUM

Suggested Fix

Modify the CLASS_RE regex to make the class name capture group optional. This will allow it to correctly match and parse anonymous default exported classes.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: packages/cloudflare/src/vite/moduleShape.ts#L213-L214
Potential issue: The regex `CLASS_RE` used in `shapeFromSource()` to parse module shapes
requires a class name to be present after the `class` keyword. This means it will not
match an anonymous default export, such as `export default class extends Agent {}`. When
this pattern is used in a module, `defaultExportIsClass` is not set on the module's
shape. Consequently, when `isAgentBinding()` later checks the module, it incorrectly
determines that the default export is not an agent, leading to a failure in agent
detection. The code seems to anticipate this pattern, as `shapeFromAst()` handles it,
but the regex-based fallback does not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh this seems relevant too, speaking of edge-cases @JPeer264

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

You can't export default a DurableObject.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

At least what I'm aware of*

@JPeer264
JPeer264 merged commit 550b2d8 into developJul 28, 2026
81 checks passed
@JPeer264
JPeer264 deleted the jp/cloudflare-instrument-agent-automatically branch July 28, 2026 17:15
JPeer264 added a commit that referenced this pull request Jul 28, 2026
JPeer264 added a commit that referenced this pull request Jul 29, 2026
Sign up for freeto 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

@JPeer264@andreiborza
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(cloudflare): Instrument Agents automatically - #22727

Merged
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically
Jul 28, 2026
Merged

feat(cloudflare): Instrument Agents automatically#22727
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically

Conversation

@JPeer264

Copy link
Copy Markdown
Member

Since instrumentAgentWithSentry is here, it also needs to be supported within the Vite plugin and its autoInstrument flag. The issue here is that we need to parse through the imports in order to understand where the Agent import is coming from. (The majority of the changes are tests again)

It could also be that external libraries are extending from Agent, such as McpAgent, and these need to be wrapped too. It is super hacky but it works.

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

@JPeer264JPeer264 self-assigned this Jul 28, 2026
@JPeer264JPeer264 changed the title feat(cloudflare): Add instrumentAgentWithSentry for Cloudflare Agentsfeat(cloudflare): Instrument Agents automaticallyJul 28, 2026
@github-actions

github-actionsBot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser29.85 kB+0.04%+9 B 🔺
@sentry/browser - with treeshaking flags28.05 kB+0.03%+8 B 🔺
@sentry/browser (incl. Tracing)47.42 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing + Span Streaming)47.43 kB+0.03%+10 B 🔺
@sentry/browser (incl. Tracing, Profiling)52.16 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing, Replay)86.74 kB+0.01%+6 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags76.16 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)91.48 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)104.11 kB+0.01%+9 B 🔺
@sentry/browser (incl. Feedback)47.16 kB+0.02%+8 B 🔺
@sentry/browser (incl. sendFeedback)34.69 kB+0.03%+9 B 🔺
@sentry/browser (incl. FeedbackAsync)39.78 kB+0.03%+8 B 🔺
@sentry/browser (incl. Metrics)30.92 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)31.14 kB+0.02%+6 B 🔺
@sentry/browser (incl. Metrics & Logs)31.83 kB+0.02%+6 B 🔺
@sentry/react31.64 kB+0.03%+7 B 🔺
@sentry/react (incl. Tracing)49.64 kB+0.02%+5 B 🔺
@sentry/vue34.77 kB+0.03%+8 B 🔺
@sentry/vue (incl. Tracing)49.37 kB+0.02%+9 B 🔺
@sentry/svelte29.88 kB+0.04%+9 B 🔺
CDN Bundle31.89 kB+0.02%+5 B 🔺
CDN Bundle (incl. Tracing)47.76 kB+0.01%+2 B 🔺
CDN Bundle (incl. Logs, Metrics)33.44 kB+0.02%+4 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)49.14 kB--
CDN Bundle (incl. Replay, Logs, Metrics)72.8 kB+0.01%+7 B 🔺
CDN Bundle (incl. Tracing, Replay)85.4 kB+0.01%+6 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.68 kB+0.01%+4 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)91.17 kB+0.01%+8 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.47 kB+0.01%+8 B 🔺
CDN Bundle - uncompressed95.12 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing) - uncompressed143.26 kB+0.03%+41 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed99.83 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed147.25 kB+0.03%+41 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed224.6 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed262.52 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed266.49 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed276.23 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed280.18 kB+0.02%+41 B 🔺
@sentry/nextjs (client)52.23 kB+0.01%+5 B 🔺
@sentry/sveltekit (client)47.84 kB+0.03%+10 B 🔺
@sentry/core/server79.73 kB-0.06%-47 B 🔽
@sentry/core/browser51.61 kB+0.02%+7 B 🔺
@sentry/node122.35 kB+1.07%+1.29 kB 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection)166 B--
@sentry/node - without tracing85.75 kB+1.51%+1.27 kB 🔺
@sentry/aws-serverless93.78 kB+1.16%+1.07 kB 🔺
@sentry/cloudflare (withSentry) - minified197.53 kB+0.01%+11 B 🔺
@sentry/cloudflare (withSentry)485.64 kB-0.03%-124 B 🔽

View base workflow run

@JPeer264
JPeer264 marked this pull request as ready for review July 28, 2026 11:05
@JPeer264
JPeer264 requested a review from a team as a code ownerJuly 28, 2026 11:05
@JPeer264
JPeer264 requested review from andreiborza, isaacs, mydea and timfish and removed request for a teamJuly 28, 2026 11:05
@andreiborza

Copy link
Copy Markdown
Member

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

@JPeer264

Copy link
Copy Markdown
MemberAuthor

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

Because I need to backport that PR. We can change that anytime now ofc

@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from cc88548 to 62f5ca2CompareJuly 28, 2026 13:36
Comment threadpackages/cloudflare/test/vite/autoInstrument.test.ts
@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from 62f5ca2 to 8d6a511CompareJuly 28, 2026 14:05
Comment on lines +94 to +95
// its base-class chain tells the two apart. Detection walks the module graph (base classes
// usually live in their own file), so it is limited to the configured DO classes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

q: Does that constraint really hold true? I wouldn't be surprised if someone has several classes in one file. Or am I misunderstanding?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

By definition all the classes are in one file, it is just the question if they are defined in the main entrypoint or in other files.

const code = stripComments(rawCode);
const shape = emptyShape();

// `class X extends Y {` / `class X<T> extends Y<T> {`, optionally exported and/or abstract.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about cases where generics extend, e.g. class X<T extends S> extends Y<T extends Z>?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

True. I'm sure there are more edge cases which we didn't think of just yet.
Added in facc163

Comment on lines +213 to +214
const CLASS_RE = /\b(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)([^{]*)\{/g;
for (const match of code.matchAll(CLASS_RE)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The CLASS_RE regex in shapeFromSource() fails to detect anonymous classes like export default class extends Agent {}, causing agent detection to fail for those modules.
Severity: MEDIUM

Suggested Fix

Modify the CLASS_RE regex to make the class name capture group optional. This will allow it to correctly match and parse anonymous default exported classes.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: packages/cloudflare/src/vite/moduleShape.ts#L213-L214
Potential issue: The regex `CLASS_RE` used in `shapeFromSource()` to parse module shapes
requires a class name to be present after the `class` keyword. This means it will not
match an anonymous default export, such as `export default class extends Agent {}`. When
this pattern is used in a module, `defaultExportIsClass` is not set on the module's
shape. Consequently, when `isAgentBinding()` later checks the module, it incorrectly
determines that the default export is not an agent, leading to a failure in agent
detection. The code seems to anticipate this pattern, as `shapeFromAst()` handles it,
but the regex-based fallback does not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh this seems relevant too, speaking of edge-cases @JPeer264

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

You can't export default a DurableObject.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

At least what I'm aware of*

@JPeer264
JPeer264 merged commit 550b2d8 into developJul 28, 2026
81 checks passed
@JPeer264
JPeer264 deleted the jp/cloudflare-instrument-agent-automatically branch July 28, 2026 17:15
JPeer264 added a commit that referenced this pull request Jul 28, 2026
JPeer264 added a commit that referenced this pull request Jul 29, 2026
Sign up for freeto 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

@JPeer264@andreiborza
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(cloudflare): Instrument Agents automatically - #22727

Merged
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically
Jul 28, 2026
Merged

feat(cloudflare): Instrument Agents automatically#22727
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically

Conversation

@JPeer264

Copy link
Copy Markdown
Member

Since instrumentAgentWithSentry is here, it also needs to be supported within the Vite plugin and its autoInstrument flag. The issue here is that we need to parse through the imports in order to understand where the Agent import is coming from. (The majority of the changes are tests again)

It could also be that external libraries are extending from Agent, such as McpAgent, and these need to be wrapped too. It is super hacky but it works.

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

@JPeer264JPeer264 self-assigned this Jul 28, 2026
@JPeer264JPeer264 changed the title feat(cloudflare): Add instrumentAgentWithSentry for Cloudflare Agentsfeat(cloudflare): Instrument Agents automaticallyJul 28, 2026
@github-actions

github-actionsBot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser29.85 kB+0.04%+9 B 🔺
@sentry/browser - with treeshaking flags28.05 kB+0.03%+8 B 🔺
@sentry/browser (incl. Tracing)47.42 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing + Span Streaming)47.43 kB+0.03%+10 B 🔺
@sentry/browser (incl. Tracing, Profiling)52.16 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing, Replay)86.74 kB+0.01%+6 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags76.16 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)91.48 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)104.11 kB+0.01%+9 B 🔺
@sentry/browser (incl. Feedback)47.16 kB+0.02%+8 B 🔺
@sentry/browser (incl. sendFeedback)34.69 kB+0.03%+9 B 🔺
@sentry/browser (incl. FeedbackAsync)39.78 kB+0.03%+8 B 🔺
@sentry/browser (incl. Metrics)30.92 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)31.14 kB+0.02%+6 B 🔺
@sentry/browser (incl. Metrics & Logs)31.83 kB+0.02%+6 B 🔺
@sentry/react31.64 kB+0.03%+7 B 🔺
@sentry/react (incl. Tracing)49.64 kB+0.02%+5 B 🔺
@sentry/vue34.77 kB+0.03%+8 B 🔺
@sentry/vue (incl. Tracing)49.37 kB+0.02%+9 B 🔺
@sentry/svelte29.88 kB+0.04%+9 B 🔺
CDN Bundle31.89 kB+0.02%+5 B 🔺
CDN Bundle (incl. Tracing)47.76 kB+0.01%+2 B 🔺
CDN Bundle (incl. Logs, Metrics)33.44 kB+0.02%+4 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)49.14 kB--
CDN Bundle (incl. Replay, Logs, Metrics)72.8 kB+0.01%+7 B 🔺
CDN Bundle (incl. Tracing, Replay)85.4 kB+0.01%+6 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.68 kB+0.01%+4 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)91.17 kB+0.01%+8 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.47 kB+0.01%+8 B 🔺
CDN Bundle - uncompressed95.12 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing) - uncompressed143.26 kB+0.03%+41 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed99.83 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed147.25 kB+0.03%+41 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed224.6 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed262.52 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed266.49 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed276.23 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed280.18 kB+0.02%+41 B 🔺
@sentry/nextjs (client)52.23 kB+0.01%+5 B 🔺
@sentry/sveltekit (client)47.84 kB+0.03%+10 B 🔺
@sentry/core/server79.73 kB-0.06%-47 B 🔽
@sentry/core/browser51.61 kB+0.02%+7 B 🔺
@sentry/node122.35 kB+1.07%+1.29 kB 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection)166 B--
@sentry/node - without tracing85.75 kB+1.51%+1.27 kB 🔺
@sentry/aws-serverless93.78 kB+1.16%+1.07 kB 🔺
@sentry/cloudflare (withSentry) - minified197.53 kB+0.01%+11 B 🔺
@sentry/cloudflare (withSentry)485.64 kB-0.03%-124 B 🔽

View base workflow run

@JPeer264
JPeer264 marked this pull request as ready for review July 28, 2026 11:05
@JPeer264
JPeer264 requested a review from a team as a code ownerJuly 28, 2026 11:05
@JPeer264
JPeer264 requested review from andreiborza, isaacs, mydea and timfish and removed request for a teamJuly 28, 2026 11:05
@andreiborza

Copy link
Copy Markdown
Member

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

@JPeer264

Copy link
Copy Markdown
MemberAuthor

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

Because I need to backport that PR. We can change that anytime now ofc

@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from cc88548 to 62f5ca2CompareJuly 28, 2026 13:36
Comment threadpackages/cloudflare/test/vite/autoInstrument.test.ts
@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from 62f5ca2 to 8d6a511CompareJuly 28, 2026 14:05
Comment on lines +94 to +95
// its base-class chain tells the two apart. Detection walks the module graph (base classes
// usually live in their own file), so it is limited to the configured DO classes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

q: Does that constraint really hold true? I wouldn't be surprised if someone has several classes in one file. Or am I misunderstanding?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

By definition all the classes are in one file, it is just the question if they are defined in the main entrypoint or in other files.

const code = stripComments(rawCode);
const shape = emptyShape();

// `class X extends Y {` / `class X<T> extends Y<T> {`, optionally exported and/or abstract.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about cases where generics extend, e.g. class X<T extends S> extends Y<T extends Z>?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

True. I'm sure there are more edge cases which we didn't think of just yet.
Added in facc163

Comment on lines +213 to +214
const CLASS_RE = /\b(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)([^{]*)\{/g;
for (const match of code.matchAll(CLASS_RE)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The CLASS_RE regex in shapeFromSource() fails to detect anonymous classes like export default class extends Agent {}, causing agent detection to fail for those modules.
Severity: MEDIUM

Suggested Fix

Modify the CLASS_RE regex to make the class name capture group optional. This will allow it to correctly match and parse anonymous default exported classes.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: packages/cloudflare/src/vite/moduleShape.ts#L213-L214
Potential issue: The regex `CLASS_RE` used in `shapeFromSource()` to parse module shapes
requires a class name to be present after the `class` keyword. This means it will not
match an anonymous default export, such as `export default class extends Agent {}`. When
this pattern is used in a module, `defaultExportIsClass` is not set on the module's
shape. Consequently, when `isAgentBinding()` later checks the module, it incorrectly
determines that the default export is not an agent, leading to a failure in agent
detection. The code seems to anticipate this pattern, as `shapeFromAst()` handles it,
but the regex-based fallback does not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh this seems relevant too, speaking of edge-cases @JPeer264

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

You can't export default a DurableObject.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

At least what I'm aware of*

@JPeer264
JPeer264 merged commit 550b2d8 into developJul 28, 2026
81 checks passed
@JPeer264
JPeer264 deleted the jp/cloudflare-instrument-agent-automatically branch July 28, 2026 17:15
JPeer264 added a commit that referenced this pull request Jul 28, 2026
JPeer264 added a commit that referenced this pull request Jul 29, 2026
Sign up for freeto 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

@JPeer264@andreiborza
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(cloudflare): Instrument Agents automatically - #22727

Merged
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically
Jul 28, 2026
Merged

feat(cloudflare): Instrument Agents automatically#22727
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically

Conversation

@JPeer264

Copy link
Copy Markdown
Member

Since instrumentAgentWithSentry is here, it also needs to be supported within the Vite plugin and its autoInstrument flag. The issue here is that we need to parse through the imports in order to understand where the Agent import is coming from. (The majority of the changes are tests again)

It could also be that external libraries are extending from Agent, such as McpAgent, and these need to be wrapped too. It is super hacky but it works.

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

@JPeer264JPeer264 self-assigned this Jul 28, 2026
@JPeer264JPeer264 changed the title feat(cloudflare): Add instrumentAgentWithSentry for Cloudflare Agentsfeat(cloudflare): Instrument Agents automaticallyJul 28, 2026
@github-actions

github-actionsBot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser29.85 kB+0.04%+9 B 🔺
@sentry/browser - with treeshaking flags28.05 kB+0.03%+8 B 🔺
@sentry/browser (incl. Tracing)47.42 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing + Span Streaming)47.43 kB+0.03%+10 B 🔺
@sentry/browser (incl. Tracing, Profiling)52.16 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing, Replay)86.74 kB+0.01%+6 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags76.16 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)91.48 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)104.11 kB+0.01%+9 B 🔺
@sentry/browser (incl. Feedback)47.16 kB+0.02%+8 B 🔺
@sentry/browser (incl. sendFeedback)34.69 kB+0.03%+9 B 🔺
@sentry/browser (incl. FeedbackAsync)39.78 kB+0.03%+8 B 🔺
@sentry/browser (incl. Metrics)30.92 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)31.14 kB+0.02%+6 B 🔺
@sentry/browser (incl. Metrics & Logs)31.83 kB+0.02%+6 B 🔺
@sentry/react31.64 kB+0.03%+7 B 🔺
@sentry/react (incl. Tracing)49.64 kB+0.02%+5 B 🔺
@sentry/vue34.77 kB+0.03%+8 B 🔺
@sentry/vue (incl. Tracing)49.37 kB+0.02%+9 B 🔺
@sentry/svelte29.88 kB+0.04%+9 B 🔺
CDN Bundle31.89 kB+0.02%+5 B 🔺
CDN Bundle (incl. Tracing)47.76 kB+0.01%+2 B 🔺
CDN Bundle (incl. Logs, Metrics)33.44 kB+0.02%+4 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)49.14 kB--
CDN Bundle (incl. Replay, Logs, Metrics)72.8 kB+0.01%+7 B 🔺
CDN Bundle (incl. Tracing, Replay)85.4 kB+0.01%+6 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.68 kB+0.01%+4 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)91.17 kB+0.01%+8 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.47 kB+0.01%+8 B 🔺
CDN Bundle - uncompressed95.12 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing) - uncompressed143.26 kB+0.03%+41 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed99.83 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed147.25 kB+0.03%+41 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed224.6 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed262.52 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed266.49 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed276.23 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed280.18 kB+0.02%+41 B 🔺
@sentry/nextjs (client)52.23 kB+0.01%+5 B 🔺
@sentry/sveltekit (client)47.84 kB+0.03%+10 B 🔺
@sentry/core/server79.73 kB-0.06%-47 B 🔽
@sentry/core/browser51.61 kB+0.02%+7 B 🔺
@sentry/node122.35 kB+1.07%+1.29 kB 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection)166 B--
@sentry/node - without tracing85.75 kB+1.51%+1.27 kB 🔺
@sentry/aws-serverless93.78 kB+1.16%+1.07 kB 🔺
@sentry/cloudflare (withSentry) - minified197.53 kB+0.01%+11 B 🔺
@sentry/cloudflare (withSentry)485.64 kB-0.03%-124 B 🔽

View base workflow run

@JPeer264
JPeer264 marked this pull request as ready for review July 28, 2026 11:05
@JPeer264
JPeer264 requested a review from a team as a code ownerJuly 28, 2026 11:05
@JPeer264
JPeer264 requested review from andreiborza, isaacs, mydea and timfish and removed request for a teamJuly 28, 2026 11:05
@andreiborza

Copy link
Copy Markdown
Member

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

@JPeer264

Copy link
Copy Markdown
MemberAuthor

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

Because I need to backport that PR. We can change that anytime now ofc

@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from cc88548 to 62f5ca2CompareJuly 28, 2026 13:36
Comment threadpackages/cloudflare/test/vite/autoInstrument.test.ts
@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from 62f5ca2 to 8d6a511CompareJuly 28, 2026 14:05
Comment on lines +94 to +95
// its base-class chain tells the two apart. Detection walks the module graph (base classes
// usually live in their own file), so it is limited to the configured DO classes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

q: Does that constraint really hold true? I wouldn't be surprised if someone has several classes in one file. Or am I misunderstanding?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

By definition all the classes are in one file, it is just the question if they are defined in the main entrypoint or in other files.

const code = stripComments(rawCode);
const shape = emptyShape();

// `class X extends Y {` / `class X<T> extends Y<T> {`, optionally exported and/or abstract.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about cases where generics extend, e.g. class X<T extends S> extends Y<T extends Z>?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

True. I'm sure there are more edge cases which we didn't think of just yet.
Added in facc163

Comment on lines +213 to +214
const CLASS_RE = /\b(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)([^{]*)\{/g;
for (const match of code.matchAll(CLASS_RE)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The CLASS_RE regex in shapeFromSource() fails to detect anonymous classes like export default class extends Agent {}, causing agent detection to fail for those modules.
Severity: MEDIUM

Suggested Fix

Modify the CLASS_RE regex to make the class name capture group optional. This will allow it to correctly match and parse anonymous default exported classes.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: packages/cloudflare/src/vite/moduleShape.ts#L213-L214
Potential issue: The regex `CLASS_RE` used in `shapeFromSource()` to parse module shapes
requires a class name to be present after the `class` keyword. This means it will not
match an anonymous default export, such as `export default class extends Agent {}`. When
this pattern is used in a module, `defaultExportIsClass` is not set on the module's
shape. Consequently, when `isAgentBinding()` later checks the module, it incorrectly
determines that the default export is not an agent, leading to a failure in agent
detection. The code seems to anticipate this pattern, as `shapeFromAst()` handles it,
but the regex-based fallback does not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh this seems relevant too, speaking of edge-cases @JPeer264

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

You can't export default a DurableObject.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

At least what I'm aware of*

@JPeer264
JPeer264 merged commit 550b2d8 into developJul 28, 2026
81 checks passed
@JPeer264
JPeer264 deleted the jp/cloudflare-instrument-agent-automatically branch July 28, 2026 17:15
JPeer264 added a commit that referenced this pull request Jul 28, 2026
JPeer264 added a commit that referenced this pull request Jul 29, 2026
Sign up for freeto 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

@JPeer264@andreiborza
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(cloudflare): Instrument Agents automatically - #22727

Merged
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically
Jul 28, 2026
Merged

feat(cloudflare): Instrument Agents automatically#22727
JPeer264 merged 2 commits into
jp/cloudflare-instrument-reset-conv-idfrom
jp/cloudflare-instrument-agent-automatically

Conversation

@JPeer264

Copy link
Copy Markdown
Member

Since instrumentAgentWithSentry is here, it also needs to be supported within the Vite plugin and its autoInstrument flag. The issue here is that we need to parse through the imports in order to understand where the Agent import is coming from. (The majority of the changes are tests again)

It could also be that external libraries are extending from Agent, such as McpAgent, and these need to be wrapped too. It is super hacky but it works.

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

@JPeer264JPeer264 self-assigned this Jul 28, 2026
@JPeer264JPeer264 changed the title feat(cloudflare): Add instrumentAgentWithSentry for Cloudflare Agentsfeat(cloudflare): Instrument Agents automaticallyJul 28, 2026
@github-actions

github-actionsBot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser29.85 kB+0.04%+9 B 🔺
@sentry/browser - with treeshaking flags28.05 kB+0.03%+8 B 🔺
@sentry/browser (incl. Tracing)47.42 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing + Span Streaming)47.43 kB+0.03%+10 B 🔺
@sentry/browser (incl. Tracing, Profiling)52.16 kB+0.03%+11 B 🔺
@sentry/browser (incl. Tracing, Replay)86.74 kB+0.01%+6 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags76.16 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)91.48 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)104.11 kB+0.01%+9 B 🔺
@sentry/browser (incl. Feedback)47.16 kB+0.02%+8 B 🔺
@sentry/browser (incl. sendFeedback)34.69 kB+0.03%+9 B 🔺
@sentry/browser (incl. FeedbackAsync)39.78 kB+0.03%+8 B 🔺
@sentry/browser (incl. Metrics)30.92 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)31.14 kB+0.02%+6 B 🔺
@sentry/browser (incl. Metrics & Logs)31.83 kB+0.02%+6 B 🔺
@sentry/react31.64 kB+0.03%+7 B 🔺
@sentry/react (incl. Tracing)49.64 kB+0.02%+5 B 🔺
@sentry/vue34.77 kB+0.03%+8 B 🔺
@sentry/vue (incl. Tracing)49.37 kB+0.02%+9 B 🔺
@sentry/svelte29.88 kB+0.04%+9 B 🔺
CDN Bundle31.89 kB+0.02%+5 B 🔺
CDN Bundle (incl. Tracing)47.76 kB+0.01%+2 B 🔺
CDN Bundle (incl. Logs, Metrics)33.44 kB+0.02%+4 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)49.14 kB--
CDN Bundle (incl. Replay, Logs, Metrics)72.8 kB+0.01%+7 B 🔺
CDN Bundle (incl. Tracing, Replay)85.4 kB+0.01%+6 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.68 kB+0.01%+4 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)91.17 kB+0.01%+8 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.47 kB+0.01%+8 B 🔺
CDN Bundle - uncompressed95.12 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing) - uncompressed143.26 kB+0.03%+41 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed99.83 kB+0.04%+38 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed147.25 kB+0.03%+41 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed224.6 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed262.52 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed266.49 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed276.23 kB+0.02%+41 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed280.18 kB+0.02%+41 B 🔺
@sentry/nextjs (client)52.23 kB+0.01%+5 B 🔺
@sentry/sveltekit (client)47.84 kB+0.03%+10 B 🔺
@sentry/core/server79.73 kB-0.06%-47 B 🔽
@sentry/core/browser51.61 kB+0.02%+7 B 🔺
@sentry/node122.35 kB+1.07%+1.29 kB 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection)166 B--
@sentry/node - without tracing85.75 kB+1.51%+1.27 kB 🔺
@sentry/aws-serverless93.78 kB+1.16%+1.07 kB 🔺
@sentry/cloudflare (withSentry) - minified197.53 kB+0.01%+11 B 🔺
@sentry/cloudflare (withSentry)485.64 kB-0.03%-124 B 🔽

View base workflow run

@JPeer264
JPeer264 marked this pull request as ready for review July 28, 2026 11:05
@JPeer264
JPeer264 requested a review from a team as a code ownerJuly 28, 2026 11:05
@JPeer264
JPeer264 requested review from andreiborza, isaacs, mydea and timfish and removed request for a teamJuly 28, 2026 11:05
@andreiborza

Copy link
Copy Markdown
Member

For the tests I wished to add them in the cloudflare-integration-tests, but unfortunately agents needs Node v20+ and locally we run with v18, so it wouldn't work without either bumping Nodejs or making the tests their own package so they are independent.

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

@JPeer264

Copy link
Copy Markdown
MemberAuthor

What do you mean we run with v18 locally? Why can't we bump now that we're on v11?

Because I need to backport that PR. We can change that anytime now ofc

@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from cc88548 to 62f5ca2CompareJuly 28, 2026 13:36
Comment threadpackages/cloudflare/test/vite/autoInstrument.test.ts
@JPeer264
JPeer264force-pushed the jp/cloudflare-instrument-agent-automatically branch from 62f5ca2 to 8d6a511CompareJuly 28, 2026 14:05
Comment on lines +94 to +95
// its base-class chain tells the two apart. Detection walks the module graph (base classes
// usually live in their own file), so it is limited to the configured DO classes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

q: Does that constraint really hold true? I wouldn't be surprised if someone has several classes in one file. Or am I misunderstanding?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

By definition all the classes are in one file, it is just the question if they are defined in the main entrypoint or in other files.

const code = stripComments(rawCode);
const shape = emptyShape();

// `class X extends Y {` / `class X<T> extends Y<T> {`, optionally exported and/or abstract.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about cases where generics extend, e.g. class X<T extends S> extends Y<T extends Z>?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

True. I'm sure there are more edge cases which we didn't think of just yet.
Added in facc163

Comment on lines +213 to +214
const CLASS_RE = /\b(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)([^{]*)\{/g;
for (const match of code.matchAll(CLASS_RE)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The CLASS_RE regex in shapeFromSource() fails to detect anonymous classes like export default class extends Agent {}, causing agent detection to fail for those modules.
Severity: MEDIUM

Suggested Fix

Modify the CLASS_RE regex to make the class name capture group optional. This will allow it to correctly match and parse anonymous default exported classes.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: packages/cloudflare/src/vite/moduleShape.ts#L213-L214
Potential issue: The regex `CLASS_RE` used in `shapeFromSource()` to parse module shapes
requires a class name to be present after the `class` keyword. This means it will not
match an anonymous default export, such as `export default class extends Agent {}`. When
this pattern is used in a module, `defaultExportIsClass` is not set on the module's
shape. Consequently, when `isAgentBinding()` later checks the module, it incorrectly
determines that the default export is not an agent, leading to a failure in agent
detection. The code seems to anticipate this pattern, as `shapeFromAst()` handles it,
but the regex-based fallback does not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh this seems relevant too, speaking of edge-cases @JPeer264

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

You can't export default a DurableObject.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

At least what I'm aware of*

@JPeer264
JPeer264 merged commit 550b2d8 into developJul 28, 2026
81 checks passed
@JPeer264
JPeer264 deleted the jp/cloudflare-instrument-agent-automatically branch July 28, 2026 17:15
JPeer264 added a commit that referenced this pull request Jul 28, 2026
JPeer264 added a commit that referenced this pull request Jul 29, 2026
Sign up for freeto 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

@JPeer264@andreiborza