Repository navigation
feat(usage): Claude Code's plan limits, signed-out logins, team seats, and boxes with no projects - #115
feat(usage): Claude Code's plan limits, signed-out logins, team seats, and boxes with no projects#115tinhthanh wants to merge 3 commits into
Conversation
|
@tinhthanh is attempting to deploy a commit to the seanbrydonio's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
Hey this looks good! Let me know when its not draft!! |
3912f53 to
5b51f3f
Compare
6b01bbb to
cc62cb5
Compare
…gins, team seats, and boxes with no projects Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cc62cb5 to
70c2515
Compare
|
Ready for review. It's brought up to date with what we've been running, and reviewed twice before asking. The description lists what changed. One thing to look at on your side is |
There was a problem hiding this comment.
4 issues found across 18 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="plugins/usage/box/usage_test.py">
<violation number="1" location="plugins/usage/box/usage_test.py:319">
P3: When no stand-in writes its `child` marker, this test skips instead of failing, so the "nothing left running" guarantee is silently unchecked on slow machines. Make the stand-ins start before the timeout, for example by waiting for the marker file with a bounded poll before calling `claude_limits()`, so the check always runs.</violation>
</file>
<file name="plugins/usage/src/summary.ts">
<violation number="1" location="plugins/usage/src/summary.ts:25">
P2: Signed-out warnings discard organization and box identity, so distinct team/personal profiles can collapse into one label. Include the organization or box fallback when deduplicating signed-out logins.</violation>
<violation number="2" location="plugins/usage/src/summary.ts:114">
P2: The home widget distinguishes same-email organizations internally but names them only by account. Same-email team and personal plans can therefore render identical labels and React keys; include the organization in the display identity.</violation>
</file>
<file name="plugins/usage/src/data.ts">
<violation number="1" location="plugins/usage/src/data.ts:194">
P2: The limits filter compares the UTC date of `at` with the box-local day. On boxes east of UTC, limits read in the early local morning fall on the previous UTC date and drop out of the Today view. Compare against the box's local day, e.g. convert `at` with the box's offset as `local_day` does in usage.py.</violation>
</file>
Reply to a comment to ask cubic a question or push back. It learns from your replies.
View guided diff | Re-trigger cubic
| export function signedOutLogins(reports: { report: Report }[]): string[] { | ||
| const out = new Set<string>(); | ||
| for (const { report } of reports) { | ||
| for (const s of report.signed_out ?? []) out.add(s.email ? `${s.account} (${s.email})` : s.account); |
There was a problem hiding this comment.
P2: Signed-out warnings discard organization and box identity, so distinct team/personal profiles can collapse into one label. Include the organization or box fallback when deduplicating signed-out logins.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At plugins/usage/src/summary.ts, line 25:
<comment>Signed-out warnings discard organization and box identity, so distinct team/personal profiles can collapse into one label. Include the organization or box fallback when deduplicating signed-out logins.</comment>
<file context>
@@ -0,0 +1,156 @@
+export function signedOutLogins(reports: { report: Report }[]): string[] {
+ const out = new Set<string>();
+ for (const { report } of reports) {
+ for (const s of report.signed_out ?? []) out.add(s.email ? `${s.account} (${s.email})` : s.account);
+ }
+ return [...out].sort();
</file context>
There was a problem hiding this comment.
Leaving this as is. Both entries would still read the same, because a signed-out login carries no box in the report, so a different key would only print the same text twice. The two-plans case is about logins that are signed in. A signed-out profile is told by its folder and email, which is what the person signs in again with.
| const names = emails.map((e) => e.split("@")[0]); | ||
| logins.forEach((l, i) => { | ||
| let name = names.filter((n) => n === names[i]).length > 1 ? emails[i] : names[i]; | ||
| if (emails.filter((e) => e === emails[i]).length > 1) name = `${names[i]} · ${of(l).account}`; |
There was a problem hiding this comment.
P2: The home widget distinguishes same-email organizations internally but names them only by account. Same-email team and personal plans can therefore render identical labels and React keys; include the organization in the display identity.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At plugins/usage/src/summary.ts, line 114:
<comment>The home widget distinguishes same-email organizations internally but names them only by account. Same-email team and personal plans can therefore render identical labels and React keys; include the organization in the display identity.</comment>
<file context>
@@ -0,0 +1,156 @@
+ const names = emails.map((e) => e.split("@")[0]);
+ logins.forEach((l, i) => {
+ let name = names.filter((n) => n === names[i]).length > 1 ? emails[i] : names[i];
+ if (emails.filter((e) => e === emails[i]).length > 1) name = `${names[i]} · ${of(l).account}`;
+ who.set(`${agent}:${l}`, name);
+ });
</file context>
There was a problem hiding this comment.
Leaving this for the widget. It's a small card with no box to name, and the case needs one email signed in to two plans, both as the default login of different boxes. The Usage screen tells them apart by box (4900d8b). The widget's React key is label@resets, and the two windows' resets differ, so the keys don't collide.
| except ProcessLookupError: | ||
| alive = False | ||
| self.assertFalse(alive, f"{d.name}'s child {pid} is still running") | ||
| if not started: |
There was a problem hiding this comment.
P3: When no stand-in writes its child marker, this test skips instead of failing, so the "nothing left running" guarantee is silently unchecked on slow machines. Make the stand-ins start before the timeout, for example by waiting for the marker file with a bounded poll before calling claude_limits(), so the check always runs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At plugins/usage/box/usage_test.py, line 319:
<comment>When no stand-in writes its `child` marker, this test skips instead of failing, so the "nothing left running" guarantee is silently unchecked on slow machines. Make the stand-ins start before the timeout, for example by waiting for the marker file with a bounded poll before calling `claude_limits()`, so the check always runs.</comment>
<file context>
@@ -0,0 +1,431 @@
+ except ProcessLookupError:
+ alive = False
+ self.assertFalse(alive, f"{d.name}'s child {pid} is still running")
+ if not started:
+ self.skipTest("neither stand-in started its child in time on this machine")
+
</file context>
There was a problem hiding this comment.
Keeping the skip. It only applies when the machine couldn't start the stand-in's child before the timeout; the one-timeout bound is asserted either way. Waiting for the marker before calling would change what's tested: the point is a read cut off while its child runs.
…h no projects, logins told apart by organisation everywhere, one /usage read at a time Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 12 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
…ecks for projects without asking git Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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
/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.--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.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.An API change to look at:
POST /v1/execwithout alocationinternal/box/orchestrate.go,execDir). That's how the plugin reads such a box.before:gates. Thanks to the review for catching that.Tests
plugins/usage/box/usage_test.py, run byapp/src/lib/usage-py.test.tsinpnpm testand skipped without python3. It covers:BERTH_DRIVER;defaultlogins kept apart, with real Codex limits;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./usageonce (an flock on the cache), and a report too big for exec drops the oldest limits after its sessions.pnpm test(653 pass), build,tsc -p e2e, docs-site build.go test ./internal/boxpasses exceptTestAttachPassesShiftEnter, which fails the same way onmainon macOS.🤖 Generated with Claude Code