Claude Code usage in the macOS menu bar: the two-tier redraw that keeps the dropdown from snapping shut

You came here because Claude Code keeps walking your 5-hour window mid-refactor and you want a menu bar chip that tells you when you are about to hit it. This page is about which app to install, what the chip is actually showing, and the one piece of control flow that distinguishes a usable menu bar reader from one that closes its own dropdown every minute.

M
Matthew Diakonov
9 min

Direct answer (verified 2026-04-30)

ClaudeMeter is the macOS menu bar app for Claude Code usage.

Free, open-source (MIT), source at github.com/m13v/claude-meter. Install with brew install --cask m13v/tap/claude-meter. Reads the same server quota Anthropic enforces for 429s, not the local ~/.claude/projects JSONL token estimate ccusage reads. Renders 5h percent and 7d percent in the menu bar, color-coded at 90 and 100 percent. Polls once per minute via the browser extension, no manual cookie paste, no API key.

Why the menu bar (and not a terminal status line)

ccusage --watch and the other Claude Code usage monitors live in your terminal. That is the wrong surface for an agentic loop you are not actively babysitting. You are in your editor, you are reviewing a diff, you tabbed to your browser to read a doc, your tmux session is in another Space. The terminal pane you started ccusage in is three keystrokes and a context switch away.

The macOS menu bar sits above every Space, every full-screen app, every workspace. The chip is in your peripheral vision while Claude Code is mid-edit. The format is tight enough to read without looking directly at it: two two-digit percentages with a separator. When 5h crosses 90, the bg flips to orange and your peripheral vision notices. When it crosses 100, it flips to red and you have already 429'd.

You can have both. The same data is available via claude-meter --json on the CLI (the brew cask installs the binary inside the app bundle), so a Starship prompt or tmux status line can render the same number alongside the menu bar.

What the chip is actually showing

Two percentages: 5h and 7d. Each one is a separate NSAttributedString segment so its background can be colored independently from the rest of the title. The painter is one function:

src/bin/menubar.rs

Three thresholds, three states. Below 90 percent the percent renders as plain text and the menu bar stays quiet during ambient Claude Code work. At 90 percent and up the segment paints RGB (219, 118, 32), an orange that contrasts against both light and dark menu bars. At 100 percent it paints RGB (215, 58, 73), a saturated red that is unmistakable in peripheral vision. A 429 from Anthropic happens at exactly that last threshold; the red flash and the rate-limit message arrive together.

The dropdown's 'Menu bar style' submenu picks one of three layouts: Long ("Claude 5h 47% · 7d 62%"), Medium ("5h 47% · 7d 62%"), or Compact ("47 · 62"). On a 13" MacBook with a packed menu bar, Compact is the format that fits without truncation.

The redraw branch nobody else documents

Here is the part that matters when you are watching a long Claude Code run. You hit 88 percent. You click the menu bar to see the resets_at countdown. The submenu opens. Sixty seconds later, the poll fires.

A naive menu bar app calls tray.set_menu(...) on every poll to refresh the percentages in the submenu rows. AppKit treats that as a tear-down: the open dropdown gets dismissed out from under you, mid-read. ClaudeMeter does not do that. It splits the redraw into two tiers:

src/bin/menubar.rs

Two equality checks decide the path. snaps_equal (lines 279-288) compares utilization fingerprints to detect numeric drift. account_set_changed (lines 294-310) compares the (browser, account_email, stale) tuple set to detect a structural change. Numbers tick on every poll. Account sets change when you sign in to a new claude.ai account, when an account's session expires (stale flip), or when you click "Forget this account" from the dropdown.

If only the numbers changed, the menu does not get rebuilt at all. The title gets a fresh paint via apply_title, which renders new segments through setAttributedTitle on the existing NSStatusBarButton. The dropdown stays open. You read the new percent.

The comment on lines 137-138 names the contract: "Mid-flight percentage updates reach the user on their next click via title + re-render." The user sees the new number in the title without ever losing the dropdown.

The fingerprint cache one tier deeper

Even within the cheap title-repaint path, ClaudeMeter avoids the AppKit work when the rendered output would be identical. A hash of the segment list is held in a thread-local cell; the next render compares fingerprints first.

src/bin/menubar.rs (macos_title module)

A poll where 5h goes from 47.2 to 47.4 percent rounds to 47 in both cases. The fingerprint matches. setAttributedTitle never gets called. The status item paints zero pixels. This sounds like trivial micro-optimization, but it is the difference between a menu bar app that lets your mouse hover stably on a chip and one that re-renders behind the cursor every minute.

A Claude Code run, from the menu bar

Here is the same loop watched through the chip. The menu bar paints in a tight loop; the dropdown is the user's checkpoint:

claude code agentic run, watched from the menu bar

OS-truth browser identification (no header trust)

When you have Chrome and Arc both open against different claude.ai accounts, the menu bar has to label which row came from which browser. The naive way is to read Sec-Ch-Ua off the POST headers and trust them. That fails on Arc, which identifies as Chromium in many headers and would mislabel rows.

ClaudeMeter asks the OS instead. It reads the peer TCP socket's port, calls lsof -nP -iTCP:<port> -sTCP:ESTABLISHED to find the connected process, then ps -o command= to get its executable path. Pattern matching on path substrings does the rest:

src/bin/menubar.rs

The Sec-Ch-Ua fallback at detect_browser_from_headers (lines 505-536) only fires if the OS lookup failed (sandboxed environments, lsof unavailable, etc.). In practice it is the second-choice path; the OS owns the authoritative answer.

ccusage vs ClaudeMeter, side by side

They answer different questions. ccusage answers "how many tokens did my Claude Code session weigh?" ClaudeMeter answers "how close am I to a 429?" You want both, and they do not overlap.

FeatureccusageClaudeMeter
Source of truthLocal ~/.claude/projects/<project>/<session>.jsonl token totalsServer-side /api/organizations/{org}/usage utilization (the same number Anthropic checks for 429)
Browser-chat usage includedNo (JSONL only sees Claude Code traffic)Yes (server bucket counts it)
Per-attachment cost includedNoYes (server bucket folds it in)
Peak-hour multiplierNoYes
Where you read itTerminal pane (ccusage --watch), or one-shot ccusage tablemacOS menu bar (always visible), CLI, browser toolbar popup
Open-sourceMIT, TypeScriptMIT, Rust + JavaScript
CostFreeFree

The CLI, for status lines

The brew cask installs a CLI binary at /Applications/ClaudeMeter.app/Contents/MacOS/claude-meter. With --json it prints the parsed UsageSnapshot for piping into Starship, tmux, fish, or your own dashboard. With no flag it prints a human block via format_window (src/format.rs lines 75-98).

status line wiring

Same numbers as the menu bar, machine-readable. The localhost bridge at curl http://127.0.0.1:63762/snapshots returns the same payload without spawning the binary, which is the path to use if you are polling from a status line every couple of seconds.

Install in four steps

1

1. brew install the menu bar app

brew install --cask m13v/tap/claude-meter. Drops ClaudeMeter.app under /Applications and registers a launch agent so the icon comes back after reboot. The CLI binary lands at the same path inside the app bundle.

2

2. load the unpacked extension

git clone github.com/m13v/claude-meter, open chrome://extensions (or arc://extensions, brave://extensions, edge://extensions), enable Developer mode in the top right, click Load unpacked, point it at the extension/ folder. The icon pins next to the URL bar.

3

3. visit claude.ai once

If you are not already logged in, sign in. The extension calls /api/organizations/{org}/usage with credentials: 'include', so your existing claude.ai cookie travels automatically. No paste, no API key, no second login.

4

4. start your next Claude Code run

Within sixty seconds the menu bar lights up. The title shows '5h X% · 7d Y%' for one account or 'M: 5h X% · 7d Y% P: 5h X% · 7d Y%' for two. Click for the per-window dropdown, including resets_at countdowns and the extra-usage credit balance for metered billing.

Invariants the chip holds to

What the chip guarantees

  • The chip is two numbers: 5h percent and 7d percent. Three layouts in the dropdown's 'Menu bar style' submenu pick how compact (Long, Medium, Compact). The Compact format renders just '47 · 62' for users on small screens.
  • The chip background flashes orange (RGB 219, 118, 32) at >=90 percent and red (RGB 215, 58, 73) at >=100 percent. Plain text below 90 keeps the menu bar quiet during ambient runs.
  • The dropdown only rebuilds when the account set changes (new email, stale flip, forget action). Mid-flight percentage updates repaint the title only. Open dropdowns do not snap shut between polls.
  • Multiple accounts compress into 'M: 5h X% · 7d Y% P: 5h X% · 7d Y%' using the first letter of each email. The dropdown still shows full emails per submenu, browser-tagged.
  • If the browser extension is alive (any POST in the last 120 seconds), the menu bar app skips the cookie-decrypt fetch path entirely. One HTTPS request per minute either way; the extension just owns it when it is awake.

Hitting the 5-hour wall mid-refactor?

Twenty minutes to walk through the menu bar setup, server-truth quota, and how to wire the CLI into your Starship or tmux status line.

Frequently asked questions

What is the macOS menu bar app for Claude Code usage?

ClaudeMeter. It is free, open-source (MIT), and lives at github.com/m13v/claude-meter. Install with brew install --cask m13v/tap/claude-meter, load the unpacked browser extension from the same repo, and the menu bar icon lights up within sixty seconds with two numbers: 5h percent and 7d percent. The numbers are pulled from /api/organizations/{org_uuid}/usage on claude.ai, the same endpoint claude.ai/settings/usage renders. It is the same server quota Anthropic actually checks before throwing a 429 on your next Claude Code prompt.

Why a menu bar, not a terminal status line, for Claude Code usage?

Because the menu bar is in the same field of view as your editor and terminal, but does not steal a row of your terminal. ccusage --watch and similar tools render a live updating row inside a terminal pane, which is fine until you tab to a different pane or your tmux split changes layout. The macOS menu bar sits above every Space, every full-screen app, and every workspace; the chip is in your peripheral vision while Claude Code is mid-edit. If you also want a status line, the same data is available via claude-meter --json on the CLI; you can wire it into Starship, tmux, or fish without losing the menu bar version.

Will the dropdown snap shut every time the percent updates?

No, and that is the part most home-built menu bar apps get wrong. ClaudeMeter splits the redraw into two tiers. The poll fires once a minute. The title repaint is cheap and runs on every numeric change. The menu rebuild is expensive (it tears down and re-attaches the submenu tree, which dismisses an open dropdown) and only runs when the account set changes: a new email logged in, an account went stale, an account got forgotten. The branch lives at src/bin/menubar.rs lines 137-146. The comment on the same branch names the reason: 'Mid-flight percentage updates reach the user on their next click via title + re-render.' If you keep the dropdown open during a long Claude Code run, the percent in the title still ticks; the dropdown stays put.

How is this different from ccusage and Claude-Code-Usage-Monitor?

ccusage and Claude-Code-Usage-Monitor read your local ~/.claude/projects JSONL files and total tokens per session. That is a faithful local-log signal for Claude Code traffic only. They cannot see the per-model weights, the peak-hour multiplier, the per-attachment cost, or any browser-chat usage Anthropic stacks into the same five_hour and seven_day buckets. ccusage at 5 percent next to claude.ai at 90 percent is a normal, predictable mismatch. ClaudeMeter shows the second number, the one Anthropic uses to decide whether to 429 your next prompt. ClaudeMeter does not replace ccusage; ccusage tells you what your Claude Code traffic weighed in tokens, ClaudeMeter tells you what fraction of the cap Anthropic counted that against. Both repos are linked at the bottom of this page.

What does the menu bar chip actually show?

Two percentages: 5h and 7d. Three formats configurable from the dropdown's 'Menu bar style' submenu (TitleFormat enum at src/bin/menubar.rs lines 22-31). Long is 'Claude 5h 47% · 7d 62%'. Medium is '5h 47% · 7d 62%'. Compact is '47 · 62'. Each percent is its own NSAttributedString segment so the background can be colored independently: bg_for at lines 942-950 paints RGB (215, 58, 73), saturated red, at >=100 percent; RGB (219, 118, 32), orange, at >=90 percent; no background below. With the dropdown closed, you read both numbers in your peripheral vision, and the orange flash at 90 is your tap on the shoulder.

Does it identify which browser my Claude Code session is using?

Yes, and not by trusting a header. The menu bar app receives POSTs from the browser extension on 127.0.0.1:63762. To label which Chromium browser sent the snapshot, it asks the OS who owns the peer TCP socket: lsof -nP -iTCP:<port> -sTCP:ESTABLISHED returns the connected process, ps -o command= returns its executable path, and classify_browser_exe at src/bin/menubar.rs lines 464-477 maps '/google chrome.app/' to 'Chrome', '/arc.app/' to 'Arc', '/brave browser.app/' to 'Brave', and '/microsoft edge.app/' to 'Edge'. Sec-Ch-Ua headers are a fallback only, because Arc identifies as Chromium in many of those headers and would mislabel rows.

What about multiple Claude accounts logged into different browsers?

Each (browser, account_email) pair becomes its own snapshot. The menu bar then compresses them into one title. With one account, you see '5h 47% · 7d 62%'. With two, you see 'M: 5h 47% · 7d 62% P: 5h 30% · 7d 12%' where M and P are the first letter of each account email (account_tag at lines 952-958). The dropdown shows one submenu per account, labeled with the email and the browser. Mergewith_persisted at lines 840-895 keys snapshots by (browser, account) so a POST from Chrome does not disturb your Arc rows, and a stale row stays visible (with a 'stale' marker) for two hours after the last fetch.

Does it run as a regular app with a Dock icon?

No. set_macos_accessory at src/bin/menubar.rs lines 1116-1126 calls NSApplication::sharedApplication(mtm).setActivationPolicy(NSApplicationActivationPolicy::Accessory) before anything else. Accessory means the process has no Dock icon, no app menu, and no Cmd-Tab presence. The status item in the menu bar is the entire user surface. If you want to quit, the dropdown has a Quit row. If you want to launch it on login, the brew cask installs a launch agent so it comes back after reboot.

How often does it poll the usage endpoint?

Every 60 seconds, but with a backoff. POLL_INTERVAL at line 18 is sixty seconds. BRIDGE_FRESHNESS at line 350 is 120 seconds. If the browser extension has POSTed a snapshot to the localhost bridge in the last 120 seconds, the poll loop skips the cookie-decrypt fetch entirely (lines 331-342). So in the common case, where you have the extension loaded and a browser open, the menu bar app does zero direct claude.ai requests; it just receives extension snapshots. If you close the browser, the cookie-decrypt path takes over after 120 seconds of bridge silence. One HTTPS request per minute either way.

Can I read the same data from a Claude Code shell command?

Yes. The brew cask installs a CLI binary at /Applications/ClaudeMeter.app/Contents/MacOS/claude-meter. Run it with no flags for a human-readable block (format_window in src/format.rs lines 75-98 renders each window as 'X.X% used -> resets <local clock> (in Nh)'). Run with --json for the parsed UsageSnapshot as JSON. The localhost bridge is also a read endpoint: curl http://127.0.0.1:63762/snapshots returns whatever the menu bar last received. That is the hook for embedding the same number into a Starship prompt or tmux status line, alongside the menu bar.

What if I am on Linux or Windows?

Not supported yet. The menu bar app is macOS-only because the AppKit code path that paints the colored title segments (the macos_title module at lines 1128-1283 of src/bin/menubar.rs) is locked behind cfg(target_os = 'macos') and there is no cross-platform analogue for setAttributedTitle on a status item. The browser extension itself runs anywhere Chromium runs, and exposes the same snapshot at chrome.action.setBadgeText, but the tray-icon Linux/Windows fallback in tray-icon does not support per-segment background colors. Until that lands upstream, the macOS-only experience is what ships.

Is the cookie path safe?

It reads what your browser is already storing, decrypts it with the key your browser already gave macOS, and never leaves the loopback interface. Specifically: Chrome's Safe Storage entry has an ACL that already trusts /usr/bin/security, so calling /usr/bin/security find-generic-password -wa Chrome -s 'Chrome Safe Storage' returns the AES key without a prompt. Arc, Brave, and Edge prompt once on first run because their ACLs do not list the security binary; click Always Allow once and the path runs silently afterward. The decrypted cookie is held in memory just long enough to fetch /api/organizations/{org_uuid}/usage and gets dropped. No telemetry, no analytics, no network egress beyond claude.ai itself. The bridge listens only on 127.0.0.1; the browser extension and the menu bar app are the only two processes that touch it.