Skip to content

Fix Cursor CLI activity trace grouping - #169

Merged
Minitour merged 6 commits into
developfrom
fix/cursor-activity-conversation-reconcile
Aug 4, 2026
Merged

Fix Cursor CLI activity trace grouping#169
Minitour merged 6 commits into
developfrom
fix/cursor-activity-conversation-reconcile

Conversation

@Minitour

@Minitour Minitour commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

  • Reconcile Cursor CLI activity rows so prompts and tool spans stay in one run during live SSE updates (chat vs agent-session conversation_id mismatch).
  • PR Release: v2.0.1 #168 review fixes: valid JSON (or null) for oversized attributes_json; plugin skill fallback only for type: plugin; composite run ids (conversation:generation); real manifestFile in nested plugin lookup; test formatting.
  • Skip Cursor conversation reconcile when the same generation_id maps to conflicting chat ids.

Test plan

  • bun test src/shared/__tests__/activity-correlation-reconcile.test.ts src/shared/__tests__/activity-attributes.test.ts web-ui/src/features/projects/components/activity/groupActivityRuns.test.ts src/server/__tests__/skill-content-plugin.test.ts
  • Cursor Agent CLI: opening prompt remains visible as tools stream in
  • Activity run dialog: two conversations with the same generation_id open distinct runs

Cursor Agent CLI sends different conversation_id values on prompts vs tool hooks; reconcile by generation before grouping so live SSE updates keep one run per turn.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix Cursor CLI activity trace grouping via conversation ID reconciliation

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Reconcile Cursor activity rows so one generation uses a single chat conversation_id.
• Apply reconciliation before UI grouping to keep SSE tool spans in the same run.
• Add unit tests covering reconciliation and Cursor CLI grouping behavior.
Diagram

graph TD
  cursor{{"Cursor Agent CLI"}} --> rows["Activity hook rows"] --> reconcile["Reconcile conv IDs"] --> group["Group activity runs"] --> ui["Activity feed UI"]
  sharedTests["Shared reconcile tests"] --> reconcile
  uiTests["UI grouping tests"] --> group
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Fix the Cursor CLI hook emitter upstream
  • ➕ Eliminates the need for downstream reconciliation logic
  • ➕ Prevents inconsistent data from entering any consumers (UI, storage, analytics)
  • ➖ May not be actionable/owned by this repo
  • ➖ Doesn’t help users until Cursor ships the change
2. Reconcile at ingestion/storage time (server-side)
  • ➕ Ensures DB rows are consistent for all clients and historical queries
  • ➕ Avoids needing UI-side normalization in multiple places
  • ➖ More invasive; may require backfill/migration strategy for existing rows
  • ➖ Higher risk if reconciliation logic has edge cases across sources
3. Group primarily by generation_id instead of conversation_id rewriting
  • ➕ Avoids mutating/rewriting identifiers
  • ➕ Can be localized to the grouping algorithm
  • ➖ Requires broader grouping rule changes and careful handling of multi-conversation generations
  • ➖ May complicate other sources/providers where generation_id semantics differ

Recommendation: The PR’s approach (provider-scoped reconciliation for Cursor, applied before grouping) is a pragmatic fix with low blast radius and directly addresses the live SSE split-run symptom. Long term, consider moving the same normalization server-side at ingestion so all consumers (and historical data) benefit, but the current UI-level normalization is an appropriate, fast remediation.

Files changed (4) +224 / -1

Bug fix (2) +99 / -1
activity-correlation-reconcile.tsImplement Cursor activity conversation_id reconciliation by generation +96/-0

Implement Cursor activity conversation_id reconciliation by generation

• Adds shared utilities to extract chat conversation IDs from Cursor transcript paths and to normalize Cursor activity rows so a generation’s prompt/tools/stops share the same chat conversation_id. Uses a two-pass mapping (generation -> chat, then agent-session -> chat) to reconcile tool spans even when they follow rewritten rows.

src/shared/activity-correlation-reconcile.ts

groupActivityRuns.tsNormalize Cursor rows before grouping activity conversations +3/-1

Normalize Cursor rows before grouping activity conversations

• Imports and applies the shared Cursor conversation_id reconciliation to the incoming calls before correlating/grouping. This prevents live SSE updates from splitting prompts and tool spans into separate activity runs.

web-ui/src/features/projects/components/activity/groupActivityRuns.ts

Tests (2) +125 / -0
activity-correlation-reconcile.test.tsAdd unit tests for Cursor conversation ID reconciliation +93/-0

Add unit tests for Cursor conversation ID reconciliation

• Introduces tests for transcript-path chat ID parsing and for rewriting Cursor tool/shell rows to the prompt chat conversation_id within a generation. Includes a guard test ensuring non-Cursor sources are not modified.

src/shared/tests/activity-correlation-reconcile.test.ts

groupActivityRuns.test.tsAdd regression test for Cursor CLI prompt/tool span grouping +32/-0

Add regression test for Cursor CLI prompt/tool span grouping

• Adds a test ensuring a Cursor CLI tool span with an agent-session conversation_id merges into the prompt’s chat conversation_id for the same generation, resulting in a single grouped run.

web-ui/src/features/projects/components/activity/groupActivityRuns.test.ts

Keep attributes_json valid JSON under the size cap, tighten plugin skill fallback guards, namespace activity run ids by conversation, populate nested plugin manifest paths, and skip Cursor id reconcile when generation chat ids conflict.
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 4, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Informational

1. Unneeded JSON parsing ✓ Resolved 🐞 Bug ➹ Performance
Description
reconcileCursorActivityConversationIds() JSON.parses attributes_json for every Cursor row with a
generation_id that isn’t a prompt with a conversation_id, even when transcript_path is absent.
Because groupActivityConversations() runs this reconciliation on every activity grouping, it adds
avoidable CPU overhead during UI rendering.
Code

src/shared/activity-correlation-reconcile.ts[R65-68]

+		if (!chatId) {
+			chatId = chatConversationIdFromTranscriptPath(
+				transcriptPathFromRow(call),
+			);
Evidence
The reconciliation loop calls transcriptPathFromRow(call) whenever a Cursor row isn’t a prompt
with a usable conversation_id; transcriptPathFromRow() unconditionally calls
parseAttributesJson(), which runs JSON.parse() for any non-empty attributes_json. This work is
triggered for the entire activity list because groupActivityConversations() runs reconciliation
before grouping.

web-ui/src/features/projects/components/activity/groupActivityRuns.ts[38-47]
src/shared/activity-correlation-reconcile.ts[26-44]
src/shared/activity-correlation-reconcile.ts[56-69]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`reconcileCursorActivityConversationIds()` ends up calling `JSON.parse()` on `attributes_json` for many Cursor rows (any non-prompt row with a non-empty `generation_id`) just to attempt to extract `transcript_path`. In typical feeds, most rows won’t contribute a `transcript_path`, so this adds avoidable CPU work in the browser each time `groupActivityConversations()` is called.
### Issue Context
- In `groupActivityConversations()`, reconciliation runs before grouping.
- In reconciliation, `transcriptPathFromRow()` always calls `parseAttributesJson()` which calls `JSON.parse()` when `attributes_json` is non-empty.
### Fix Focus Areas
- src/shared/activity-correlation-reconcile.ts[26-44]
- src/shared/activity-correlation-reconcile.ts[56-70]
### Suggested approach
- Only attempt transcript-path extraction on rows that are likely to carry it (e.g. `kind === "stop"`), **or**
- Add a cheap pre-check before parsing (e.g. `attributes_json.includes('"transcript_path"')`) to avoid `JSON.parse()` when the key can’t be present.
- Keep behavior unchanged for correctness (still learn chat id from prompt rows and/or transcript_path when available).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread src/shared/activity-correlation-reconcile.ts Outdated
Minitour and others added 4 commits August 4, 2026 15:39
Cursor CLI does not emit postToolUse for CallDynamicTool (WebSearch/WebFetch) or Glob; ingest those at preToolUse without duplicating Read/Shell spans.
Show touched files beside activity spans with search, resizable split,
wrap shadow path remapping, and two-way highlight between expanded spans
and file rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use Array.isArray for file-tree selection typing. Only read
transcript_path from stop rows after a cheap string pre-check.

Co-authored-by: Cursor <cursoragent@cursor.com>
Treat strict path prefixes and extensionless search roots as directories
so nested activity paths no longer render with file icons.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Minitour
Minitour merged commit 2c4b9f4 into develop Aug 4, 2026
10 of 11 checks passed
@Minitour
Minitour deleted the fix/cursor-activity-conversation-reconcile branch August 4, 2026 14:08
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.

1 participant