Skip to content

Don't feed a {"ok":false} no-data beat to the rate ring - #151

Open
TB1982 wants to merge 1 commit into
HermannBjorgvin:mainfrom
TB1982:fix/no-data-beat-rate-ring
Open

Don't feed a {"ok":false} no-data beat to the rate ring#151
TB1982 wants to merge 1 commit into
HermannBjorgvin:mainfrom
TB1982:fix/no-data-beat-rate-ring

Conversation

@TB1982

@TB1982 TB1982 commented Aug 8, 2026

Copy link
Copy Markdown

The bug

A {"ok":false} beat makes the device announce that the 5-hour quota came back, at the moment the daemon stops being able to ask about it.

The chain:

  1. claude_usage_daemon.py has no usable token, so it writes {"ok": False} — that key and nothing else.
  2. parse_json fills the rest from defaults, so session_pct arrives as 0.0f from doc["s"] | 0.0f rather than as a reading.
  3. main.cpp handed that to usage_rate_sample() before anything looked at ok.
  4. usage_rate_sample() treats a drop of more than five points as a session refill — it clears the ring and returns true.
  5. So after any reading above 5%, one no-data beat throws away the rate history and reports a reset. The device rings the chime.

ui_update() already guards on ok, with the comment "fall through to idle, keep last numbers" — the intent that these beats carry no readings is established. But the sampler runs at line 375 and ui_update() at 390, so the guard sat downstream of the damage.

Moving the sampler inside the same condition is the whole fix. No behaviour changes for a beat that has real numbers.

test_freeride.py::test_freeride_autherror_emits_no_data_beat already covers the daemon half; the beat is sent exactly as intended. Nothing covered what the firmware did on receipt.

Also: daemon/requirements-dev.txt

Runtime deps are declared per platform and this doesn't touch that — install-mac.sh pins bleak/httpx inline, requirements-windows.txt covers the tray build. But the test extras weren't declared anywhere, so running pytest daemon/tests means discovering bleak, httpx, Pillow and pytest one ModuleNotFoundError at a time. That's how I found them.

pystray is deliberately excluded. tray_windows.py imports it inside main(), and test_windows_tray.py says in its own docstring that the tests exercise the handlers without importing the top-level module — which would want a GUI toolkit on Linux. Adding it would make every contributor pay for that.

Plus a short README section, since a manifest nobody can find is half a fix.

Verification

  • Builds on waveshare_amoled_216.
  • A fresh venv installed from the new manifest alone runs the suite green: 119 passed, 2 skipped. The two skips are the Linux/WSL-only pair that can't run on macOS by their own markers.
  • Not exercised on hardware — reproducing it needs the daemon's no-token path. The chain above is read from the source, and step 4's threshold is usage_rate.cpp's session_pct + 5.0f < ring[latest].pct.

Happy to split the manifest out into its own PR if you'd rather keep the fix by itself.

The daemon writes `{"ok": False}` — that key and nothing else — when it has
no usable token (`claude_usage_daemon.py`, the "No usable token; signalling
no-data to device" branch). `parse_json` fills the rest from defaults, so
`session_pct` arrives as 0.0f, and `main.cpp` handed that straight to
`usage_rate_sample()` before anything looked at `ok`.

`usage_rate_sample()` reads a drop of more than five points as a 5-hour
refill: it clears the ring and returns true. So one no-data beat, after any
reading above 5%, threw away the rate history and reported a session reset
— the device rings the chime to say the quota came back at the moment the
daemon stopped being able to ask about it.

`ui_update()` already guards on `ok`, with the comment "fall through to
idle, keep last numbers", so the intent that these beats carry no readings
is established. But the sampler runs at line 375 and `ui_update` at 390:
the guard sat downstream of the damage. Moving the sampler inside the same
condition is the whole change.

`test_freeride.py::test_freeride_autherror_emits_no_data_beat` already
covers the daemon half — the beat is sent correctly. Nothing covered what
the firmware did with it.

Also adds `daemon/requirements-dev.txt`. The runtime deps are declared per
platform (install-mac.sh pins bleak/httpx inline, requirements-windows.txt
covers the tray), but the test extras were nowhere, so running the suite
means discovering bleak, httpx, Pillow and pytest one ModuleNotFoundError
at a time. pystray is deliberately excluded: tray_windows.py imports it
inside main() and test_windows_tray.py documents that the tests never touch
the top-level module, which would want a GUI toolkit on Linux.

Verified: builds on waveshare_amoled_216. A fresh venv installed from the
new manifest alone runs the suite green — 119 passed, 2 skipped, the two
skips being the Linux/WSL-only pair that can't run on macOS by their own
markers. Not exercised on hardware: reproducing it needs the daemon's
no-token path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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