Skip to content

telemetry: add agentName to anonymous metadata#95586

Merged
andrewimm merged 2 commits into
canaryfrom
andrewimm/agent-telemetry-meta
Jul 14, 2026
Merged

telemetry: add agentName to anonymous metadata#95586
andrewimm merged 2 commits into
canaryfrom
andrewimm/agent-telemetry-meta

Conversation

@andrewimm

Copy link
Copy Markdown
Contributor

Add agentName to anonymous telemetry metadata

What

Adds an agentName: string | null field to the anonymous telemetry metadata block that accompanies every payload, alongside existing fields like ciName, nextVersion, and isCI. The value is the detected AI coding agent driving the process (e.g. claude, cursor, codex), or null when none is detected.

Why

We currently have no visibility into how much Next.js development happens under AI coding agents. This field lets us understand that split in the same anonymous way we already track other CLI usage.

How

Agent detection via @vercel/detect-agent

  • Vendored @vercel/detect-agent (from the Vercel CLI) into next/dist/compiled/@vercel/detect-agent (new taskfile ncc task, externals entry, and type declaration), matching how I understand other third-party deps are bundled.
  • The vendored detection method is async (it does a filesystem probe for Devin in addition to env-var checks) and potentially slower than we'd want for logging, so it's wrapped in a memoized helper: telemetry/agent-name.ts. getAgentName() caches the promise from determineAgent(): the first call runs detection once, concurrent callers share that in-flight promise, and every later call resolves against the already-settled result. The agent shouldn't change over the process lifetime, so this is safe. Detection failures resolve to null so telemetry never throws on this path.

Wiring into the payload

  • getAnonymousMeta() is now async and includes agentName: await getAgentName(). storage.ts awaits it on the send path (the record method was thankfully already async).
  • The NEXT_TELEMETRY_DEBUG=1 output now includes the meta block alongside each event, so the full payload — including agentName — is observable locally without sending anything. We didn't output this before, so it was harder to debug these fields.

Consolidating agent detection

  • There was a second, synchronous, hand-vendored copy of agent detection (telemetry/detect-agent.ts) used only by the AGENTS.md/CLAUDE.md auto-generation hook. It's been removed and that hook (ensureAgentRulesForDev) now uses the same vendored getAgentName(). Now we have a single source of truth and can easily get upstream changes from the vercel CLI package. ensureAgentRulesForDev became async, its only caller in start-server.ts was already in an async scope, so this is easy.

Testing

  • NEXT_TELEMETRY_DEBUG=1 next build shows agentName in the payload meta block next to ciName/nextVersion.
  • Verified the memoized helper: agent present -> name; cleared env -> null; repeated calls return the same promise instantly.
  • agent-rules-auto-generate dev tests pass (agent-detected generates files; cleared-env does not)
  • next-server-nft trace snapshot updated: adds @vercel/detect-agent + agent-name.js, removes detect-agent.js.
  • ts, build, existing telemetry unit test, lint

Adds `agentName: string | null` to the anonymous telemetry meta block
(alongside ciName, nextVersion, etc.), reporting the detected AI coding
agent or null.

- Vendor @vercel/detect-agent into compiled and wrap its async
  determineAgent() in a memoized getAgentName() helper (caches the
  promise; detection can't change over the process lifetime).
- Make getAnonymousMeta() async and include the field; also surface the
  meta block in NEXT_TELEMETRY_DEBUG output.
- Drop the duplicate sync detect-agent.ts; the AGENTS.md auto-gen hook
  now shares the same vendored detection.
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Tests Passed

Commit: f8d7f60

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Stats from current PR

🔴 1 regression, 1 improvement

Metric Canary PR Change Trend
node_modules Size 516 MB 516 MB 🔴 +56.9 kB (+0%) ▁▁▁▁▁
Webpack Build Time (cached) 25.826s 25.102s 🟢 724ms (-3%) ▁▁▂▁█
📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 814ms 814ms ▂▂▁▂▂
Cold (Ready in log) 794ms 798ms ▁▁▁▁▁
Cold (First Request) 1.351s 1.358s ▁▂▁▁▁
Warm (Listen) 814ms 814ms ▂▂▁▁▂
Warm (Ready in log) 794ms 800ms ▁▁▁▁▁
Warm (First Request) 1.354s 1.354s ▁▁▂▂▁
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 813ms 813ms ▂▁▂▂█
Cold (Ready in log) 779ms 782ms ▁▁▁▁█
Cold (First Request) 3.405s 3.395s ▂▁▁▁█
Warm (Listen) 814ms 813ms ▂▁▂▂█
Warm (Ready in log) 795ms 783ms ▁▁▂▁█
Warm (First Request) 3.497s 3.397s ▂▁▂▁█

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 5.523s 5.453s ▂▁▁▁▁
Cached Build 5.704s 5.774s ▂▁▁▁▁
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 25.482s 25.791s ▁▁▁▁█
Cached Build 25.826s 25.102s 🟢 724ms (-3%) ▁▁▂▁█
node_modules Size 516 MB 516 MB 🔴 +56.9 kB (+0%) ▁▁▁▁▁
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
03gmyxr7dukvs.js gzip 10.3 kB N/A -
040t1q17lo593.js gzip 8.77 kB N/A -
0bb0vtp8dwsez.js gzip 450 B N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0l0sr6gau5xjn.js gzip 3.53 kB N/A -
0nxqz3q165m2l.js gzip 220 B N/A -
0qr-1tnb7_hg5.js gzip 5.72 kB N/A -
0uilzvklme4ea.js gzip 8.76 kB N/A -
0z0y83mfx-l-h.js gzip 10 kB N/A -
1_-g_r52k540c.js gzip 7.4 kB N/A -
15ok8ydf274z0.js gzip 10.6 kB N/A -
16cqqcigykfea.js gzip 13.6 kB N/A -
17h18zxt1915l.js gzip 1.46 kB N/A -
18-9bq-26uhe7.js gzip 169 B N/A -
18litfkiq0d30.js gzip 157 B N/A -
1elt1qium-r2m.css gzip 115 B 115 B
1eme33rcct9nc.js gzip 71 kB N/A -
1fovbm8u-lg5_.js gzip 155 B N/A -
1fq1h7dxad4cv.js gzip 9.44 kB N/A -
1fym-adbk_69_.js gzip 13.1 kB N/A -
1g80-jfls9qyr.js gzip 13.2 kB N/A -
1gav7nfh4y8ln.js gzip 8.76 kB N/A -
20kz8xljphpr_.js gzip 157 B N/A -
2rypqffktcxqh.js gzip 8.69 kB N/A -
2u-dnpku2a1z9.js gzip 158 B N/A -
2uqsizxtehgqf.js gzip 8.79 kB N/A -
2vzktdqwbijqc.js gzip 8.74 kB N/A -
2wcar9zudlttv.js gzip 44.9 kB N/A -
2wcfiwm7zhx_e.js gzip 157 B N/A -
3-xdp916emiuf.js gzip 161 B N/A -
34ezsi7cg3q5c.js gzip 154 B N/A -
38znnljaf8amd.js gzip 156 B N/A -
3c244akvx_-be.js gzip 8.74 kB N/A -
3dmlemitx3ef3.js gzip 2.29 kB N/A -
3fkxmawczju21.js gzip 65.6 kB N/A -
3q5ubosw9n8d8.js gzip 154 B N/A -
3rhvwemm8rb1c.js gzip 162 B N/A -
3rvisurq-oosw.js gzip 158 B N/A -
3w1tjc34walgu.js gzip 156 B N/A -
4237vdj6eyslt.js gzip 8.7 kB N/A -
turbopack-0c..x9iy.js gzip 3.85 kB N/A -
turbopack-0j..gt5w.js gzip 3.85 kB N/A -
turbopack-0p..0dco.js gzip 3.83 kB N/A -
turbopack-1-..ay-c.js gzip 3.85 kB N/A -
turbopack-14..067k.js gzip 3.85 kB N/A -
turbopack-1a..6s-g.js gzip 3.85 kB N/A -
turbopack-1l..kus3.js gzip 3.85 kB N/A -
turbopack-1n..tbg6.js gzip 3.85 kB N/A -
turbopack-1p..ev02.js gzip 3.85 kB N/A -
turbopack-1t..zb-_.js gzip 3.85 kB N/A -
turbopack-28..g94c.js gzip 3.86 kB N/A -
turbopack-30..g3iz.js gzip 3.85 kB N/A -
turbopack-3t..fpke.js gzip 3.85 kB N/A -
turbopack-3y.._vdi.js gzip 3.85 kB N/A -
00tc16j5-eos9.js gzip N/A 1.46 kB -
0a521jxe_t-6d.js gzip N/A 160 B -
0gaw1gmpwuwkn.js gzip N/A 8.8 kB -
0o68g1i7ybfkd.js gzip N/A 156 B -
0pyf8zei43277.js gzip N/A 156 B -
0vx4zj_eeb5ud.js gzip N/A 157 B -
12zyjjmq4oad1.js gzip N/A 5.72 kB -
18mq_fqja-73h.js gzip N/A 156 B -
1k9cc4-gpa2cx.js gzip N/A 13.1 kB -
1n59uogejo81s.js gzip N/A 10.3 kB -
1npei3hlexdv3.js gzip N/A 9.99 kB -
1sltdcbv0s6o4.js gzip N/A 71 kB -
1ssuiwj_rnpc5.js gzip N/A 8.77 kB -
1yy5wxmflq6lq.js gzip N/A 153 B -
2-phc9obd3nyd.js gzip N/A 8.77 kB -
22c8soq46y2lz.js gzip N/A 157 B -
24utq57r3l1cw.js gzip N/A 8.7 kB -
2dmfqrypemelz.js gzip N/A 156 B -
2fhjq6ek3srmr.js gzip N/A 8.69 kB -
2g0t7upz_v-hf.js gzip N/A 10.6 kB -
2hp876f1rgdtl.js gzip N/A 160 B -
2ia9qqy99aie9.js gzip N/A 13.6 kB -
2n5fp99a5xt9s.js gzip N/A 7.4 kB -
2oza28kdqvoay.js gzip N/A 8.74 kB -
2vj7a78_lrrs8.js gzip N/A 156 B -
2wpt4z5vyyvpm.js gzip N/A 155 B -
2zh8-mmhujy8o.js gzip N/A 2.29 kB -
30h_m0irj-q6p.js gzip N/A 221 B -
37ejbfx1u9lpo.js gzip N/A 8.75 kB -
38c63ct4i1cy7.js gzip N/A 449 B -
3hd1us4ordyc9.js gzip N/A 13.2 kB -
3jimpal3lvz5e.js gzip N/A 9.44 kB -
3rxda-enqm36a.js gzip N/A 45 kB -
3su6a4l8jbfth.js gzip N/A 3.53 kB -
3wb4pe4z4-aim.js gzip N/A 8.77 kB -
3y8ffq2kfz4mn.js gzip N/A 169 B -
409xrovy6zci1.js gzip N/A 65.6 kB -
42h8e-xnr4f94.js gzip N/A 156 B -
turbopack-0q..2qto.js gzip N/A 3.85 kB -
turbopack-0w..yybo.js gzip N/A 3.83 kB -
turbopack-20..agko.js gzip N/A 3.85 kB -
turbopack-22..65ru.js gzip N/A 3.85 kB -
turbopack-23..q9a2.js gzip N/A 3.85 kB -
turbopack-27..pjdx.js gzip N/A 3.84 kB -
turbopack-2d..a7oc.js gzip N/A 3.85 kB -
turbopack-2f..j1mm.js gzip N/A 3.86 kB -
turbopack-2q..mpic.js gzip N/A 3.85 kB -
turbopack-3_..un87.js gzip N/A 3.85 kB -
turbopack-31..idrb.js gzip N/A 3.85 kB -
turbopack-3a..blut.js gzip N/A 3.85 kB -
turbopack-3q..c7x6.js gzip N/A 3.85 kB -
turbopack-3r..5x3y.js gzip N/A 3.85 kB -
Total 448 kB 448 kB ⚠️ +31 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 729 B 721 B 🟢 8 B (-1%)
Total 729 B 721 B ✅ -8 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 431 B 433 B
Total 431 B 433 B ⚠️ +2 B

📦 Webpack

Client

Main Bundles
Canary PR Change
2486.HASH.js gzip 169 B N/A -
3146-HASH.js gzip 64 kB N/A -
39fcf99b-HASH.js gzip 62.9 kB N/A -
8443-HASH.js gzip 4.68 kB N/A -
9431-HASH.js gzip 5.62 kB N/A -
framework-HASH.js gzip 59.8 kB 59.8 kB
main-app-HASH.js gzip 255 B 254 B
main-HASH.js gzip 39.7 kB 40.1 kB 🔴 +453 B (+1%)
webpack-HASH.js gzip 1.68 kB 1.68 kB
6105-HASH.js gzip N/A 5.63 kB -
764.HASH.js gzip N/A 169 B -
8898-HASH.js gzip N/A 63.4 kB -
9597-HASH.js gzip N/A 4.65 kB -
e1ccab69-HASH.js gzip N/A 62.9 kB -
Total 239 kB 238 kB ✅ -200 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 194 B 194 B
_error-HASH.js gzip 183 B 182 B
css-HASH.js gzip 335 B 335 B
dynamic-HASH.js gzip 1.8 kB 1.8 kB
edge-ssr-HASH.js gzip 255 B 254 B
head-HASH.js gzip 351 B 349 B
hooks-HASH.js gzip 384 B 384 B
image-HASH.js gzip 580 B 580 B
index-HASH.js gzip 259 B 259 B
link-HASH.js gzip 2.49 kB 2.49 kB
routerDirect..HASH.js gzip 319 B 319 B
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 315 B 313 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.96 kB 7.95 kB ✅ -7 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 128 kB 128 kB
page.js gzip 283 kB 284 kB
Total 410 kB 412 kB ⚠️ +2.18 kB
Middleware
Canary PR Change
middleware-b..fest.js gzip 617 B 615 B
middleware-r..fest.js gzip 156 B 155 B
middleware.js gzip 45.2 kB 45 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 46.8 kB 46.6 kB ✅ -171 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 719 B 720 B
Total 719 B 720 B ⚠️ +1 B
Build Cache
Canary PR Change
0.pack gzip 4.79 MB 4.78 MB 🟢 7.35 kB (0%)
index.pack gzip 118 kB 118 kB
index.pack.old gzip 118 kB 118 kB
Total 5.02 MB 5.01 MB ✅ -7.55 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 363 kB 363 kB
app-page-exp..prod.js gzip 201 kB 201 kB
app-page-tur...dev.js gzip 363 kB 363 kB
app-page-tur..prod.js gzip 201 kB 201 kB
app-page-tur...dev.js gzip 359 kB 359 kB
app-page-tur..prod.js gzip 199 kB 199 kB
app-page.run...dev.js gzip 360 kB 360 kB
app-page.run..prod.js gzip 199 kB 199 kB
app-route-ex...dev.js gzip 81.1 kB 81.1 kB
app-route-ex..prod.js gzip 55.2 kB 55.2 kB
app-route-tu...dev.js gzip 81.1 kB 81.1 kB
app-route-tu..prod.js gzip 55.2 kB 55.2 kB
app-route-tu...dev.js gzip 80.7 kB 80.7 kB
app-route-tu..prod.js gzip 55 kB 55 kB
app-route.ru...dev.js gzip 80.7 kB 80.7 kB
app-route.ru..prod.js gzip 54.9 kB 54.9 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 45.2 kB 45.2 kB
pages-api-tu..prod.js gzip 34 kB 34 kB
pages-api.ru...dev.js gzip 45.2 kB 45.2 kB
pages-api.ru..prod.js gzip 33.9 kB 33.9 kB
pages-turbo....dev.js gzip 54.7 kB 54.7 kB
pages-turbo...prod.js gzip 39.6 kB 39.6 kB
pages.runtim...dev.js gzip 54.7 kB 54.7 kB
pages.runtim..prod.js gzip 39.6 kB 39.6 kB
server.runti..prod.js gzip 65.3 kB 65.3 kB
use-cache-pr...dev.js gzip 71.4 kB 71.4 kB
use-cache-pr...dev.js gzip 71.4 kB 71.4 kB
use-cache-pr...dev.js gzip 69.7 kB 69.7 kB
use-cache-pr...dev.js gzip 69.7 kB 69.7 kB
Total 3.48 MB 3.48 MB ✅ -1 B
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/f8d7f602a3dc2bfe47f96ffe31c7376a276e97d4/next

Commit: f8d7f60

@andrewimm

Copy link
Copy Markdown
Contributor Author

heads up @gaojude this replaces the manually copied agent detection logic with a vendored copy, like you suggested. I integrated that into your AGENT.md generation code, so any time we change the upstream package you can just update the vendor version.

@andrewimm
andrewimm requested review from bgw and wbinnssmith July 7, 2026 23:04
const { next } = nextTestSetup({
files: __dirname,
// Explicitly clear every env var `detectAgent()` inspects so the
// Explicitly clear every env var the agent detector inspects so the

@vercel vercel Bot Jul 7, 2026

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.

The "no agent" test fails to clear CURSOR_EXTENSION_HOST_ROLE, so it is non-hermetic and will falsely detect an agent when run inside a Cursor agent-exec session.

Fix on Vercel

Comment on lines +9 to +11
* Detection is delegated to `@vercel/detect-agent`, which performs an async
* filesystem probe (e.g. for Devin) in addition to environment-variable checks,
* so it can be relatively expensive. The detected agent cannot change over the

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.

It seems a little silly that detectAgent is async at all: https://github.com/vercel/vercel/blob/e12b1bd68c6e69ab1065dda89c9209ae1bdfb549/packages/detect-agent/src/index.ts#L146

It could just trust that if DEVIN_LOCAL_PATH is set, that we're in Devin, I don't really understand why it needs to also look for the file.

The fact that it's async makes this code a bit more annoying, and is also requiring the callsites to become async, which is all unfortunate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nod. I'm just trusting the CLI team knows what they're doing here. It's frustrating that only one agent causes the whole thing to be async, but we're really lucky that all callsites were async / writing to the FS anyways.

sessionId: this.sessionId,
},
meta: getAnonymousMeta(),
meta: await getAnonymousMeta(),

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.

If NEXT_TELEMETRY_DEBUG is set, we're now computing this twice? I guess that's okay because NEXT_TELEMETRY_DEBUG isn't normally set, but it's gross.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think we're the only people who run NEXT_TELEMETRY_DEBUG

Comment on lines -1 to -6
// NOTE: This file duplicates the detection helper added in
// https://github.com/vercel/next.js/pull/91854 (branch: imm/agent-telemetry).
// When that PR lands, whichever branch merges second should collapse the
// duplicate — the contents are intentionally identical so the merge is a
// no-op. The env variable checks mirror the same functionality in the
// `vercel` CLI.

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.

Nice, thanks for cleaning up.

Comment on lines 41 to 57
env: {
AI_AGENT: '',
CURSOR_TRACE_ID: '',
CURSOR_AGENT: '',
GEMINI_CLI: '',
CODEX_SANDBOX: '',
CODEX_CI: '',
CODEX_THREAD_ID: '',
ANTIGRAVITY_AGENT: '',
AUGMENT_AGENT: '',
OPENCODE_CLIENT: '',
CLAUDECODE: '',
CLAUDE_CODE: '',
REPL_ID: '',
COPILOT_MODEL: '',
COPILOT_ALLOW_ALL: '',
COPILOT_GITHUB_TOKEN: '',

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.

@andrewimm
andrewimm merged commit ca41473 into canary Jul 14, 2026
129 checks passed
@andrewimm
andrewimm deleted the andrewimm/agent-telemetry-meta branch July 14, 2026 23:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants