Fix Cursor hook misattribution and add token usage support#1263
Fix Cursor hook misattribution and add token usage support#1263SnowingFox wants to merge 5 commits into
Conversation
1. Add transcript-owner guard in executeAgentHook to prevent Cursor sessions from being claimed by Claude Code when only .claude/settings.json is installed (fixes entireio#1262). 2. Parse token fields (input_tokens, output_tokens, cache_read_tokens, cache_write_tokens) from Cursor's stop hook payload into Event.TokenUsage so Cursor sessions report non-zero token counts. 3. Prefer hook-provided TokenUsage over transcript-based calculation in handleLifecycleTurnEnd, since Cursor's JSONL transcript has no usage data. Co-authored-by: Cursor <cursoragent@cursor.com> Entire-Checkpoint: 2a89e658287e
|
@SnowingFox thanks for your contribution! I had a look and I don't think this works yet. The issue is that the current code would overwrite the tokens again at the end since the same logic kicks in as for every other agent and tries to calculate the token usage from the script - where there isn't any for cursor. I let claude make two tests on top of your branch that proof the token calculation would be overridden but also highlights that there is more work to make sure the we add the right token count to each checkpoint. Since we would always get the whole token count in the hook of the session until then. Let me know if you want to address this otherwise I'm also happy to take over your work. Here is the draft PR with the two tests: #1388 |
Thanks for the detailed feedback and the tests! That makes sense. I'll dig into #1388 and work on fixing the override issue + per-checkpoint delta calculation. Will push updates to this branch. |
|
@Soph thanks again for the detailed review and for putting together #1388. I pushed an update to this PR. The branch now includes the two acceptance tests from #1388:
The fix keeps Cursor hook-provided token usage as a pending checkpoint-scoped delta, uses it during condensation only when transcript-derived usage is absent, and clears it after successful condensation so the next checkpoint does not inherit cumulative totals. I also merged current Verified locally:
Could you please take another look when you have a chance? |
|
Hi @Soph , just a gentle follow-up on this PR. I’ve pushed the update addressing your previous feedback: the branch now includes the two acceptance tests from #1388, fixes the Cursor token usage override/condensation issue, and keeps the token usage scoped per checkpoint. I also rebased/merged the latest main and verified it locally with the integration tests, full test suite, and lint. Could you take another look when you get a chance? No rush, I’d mainly like to know if there are any remaining blockers or changes you’d like me to make. |
close #1262

close #1264
Dispatch — feat/cursor-hook-guard-and-tokens
Summary
This branch fixes Cursor session misattribution when only Claude Code hooks are installed (#1262), and adds token usage reporting for Cursor sessions by parsing the
stophook payload — the only authoritative source, since Cursor's JSONL transcript contains no usage fields.Work Items
Bug Fix: Cross-agent hook forwarding guard (#1262)
shouldSkipForwardedHook()inhook_guard.go— checks ifevent.SessionRef(transcript path) belongs to a different registered agent viaAgentForTranscriptPathexecuteAgentHook()inhook_registry.go, beforeDispatchLifecycleEvent— silently skips with a debug log when a forwarded hook is detected.claude/settings.jsonbut no.cursor/hooks.json; Cursor IDE fires Claude Code hooks; transcript path proves it's a Cursor sessionFeature: Cursor token usage from stop hook
TokenUsage *TokenUsagefield toagent.Event— allows hook parsers to inject authoritative token data that the framework prefers over transcript-based computationstopHookInputRawincursor/types.gowithinput_tokens,output_tokens,cache_read_tokens,cache_write_tokensfieldstokenUsageFromStop()incursor/lifecycle.go— derives fresh input tokens asmax(0, total_input - cache_read - cache_write), matching the story/apps/cli approachhandleLifecycleTurnEndinlifecycle.goto preferevent.TokenUsageoveragent.CalculateTokenUsage()fallbackTests
hook_guard_test.go: transcript belongs to other agent (skip), transcript belongs to self (pass through), fullexecuteAgentHookintegration test confirming no state file createdlifecycle_test.go:TestHandleLifecycleTurnEnd_PrefersEventTokenUsage— verifies hook-provided tokens flow into session statecursor/lifecycle_test.go: token extraction from stop hook, nil when omitted, negative clamp to zero