trace: the cursor readers crash on a JSON line that is not an object

Open
#180 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@rahul05ranjan is already working on this.

Since Nov 9, 2026.

  • #181 by @rahul05ranjan — open

Assessment

Difficulty
1/5
Estimated time
Under an hour
Newbie friendliness
25/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Stale
Tech stack
python
Domain
backend

Research direction

Start with the spool loop and cursor_events_to_claude in tokeneconomizer/trace/cursor.py, then compare their JSON handling with the existing guarded transcript loop. The issue describes the regression cases and says a regression test is ready; done means both readers skip valid JSON values that are not objects and preserve the other records. A linked pull request, #181, is already open.

Written by the indexing model from the issue text.

Description

What happens

cursor_events_to_claude and cursor_prompt_count (tokeneconomizer/trace/cursor.py) parse each line with json.loads and then call .get on the result, without the isinstance(ev, dict) check. A line that is valid JSON but not an object (null, 42, "text", [1, 2]) has no .get, so both readers raise AttributeError and one such line discards the whole session's trace.

#150 added that check to the 13 readers that missed it. cursor.py's two were not in that sweep — they came with the #149 headless work (the recorded-event spool and the agent_transcript fallback). The opencode, Claude, Codex and Copilot readers all have the check.

Repro

events.jsonl:

null
[1, 2]
{"hook_event_name": "UserPromptSubmit", "prompt": "hi"}
"str"
{"hook_event_name": "PostToolUse", "tool_use_id": "t1", "tool_name": "Shell", "tool_input": {"command": "pytest"}, "tool_response": {"content": "3 passed", "exit_code": 0}}
>>> from tokeneconomizer import trace
>>> trace.cursor_prompt_count("events.jsonl")
AttributeError: 'NoneType' object has no attribute 'get'
>>> trace.cursor_events_to_claude("events.jsonl", "out.jsonl")
AttributeError: 'NoneType' object has no attribute 'get'

Both lose every record in the file, not just the bad line. The prompt-count fallback loop over Cursor's own transcript (second loop) already has the isinstance(row, dict) guard; it is the spool loop and cursor_events_to_claude that miss it.

Impact

Cursor.prompt_count and the mechanism trace conversion abort mid-session. In a hook the fail-open rule turns the exception into {} plus an errors.log line, and the fold keeps the session pending and retries the same crash at the next session start — the same failure shape #150 fixed for the other readers.

Fix

The same one-line guard after json.loads in both places. I have it with a regression test ready and will open the PR.

Dominant language
Python
Stars
5.6k
Forks
344
Avg merge
17h 41m
Merged PRs (30d)
73

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from microsoft/TokenEconomizer

All issues in microsoft/TokenEconomizer

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.