Skip to content

feat: opt-in usage tracker β€” persistent rate-limit logging to JSONL - #546

Open
tobiasstrebitzer wants to merge 1 commit into
sirmalloc:mainfrom
tobiasstrebitzer:feat/usage-tracker
Open

feat: opt-in usage tracker β€” persistent rate-limit logging to JSONL#546
tobiasstrebitzer wants to merge 1 commit into
sirmalloc:mainfrom
tobiasstrebitzer:feat/usage-tracker

Conversation

@tobiasstrebitzer

Copy link
Copy Markdown

What

An opt-in, off-by-default recorder that appends every distinct rate-limit observation to a JSONL file, so subscription-limit utilization can be analyzed later (e.g. correlated against token usage from Claude Code's session logs).

ccstatusline already fetches this data every 180s across all sessions; this optionally appends it to a log file instead of throwing it away after rendering.

How it works

Two write paths, one log ($XDG_DATA_HOME/ccstatusline/usage-log.jsonl or ~/.local/share/ccstatusline/usage-log.jsonl):

  • stdin β€” on each piped render, the rate_limits object from the status payload is logged when its value changed. Captured from the raw pre-zod parse, so buckets not yet in StatusJSONSchema survive verbatim.
  • api β€” inside fetchUsageData() at the cache-write point, the full /api/oauth/usage response body is logged when it changed. Because only the one process that performs the live fetch reaches that point, N concurrent sessions produce ~1 record per 180s window with no extra coordination.

Plus:

  • Heartbeats (default every 10 min) distinguish "no usage occurred" from "ccstatusline wasn't running".
  • Dedup is value-based per source (sha256 of the canonicalized payload) via a small state file next to the log. No locking; rare duplicate records under concurrency are harmless.
  • Rotation at a configurable size (default 5 MB) to a single .1.jsonl file bounds growth.
  • Failure isolation: every filesystem operation is best-effort swallow-and-continue, same style as the existing usage cache. A read-only disk still renders normally.

Config / UI

New usageTracker settings section (additive, defaulted β€” no settings version bump) and a πŸ“Š Usage Tracker TUI screen: enable toggle, API logging toggle, heartbeat interval, rotation size, and the resolved log path. When API logging would introduce polling for a user with no usage widgets configured, the screen says so explicitly.

Users who only want the free stdin signal can turn API logging off and never cause an API call they weren't already making.

What it does NOT do

  • No analysis, charts, or history UI β€” the JSONL file is the whole feature.
  • No daemons or background processes.
  • No new runtime dependencies.
  • Nothing sensitive is written: percentages, reset timestamps, and a truncated token hash (the same fingerprint the cache already stores) β€” never tokens or credentials.

Testing

  • bun run lint, bun test (1919 tests, including ~50 new ones covering canonicalization/signatures, dedup, heartbeats, rotation, corrupt state, unwritable dirs, and the prefetch gate), bun run build all green.
  • Manually verified: unknown buckets survive verbatim; identical payloads append nothing; read-only data dir renders normally with exit 0; disabled (default) behavior is byte-identical to today and creates no files.

Appends every distinct rate-limit observation (stdin rate_limits and
usage API responses) to a JSONL log so subscription-limit utilization
can be analyzed later. Off by default, configured via a new Usage
Tracker TUI screen.

- payloads are logged verbatim (pre-validation), so unknown buckets
  survive schema drift
- value-based dedup via a small state file; heartbeat records make
  gaps unambiguous; size-based rotation bounds growth
- api records hook the cache-refresh path, so N concurrent sessions
  produce one record per 180s window by construction
- all logging IO is best-effort and can never break rendering
@tobiasstrebitzer
tobiasstrebitzer marked this pull request as ready for review August 6, 2026 02:43
@tobiasstrebitzer

Copy link
Copy Markdown
Author
preview

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