`generation_id` has extra suffix for `AfterAgentThought`

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

The generation_id in afterAgentThought is sometimes different from the generation_id used by the other hook events in the same turn.

For example:

Other events:
f1c8a21b-…-a86f8f5f1e17

afterAgentThought:
f1c8a21b-…-a86f8f5f1e17-0-7lg9
f1c8a21b-…-a86f8f5f1e17-2-32js
f1c8a21b-…-a86f8f5f1e17-9-vilj
f1c8a21b-…-a86f8f5f1e17-10-6o9s
f1c8a21b-…-a86f8f5f1e17-11-r94a

Steps to Reproduce

Just send a complex request that will have the agent to think couple times, and check the generation_id of those afterAgentThought events

Expected Behavior

afterAgentThought should use the same generation_id as the other events in the same turn.

Operating System

MacOS

Version Information

Cursor IDE
version: 3.10.11

Does this stop you from using Cursor

No - Cursor works, but with this issue

Hey @Morris_Hsieh!

Interesting catch!

What you’re seeing is the per-model-step invocation id (the turn request id + a step counter + a short random suffix), while the other hooks report the turn-level request id.

Workaround for now: the turn’s generation_id is always a prefix of the afterAgentThought value, so you can join events within a turn by stripping the trailing -<step>-<random> using some RegEx.

^(.*)-\d+-[a-z0-9]{4}$ → capture group 1 is the turn generation_id.

We’ve filed a bug internally to decide whether to make this consistent (report the turn id everywhere) or expose the invocation id as a separate field.

One thing we noticed while reproducing: in our runs, afterAgentThought sometimes fired twice per thought, once with the suffixed invocation id and once with the bare turn generation_id. Are you seeing that too on your side (duplicate afterAgentThought events for the same thought), or just the single suffixed value?

Hey @Colin,

Thanks for confirming the ID format and filing the bug. The workaround for now should work!

For the duplicating afterAgentThought, I’m seeing that as well.

Sometimes (35 out of 91 distinct thoughts in my testing) afterAgentThought fires twice for the same thought with identical text:

  • one event uses the bare turn-level generation_id

  • the other with suffixed one

Also, those duplicate events have different timestamps, from a few milliseconds to around two seconds apart

Thanks for confirming. I’ve filed a bug for that as well.

Still reproducing on Cursor 3.19.19 / Windows 11. Sharing a larger dataset and a minimal repro.

Where does the bug appear

Cursor IDE hooks (~/.cursor/hooks.json command hook)

Describe the Bug

afterAgentThought is invoked twice for a single thought. The two payloads are identical except generation_id: one is the bare turn UUID, the other is the same UUID plus -<n>-<token>.

Over 3,938 hook events (2026-09-08 → 2026-09-10):

hook events duplicate pairs
afterAgentThought 662 264 (40%)
all other 14 registered hooks 3,276 0

Gap between the two calls: min 66ms, p50 404ms, p90 677ms, max 943ms.

Among 298 same-text / same-base-id pairs:

  • unsuffixed first, then suffixed: 240
  • suffixed first, then unsuffixed: 58
  • both suffixed / both unsuffixed: 0

Steps to Reproduce

Register only afterAgentThought:

{
  "version": 1,
  "hooks": {
    "afterAgentThought": [
      { "command": "node \"C:/abs/path/cursor-hook-probe.cjs\"", "timeout": 2 }
    ]
  }
}

Probe (no deps). Appends one JSONL line per invocation to ~/cursor-hook-probe.jsonl:

const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const OUT = path.join(os.homedir(), 'cursor-hook-probe.jsonl');
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (c) => { raw += c; });
process.stdin.on('end', () => {
  let p;
  try {
    p = JSON.parse(raw.replace(/^\uFEFF/, ''));
    if (typeof p === 'string') p = JSON.parse(p);
  } catch { p = null; }
  if (!p || typeof p !== 'object') p = {};
  if (p.conversation_id) {
    fs.appendFileSync(OUT, JSON.stringify({
      at: new Date().toISOString(),
      pid: process.pid,
      hook: p.hook_event_name ?? null,
      conversation_id: p.conversation_id,
      generation_id: p.generation_id ?? null,
      text40: typeof p.text === 'string' ? p.text.slice(0, 40) : null,
    }) + '\n');
  }
  process.exit(0);
});

Restart Cursor, send one prompt that produces thinking, then inspect ~/cursor-hook-probe.jsonl. Reproduced when two lines share conversation_id + identical text40 within ~1s, and generation_id differs only by the suffix.

Expected Behavior

One afterAgentThought per thought.

Operating System

Windows 11 Pro (10.0.26200)

Cursor IDE version: 3.19.19

Does this stop you from using Cursor

No — Cursor works, but hook-based tools record/display the same thought twice. In this dataset 298/662 afterAgentThought records (45%) were duplicates.