feat(usage): Claude Code's plan limits, signed-out logins, team seats, and boxes with no projects
TI
10d ago+1525+1525+0123456789012345678901234567890123456789-121-121-012345678901234567890123456789
The Usage plugin, brought up to date with what we've been running since this PR opened, and reviewed again before asking for a review.
What it does
- Plan limits from Claude Code's own
/usage. The Usage screen and its home widget show each Claude login's current session, its week and any per-model week, with their resets.usage.pyasks/usageat most every ten minutes per login, four logins at a time, each for at most 12 seconds.- Flags:
--print --no-session-persistence --strict-mcp-config --settings '{"disableAllHooks":true}', plusBERTH_DRIVER=structuredin its environment. So it leaves no transcript, starts no MCP server and runs no hooks. Without that, Shipyard's own Claude hooks reported an agent starting and stopping at every read: on one box we found about 2,000 suchagent.ready/agent.exitedevents in the journal. - If a read fails, the token counts still show.
- The widget shows each plan window once, and whose it is. A login used on several boxes is merged, even when the boxes print its reset a minute apart. With more than one login, each row names the login.
- Signed-out logins. Claude Code keeps a profile after it loses the login, so each login is checked once per report with
claude auth status(about 0.15s). One that Claude Code no longer holds shows Signed out with Sign in…, and its old limits are dropped. - Team seats say Claude Team (or Enterprise).
- Two plans on one email (a team seat and a personal plan) both show, in the widget and on the Usage screen, told apart by the organisation's id.
- A login's old limits never show as someone else's. A read that finishes with no limits clears them. So does a folder that is now signed in to another account. Only a read that doesn't finish (a timeout or an error exit) keeps the last good figures.
- Boxes with no projects are read too (see below).
An API change to look at: POST /v1/exec without a location
- On a box with no projects yet, it runs in the box user's home folder (
internal/box/orchestrate.go,execDir). That's how the plugin reads such a box. - Once the box has a project, it's a 400 ("name the location to run in"). So no command can start outside a project to skip that project's
before:gates. Thanks to the review for catching that. - An unknown location is still a 404. Tests cover all three cases.
Tests
plugins/usage/box/usage_test.py, run byapp/src/lib/usage-py.test.tsinpnpm testand skipped without python3. It covers:- no hooks, and
BERTH_DRIVER; - a reset read in the box's own zone across a daylight saving change;
- a no-limits read clearing the old figures;
- another account in the same folder;
- Claude and Codex
defaultlogins kept apart, with real Codex limits; - signed-out logins;
- hangs that cost one timeout and leave no process behind.
- no hooks, and
usage-plan.test.ts,usage-summary.test.tsandusage-limits.test.ts: the widget's and the Usage screen's merging and naming, including two plans of one email on two boxes and a box that listed no accounts.- Two reports at once ask each login's
/usageonce (an flock on the cache), and a report too big for exec drops the oldest limits after its sessions. - An orchestrate test for exec without a location.
- Local checks:
- typecheck:plugins, check:titles/type/grid/csp/themes,
pnpm test(653 pass), build,tsc -p e2e, docs-site build. - The Python tests pass with TZ set to the machine's zone, Europe/Berlin and America/New_York.
go test ./internal/boxpasses exceptTestAttachPassesShiftEnter, which fails the same way onmainon macOS.
- typecheck:plugins, check:titles/type/grid/csp/themes,
- All fixtures are synthetic (acme / example.com).
🤖 Generated with Claude Code