Repository navigation
Conversation
The shelf keyed a session's recency off its file mtime, so a maintenance pass that rewrites a long-dead transcript without adding content put it back on the shelf. Judge recency by the last real event in the transcript instead, and shorten the active window to 5 minutes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011xr9KLS575DHx4NEMUBYpx
Mascots hold a readable size regardless of session count and the row scrolls when they overflow, instead of shrinking to nothing past a few sessions. Size follows window height up to a 250px cap, and the row centres vertically so a tall or maximised window has no dead band below the mascots. Also themes the shelf tooltip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011xr9KLS575DHx4NEMUBYpx
Add a QToolTip rule to the base stylesheet so tooltips follow the active palette and update on theme switch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011xr9KLS575DHx4NEMUBYpx
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011xr9KLS575DHx4NEMUBYpx
The usage API reports limits beyond the 5h and weekly windows, such as a weekly limit on one model (Weekly · Fable). Until now they only appeared on the Stats page. Settings > Display > ADDITIONAL LIMITS now lists every one the account has reported, and a ticked limit gets its own bar in the full, compact and mini views and joins the approaching-limit alerts at the threshold of its group. - scoped_windows.py (new, Qt-free) parses them from limits[], including their reset time and a surface scope as well as a model one, and holds the last-known list so a failed usage request doesn't blank the bars. The usage and profile requests now raise on an error status so that case is distinguishable from "no limits". - One keyed row helper (ScopedRows) serves all three views. Compact and mini settle the rows' layouts before re-fitting, and the full window re-fits the session shelf once its rows change. - A limit crossing 100% reads OVER LIMIT on its bar and "reached 100% of its limit" in the alert, not OVERAGE: whether a model's own limit spills onto paid credits is unverified. - Ticked limits are opt-in and remembered by key; a remembered limit the API has stopped reporting is marked in Settings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With no sessions the empty-state mascot was a fixed 240px, so an additional-limit row it had no room for forced the window taller (572 -> 627px at one row) -- and did so on its own when the last session ended with a limit ticked. It now scales to the height it is given, capped at 240px, the way the session shelf's mascots already fit. A window with room looks exactly as before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Only the usage request raises on an error status. The profile request raising too meant a profile-only failure threw away the spend and scoped windows that usage had returned. - A limit the API sends without a group is labelled by name alone instead of being guessed as "Weekly". - README: the new bars, Settings > Display > Additional limits, and the approaching alert covering them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eekly · Fable) on the dashboard Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every probe failure collapsed into the status string "error", and _apply_status_badge had no branch for it, so it fell through to the else branch and CLEARED the badge. An expired OAuth token therefore rendered as the 5h / 7d bars frozen on their last good values, with the only signal anywhere being a tray tooltip reading "Clawdmeter - error". That is how a token which expired at 02:42 went unnoticed for 34 hours: the session shelf reads transcripts off disk and needs no auth, so the window went on looking alive while the half that needs a token was dead. _poll_once now classifies what failed rather than flattening it: 401/403 is auth-expired, another non-2xx is http-error, and a transport failure is offline. The badge and the tray tooltip render from one table, so they cannot disagree, and the wording splits on what the user should do about it - "Token expired" names an action, "Offline" says to wait. Settings already had this wording on the Connection tab; the main window was the only surface that stayed silent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three faults let an expired token stay expired for 34 hours. The refresh only fired once the token was already dead, so there was always a window where every poll 401'd and recovery depended on the token endpoint answering right at that moment. needs_refresh() now opens a 30-minute window before expiry. It is the same one refresh per token lifetime, just taken while there is still a working token to fall back on. is_expired() keeps its old 120s skew and its old meaning, because Settings asks a different question - is it broken NOW - and widening that would have the panel calling a healthy token expired. Every failure then looked alike. A 429 and a refused refresh token both came back as "refresh failed", so the cooldown doubled to a 15-minute ceiling and kept trying either way. RefreshOutcome splits them: THROTTLED backs off to a four-hour ceiling instead of feeding a rate limiter it may already be behind, REJECTED stops entirely because a dead refresh token cannot be retried into working, and UNREACHABLE keeps the short ceiling since that is just the network. The block self-clears when the credentials file's expiry changes, so signing in again resumes polling with nothing having to notify the poller. And there was no way to sign in again. reauth.py hands off to `claude auth login --claudeai` rather than reimplementing the flow: its authorize endpoint, redirect URI and scopes are documented nowhere we can verify, and this is the worst place in the app to be guessing. It adds no dependency either - the credentials file Clawdmeter reads is Claude Code's, so with no CLI there was never a token to read. The Connection tab gets a Sign in again button, always present and disabled with a reason when the token is healthy, matching the refresh button beside it. On macOS it stays enabled: it is the one remedy that works there, and the note above it already says so. A 401 whose refresh token is dead now reads "Sign in again" rather than "Token expired" - same symptom, different thing for the user to do. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A mutation run caught this: swapping needs_refresh() back for is_expired() in _maybe_auto_refresh left the whole suite green. The new rule was tested in isolation and nothing checked that the poller actually calls it, which is the one place it matters. The control assertion is the point - is_expired() is False at twenty minutes out, so the old behaviour could not have produced this call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The session shelf reads transcripts off disk and needs no token, so the window goes on looking alive while the half that needs one is dead. That is the real reason an expired token went unnoticed for 34 hours: nothing was broken enough to notice from across the room, and the app never said anything. AuthNotifier is edge-triggered, which is the requirement rather than a nicety - at a 30-second poll that stretch would otherwise have been around four thousand notifications. Only auth failures count. A dropped network or a 500 clears itself and is not something to act on, so alerting on those would teach you to ignore the channel. For the same reason recovery has to be a genuine OK sample: an offline poll arriving during an outage must not read as "all better now", and a test covers exactly that false positive. It is deliberately NOT primed on first sight, unlike ApproachingNotifier. Starting silent would reproduce the original failure for anyone who launches the app after the token has already died - the moment it is most worth saying something. Delivery goes through _deliver_alert alongside the reset and approaching alerts, so it honours whatever channels are already configured, including a push-only setup - which is the one that reaches someone who is not looking at the window. The Settings toggle defaults ON, unlike the approaching alert. That one is a judgement call about how close is too close; this one means the dashboard has stopped telling the truth and cannot fix itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A mutation run caught it: dropping no-token from AUTH_FAILURE_STATUSES left the suite green, because the test parametrised over that same set. Removing a status just removed a case. The list is written out by hand now, with a separate assertion that the set matches it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follows the shape of the existing known-issue docs: the timeline it was confirmed against, why the window still looked alive, why nothing renewed the token once the desktop app became the daily driver, and what each part of the fix does. Records two things worth not relearning: claude auth status reports loggedIn true against a token that 401s, because it reads presence and not validity; and the 429 seen from the token endpoint came from probes with a deliberately invalid refresh token, so it is consistent with throttling but does not prove it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two of them were the branch telling the user something untrue about its own behaviour, which is the failure mode the branch exists to remove. The delivery channels vanished for anyone running only the auth alert. _sync_notify_subtoggles counted the reset and approaching toggles but not the new one, and its handler never called the sync at all - so turning the other two off hid the "how" controls while the auth alert went on firing toasts through settings nobody could see any more. Settings still said "wait for auto-refresh" in the state where this branch deliberately switches auto-refresh off for good. The poller owns that state, so it is now handed to the panel per sample and the line names the one remedy that still works. Same defect the macOS branch of that function already guards against, in the branch that has the users. 403 no longer claims the token expired. It is forbidden - a revoked token or an org policy - and asserting an expiry pointed the user at a refresh that cannot help. It reads as "sign in again", which is true under either reading. The blocked-refresh state is its own boolean instead of being inferred from a nullable expiry. A credentials file with no refreshToken - the case that produces a REJECTED in the first place - plausibly has no expiresAt either, so the block silently failed to engage on exactly the input that triggers it. It only avoided becoming a retry loop because needs_refresh independently also returns False on an unknown expiry; two functions had to keep agreeing for that to hold. auth_notify no longer imports poller. Three string constants were dragging PySide6 and httpx into a module whose docstring promises neither, and making it unimportable from poller - where the statuses live and where a future caller belongs. The literals are guarded by the name-match test that already existed, plus a subprocess probe that proves nothing heavy gets pulled in. That probe was checked against poller itself, which reports all three. is_expired takes a blocking flag and the panel passes False. That call sat eleven lines under a comment explaining that a blocking macOS Keychain read there had hung the app before it drew anything, and blocked anyway. reauth drops the --email parameter. No caller ever passed one and the address is unavailable at the moment it would be needed. Two fixes had no test that could fail on them - the handler's call to _sync_notify_subtoggles, and the panel's blocking=False - so both got behavioural tests that drive the real signal and record the real kwarg rather than asserting on source text. Both carry a control assertion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A mutation run caught it: deleting the set_reauth_needed call from _on_sample left the suite green. The wording branch was tested, but nothing proved the panel is ever told which state it is in - so it would have gone on offering to wait for a refresh that had stopped. An AST walk rather than a substring search, so it sees a real call and not a mention in a comment, and survives a reflow. The control test beside it proves the walk can find a call that is definitely there and does not find one that is not - a broken walk and a missing call look identical otherwise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Updates the counts, names all four mutation survivors and what each had in common - a fix nothing could fail on - and records that the badge and the Sign in again button were both exercised against the real expired token rather than only against fixtures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… renew it before it does, and offer a way back in An expired OAuth token stopped the 5h/7d windows updating for 34 hours without the window saying anything. The shelf reads transcripts off disk and needs no token, so half the app went on looking alive while the half that needs one was dead — and the Claude desktop app, unlike the CLI, never writes the credentials file Clawdmeter reads, so nothing renewed it. Four things, one per commit, plus the review fixes: - Every probe failure used to collapse into the status "error", which the badge had no branch for and therefore drew as nothing. Failures now name themselves, and the badge and tray tooltip render from one table. - The refresh only fired once the token was already dead. It now renews 30 minutes ahead — the same one refresh per token lifetime, taken while there is still a working token to fall back on. - A failing refresh retried at four attempts an hour forever. Backoff now keys off the cause: throttled backs off for hours, a refused refresh token stops entirely, and network trouble keeps the short ceiling. - There was no way to sign in again. The Connection tab hands off to `claude auth login` rather than reimplementing an OAuth flow whose endpoints are documented nowhere verifiable. And it says so once when usage goes blind, once when it comes back, through the channels already configured for reset alerts. Verified against the real failure rather than fixtures: the badge rendered against the genuinely expired token, and the sign-in button completed a browser login after which the poller recovered on its own. 691 tests, 29/29 mutants killed over three runs. Write-up in docs/token-expiry-blind-spot-2026-09-20.md. A code review of the branch found seven issues, all fixed in aa7635f. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two platform gaps the Windows work left behind. macOS can never refresh its own token - the credential lives in the login Keychain and writing the rotated one back is not implemented - so _refresh_blocked could never be set there, and every expiry rendered as "Token expired". That is a refresh which is never coming, on the one platform where it is true of EVERY expiry rather than only a rejected one: the same false promise the Settings line was fixed for, still on the badge. It now reads "Sign in again", which is the only thing that does work there, and which the Sign in again button now offers. The condition behind that is one function, auto_refresh_supported(), called by both the "should I even try" gate and the wording decision so the two cannot drift. It replaces the copy of the expression that the poller had inline. CLAUDE_CREDENTIALS_PATH still opts a Mac back into the file path, and therefore back into refreshing, as it always did. The Linux handoff passed argv to xfce4-terminal's -e, which wants a single quoted string - the terminal opens and never runs the command. It uses -x now, which takes the remainder of the command line. The Debian x-terminal-emulator alternative moved from first choice to last resort, since what it actually points at is unknown. Both of those failed the same way: Popen succeeded, so the user was told to "finish in the window that just opened" about a window that had done nothing. _survived() now waits briefly and distinguishes a terminal still in the foreground, one that handed off to a server and exited 0, and one that rejected its arguments and died - and on the last, moves on to the next candidate rather than reporting success. The same check covers osascript on macOS. The AppleScript command is escaped properly too: shlex for the shell inside Terminal, then backslash and quote escaping for the AppleScript literal wrapping it, which a path containing a double quote would otherwise have ended early. None of the macOS or Linux spawn paths has ever run on its own platform. The tests cover the selection logic, the flags, the fall-through and the escaping with fakes; whether a real konsole accepts -e and a real Terminal accepts the script is still unproven and needs the VMs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI covers Linux and Windows; there is no macOS job, so nothing there has been exercised by anything but reasoning and fakes. Names what is inert on macOS and why, and which spawn paths have never run on their own platform. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Minor, not patch. The 3.0.2 sitting on develop was chosen in August for the shelf rework and themed tooltips; two feature sets have landed since - the scoped additional-limit windows, and the token resilience work with its own Settings control, notification type and badge states. No side effect beyond the About dialog, the exe file version, the User-Agent and the update check, which now compares 3.1.0 against the v3.0.1 release and correctly finds nothing newer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Claude desktop app hands Claude Code its own token and never writes ~/.claude/.credentials.json, so the file only stays fresh while the CLI is in use. Clawdmeter now refreshes the way the CLI does: - refresh 5 min before expiry instead of after it lapses - use the current token endpoint (platform.claude.com), falling back to the legacy console host, and send the stored scopes - take Claude Code's .oauth_refresh.lock and re-read the file under it, standing down if the tokens were already rotated, so the two never spend the same refresh token - on a 401 with a valid-looking expiry, re-read / force a refresh and re-poll right away instead of waiting out the expiry - remember refresh tokens the endpoint rejected and stop resending them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsZHk76Z7ym9WENv2U2MMy
Develop's fix/token-expired-badge covers the same desktop-app diagnosis (early refresh, RefreshOutcome backoff, rejected-token block, Sign in again). Keep that structure and layer on what it lacked: the current token endpoint and stored scopes, Claude Code's refresh lock with a BUSY outcome, standing down when the tokens were rotated under us, and an immediate refresh + re-poll on a 401. The per-run dead-token set is dropped in favour of the poller's REJECTED block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsZHk76Z7ym9WENv2U2MMy
Refresh the token like Claude Code does: current endpoint, shared lock, recover on 401
The OAuth token endpoint answers every request whose User-Agent is exactly "anthropic" with HTTP 429, whatever the refresh token. Measured live with a fake token: "anthropic" 429 on both hosts, while "Anthropic", httpx's default and "Clawdmeter/3.1.0" all reach the real token check (400 invalid_grant). Clawdmeter has sent "anthropic" since auto-refresh first shipped, so it read every refresh as THROTTLED and backed off, and the token stayed expired. Send Clawdmeter/<version>, as the update check already does. With the fix the same real credentials refreshed (200) and the usage poll came back allowed. The new test asserts on the header actually sent and fails with the old value restored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The "can't read usage" alert fired once per outage. When that outage began as a plain expiry, its message said Clawdmeter was refreshing the token; if the refresh was then rejected, nothing ever corrected that, so a phone or toast still said help was coming while only signing in could fix it. It now alerts once more, "Clawdmeter needs you to sign in", on that one change, at most once per outage, and re-arms after a recovery. Two of the new tests fail on the previous behaviour; the once-per-outage test catches the escalation repeating. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The checkbox read "Also alert when I cross 100% into overage", but it also switches the additional limits' own "reached 100%" alert on and off. It now reads "Also alert when a limit passes 100%", with a tooltip saying what 100% means for each kind of window, and the README says the additional-limit alert follows that box. Wording only; behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every sample told Settings whether it was a sign-in-again sample, so an offline or API-error poll in the middle of a dead-refresh-token outage cleared the flag, and the Connection tab went back to "wait for auto-refresh" while none was coming. Now sign-in-again sets it, a working poll clears it, and other failures leave it alone. The new offline and API-error cases fail with the previous line restored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every arrow in the app rendered as a solid 8x5 block, on macOS 26 and Windows 11 alike. The stylesheet used the CSS border-triangle trick (zero-size box, three borders), which Qt's stylesheet engine does not support: it fills the whole border box. The arrows are now small PNGs drawn once per colour (plus an @2x copy for high-DPI screens) into a cache folder under the temp directory. The QSS names a direction and a palette role, so they re-theme like everything else. If the folder can't be written the arrow is dropped rather than crashing startup. build_qss(MIDNIGHT_SALMON) is no longer byte-identical to _BASE_QSS, since the arrow placeholders now resolve to paths; the byte-identity guarantee moves to the hex-swap step (_swap_hexes), which is what it always covered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The glow behind the app icon was removed in 80a9745 (2026-07-29) because it clashed with the rounded tile macOS 26 wraps every icon in, but the design doc still described it and still asked for its alignment to be checked. Record the bare slot, why, and the 2026-09-27 check on an Apple Silicon Mac running macOS 26.6.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Code-review finding on the arrow fix. On Linux the temp dir is one /tmp shared by every user: the first user to run the app owned the cache folder, so nobody else could add a colour (their arrows silently vanished); another local user could plant a symlink at the predictable write path; and tmp cleaners could delete arrows mid-session. Windows and macOS were unaffected (per-user temp dirs), but the per-user cache folder (QStandardPaths GenericCacheLocation) fixes all three everywhere and needs no application name, so it resolves the same before QApplication exists. Also corrects test_theme.py's docstring, which still described build_qss() as the byte-identical step and theme.py as Qt-free. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In the compact view the status dot sat ~30px from 'IDLE' (and, on macOS, from every activity name). The row's target label is capped at 170px, so a wide row had slack left over, and with nowhere else to go Qt handed it to the dot and activity labels. A zero-stretch spacer at the end of the line now takes only what the capped target cannot. A stretch-1 spacer would also close the gap, but it splits the slack with the target and elides file names sooner; the test covers both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed compact rows, DMG doc matches the art Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Ubuntu 22.04 "list_issues" read as "list issues". ScrollingLabel was exactly fm.height() tall and drew into its own rect, and DejaVu Sans (Ubuntu's default UI font) draws '_' below its own descent: at 10px it is 11px tall with the baseline at 9, and the underscore sits on row 11. The label is now tall enough for the lowest descender, and draws top-aligned in its full height so that extra row is inside the drawText() rect (which clips). Both halves are needed; the render test fails with either one alone. On Windows none of the sizes the app uses change height (Segoe UI leaves room), so layouts there are unaffected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The "·" between a session's activity and its target never had a colour rule, unlike the dot and activity beside it, so it fell back to the system palette: near-black on a light-mode desktop (seen on Ubuntu), nearly invisible on the dark row. It now uses the theme's muted text colour and follows theme switches with the rest of the compact sheet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Code-review finding on de64d90. The height check measured "_gjpqy", but on macOS's SF the tight box of g/y/p sits a row or two below where they actually draw, so every Mac label grew 1px (10px 12->13, 13px bold 16->17) although the old height already showed them in full - measured by rendering each glyph old vs new. Only '_' genuinely overflows (on DejaVu), so measure just that. Now, at the sizes the app uses, heights are unchanged on Windows and macOS; on Ubuntu the 10px label gains the row the underscore needs, and the 12/13px bold labels gain 1px they did not strictly need (the tight box is conservative) - harmless, Linux only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd the compact separator render on Linux Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Release-review finding. A revoked token (401 whose refresh is then rejected) or a 403 still carries a future expiresAt. The poller correctly reported "sign in again" and the badge and alert sent the user to Settings, but Settings gated its button and status line on the file's expiry alone: Sign in again was disabled as "still valid", and the line read "Valid for ~4h 59m - refreshes automatically". The button now also opens when the poller says sign-in is needed, and that verdict outranks the expiry in the status line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Release-review finding. Every HTTP 400 from the token endpoint was read
as "the refresh token is dead": auto-refresh stopped and the user was
told to sign in again. But the endpoint also answers a request it can't
parse with a 400, under a different body - measured 2026-09-27 against
the live endpoint: a fake refresh token gives
400 {"error": "invalid_grant", ...}, a missing grant_type gives
400 {"type": "error", "error": {"type": "invalid_request_error", ...}}.
After an endpoint change that would have stopped refreshing for every
user and sent them to a sign-in that cannot fix it.
A 400 now counts as REJECTED only with invalid_grant; any other 400 is
ERROR and keeps retrying with backoff. 401 is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Release-review finding. scoped_windows parsed resets_at with a bare datetime.fromisoformat(), which on Python 3.10 - the Linux release build - rejects a trailing 'Z' and a millisecond fraction. Today's API shape (six-digit fraction, +00:00) parses everywhere, but any other form would have read "reset time not reported" on Linux alone. transcript.parse_iso_ts already handled these, but transcript imports Qt and scoped_windows is Qt-free, so the parser moves verbatim into a small isotime module both import; transcript re-exports the name, so existing callers are unchanged. Verified on Python 3.10: the old code fails the 'Z' and '.543Z' cases, the new code passes all three. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Release-review finding. The "When usage can't be read" alert is on by default but was not mentioned anywhere, and the token paragraph promised renewal 30 minutes before expiry without saying macOS never refreshes (the Keychain is read-only to Clawdmeter; Settings already says so). Also says what Sign in again actually does: opens `claude auth login`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Everything on
developsince v3.0.1: 35 commits.APP_VERSIONis3.1.0.New
~/.claude/.credentials.json, so Clawdmeter does. It renews about 30 minutes before expiry, under Claude Code's own refresh lock, and re-reads the file under the lock, standing down if the token has already rotated. It uses the current token endpoint and the stored scopes, with a Clawdmeter User-Agent. The endpoint answersUser-Agent: anthropicwith a 429 every time.claude auth login --claudeaiin a terminal on Windows, macOS and Linux. The new token is picked up on the next poll.Changed
Fixed
Release review (the mandatory gate)
A code review over
main..developfound 1 Medium and 4 Low issues. All are fixed except one accepted Low:dfdc81e, with a regression test.invalid_grantnow counts as a dead refresh token. Measured against the live endpoint: a dead token returns 400invalid_grant, while a malformed request returns 400invalid_request_error, which now retries rather than telling everyone to sign in.src/isotime.py). A barefromisoformatrejectsZon Python 3.10, which the Linux release build uses.Tested
0124ffd): Windows and Ubuntu tests, plus a launch test on Arch, Debian 12, Fedora 40 and Ubuntu 22.04/24.04.3ec0425(identical files to this PR's headd656f54). Each contains the newisotimemodule and older builds don't, and every checksum verifies:Clawdmeter.exe(v3.1.0): click-tested..dmgand.zip: both Apple-chip and Intel code in every file (96/96). Runs natively on an M2 (macOS 26.6.1) and as x86_64 on Intel (macOS 15.7.7). A copy marked as downloaded is rejected by Gatekeeper, as expected for an ad-hoc-signed app; the README's "Open Anyway" steps cover it.🤖 Generated with Claude Code