Claude: opt-in statusLine usage feed - #2769
Conversation
|
Codex review: needs real behavior proof before merge. Reviewed August 12, 2026, 12:22 PM ET / 16:22 UTC. ClawSweeper reviewWhat this changesThe PR adds an off-by-default local Claude Code statusLine feed that supplies recent usage windows during automatic Claude refreshes. Merge readiness⛔ Blocked until real behavior proof is added - 7 items remain The patch is not ready to merge: an uncomposable statusLine result can stop the existing Claude fallback chain before CLI runs. The capability is not on current main or in the latest release. Priority: P1 Review scores
Verification
How this fits togetherCodexBar’s Claude provider tries ordered usage sources and publishes the first successful result to the menu card. This change inserts a local statusLine source after OAuth and before CLI, then composes its partial data with stored Claude usage. flowchart LR
A[Claude Code statusLine] --> B[Local observation file]
B --> C[Claude source plan]
C --> D[OAuth attempt]
D --> E[statusLine attempt]
E --> F[Snapshot composition check]
F --> G[Menu bar usage card]
F --> H[CLI fallback]
Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Root-cause clusterRelationship: Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Make an uncomposable statusLine observation fall through to normal sources, add a pipeline-level regression, and validate the accepted path with redacted release-signed evidence. Do we have a high-confidence way to reproduce the issue? Yes. If OAuth fails, a fresh statusLine file succeeds, and no owned prior Claude snapshot exists, the pipeline accepts the feed before composition rejects it and does not try CLI. Is this the best way to solve the issue? No. Composition eligibility must be resolved before the feed becomes the pipeline’s terminal success, so normal fallback remains available. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against c4ed34d0e44a. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (17 earlier review cycles; latest 8 shown)
|
|
Both blockers confirmed against the source — thanks, these were real. 1. "The opt-in cannot currently be enabled" — fixed in You were right, and it was worse than a missing control: My 18 tests all passed because they construct 2. "A successful partial feed would replace rather than preserve existing Claude fields" — confirmed, not fixed. Also correct, and it's the more serious one because it breaks the ruling's core constraint. I haven't fixed it because I don't think it can be fixed correctly inside the strategy: So this stays a draft, and I'd rather not invent a parallel merge path next to #2675's. Two ways forward, and I'd take the maintainer's steer:
Noted your recommendation on installation — explicit user-initiated install that never overwrites a user-owned slot, reusing the shared source-label path once it exists. That matches what I proposed on #2733 and I'll implement it that way once the owner confirms; the PR ships docs and a hand-installable shim in the meantime rather than writing anything under On the proof ask: a card capture needs the feature enabled end to end, which blocker 2 gates. I'd rather post that once the merge behaviour is settled than show a screenshot of a card that is silently dropping the user's plan and identity. |
|
Went through all four findings. One fixed, two confirmed and deferred with reasons, and I want to be explicit about which is which rather than let a green suite imply more than it should. [P2] Reject observations without a valid capture time — fixed in Correct and worse than it looks. The parser defaulted a missing Worth flagging why the suite never caught it: my own test asserted the fallback as intended behaviour ( [P1] Merge statusLine windows with the existing Claude snapshot — confirmed, not fixed. Agreed, and it's the blocker that matters. It can't be fixed inside the strategy — [P1] Bind observations to the active Claude account — confirmed, not fixed. Also right, and I under-specified it: matching The blocker is placement, not intent: On the proof ask — I'm not posting a card capture yet, deliberately. Producing one requires the feed enabled end to end, and while the merge finding stands that card would be silently dropping the user's identity, plan and extra-usage rows. A screenshot of that would be misleading evidence, not proof. Current state: 793 tests, |
|
[P2] Cap acceptable future capture timestamps — fixed. Right, and it's the mirror image of the capture-time bug from the last round. I allowed future timestamps without bound to tolerate clock skew, so an observation dated arbitrarily far ahead stayed fresh forever — exactly the defect I had just fixed, in the opposite direction. Skew is now tolerated to five minutes and rejected beyond it, with both the tolerated and rejected case covered. Same root cause as last time, and worth naming: my test asserted the unbounded behaviour as intended ( [P1] Merge the partial feed with the verified Claude snapshot — still deferred. Both confirmed, neither guessed at. Reasons unchanged: the merge needs a partial-result seam in Independent evidence that the account-binding finding is worth solving in shared plumbing rather than inside this feature: on current release 0.48.0, with this PR's feed not enabled and not installed, a two-account setup already renders mixed data — identity from an expired OAuth cache belonging to account A, usage numbers from a CLI probe of the active account B, on the same card. Verified against On proof: still holding. A card capture requires the feed enabled end to end, and while the merge finding stands that card would be dropping the user's identity, plan and extra-usage rows. I would rather post proof of correct behaviour than a screenshot of a known defect. Draft stays parked on the owner's slot-ownership ruling and on #2675. |
8a92b80 to
607b59c
Compare
|
Rebased onto What changed since the last review[P1] Merge the partial feed with the verified Claude snapshot — fixed. The feed's windows now compose over the last polled Claude snapshot instead of being published whole, so identity, plan, model-scoped weekly, Daily Routines, extra usage and cost survive. A window the observation omits means "no update" rather than "cleared". It follows the shape already in that apply path, which reconciles results against previous state for Codex, CommandCode, DeepSeek and reset backfill. The helper lives in its own file and the call site replaces an existing binding, so [P1] Bind observations to the active Claude account — fixed. An observation is discarded outright when the active account changed since the snapshot it would compose over. This is exactly why #2675 was the right thing to wait for: the merge has to happen in the app target, which is also the only place One interaction worth flaggingA composed snapshot inherits Verification
Still openOnly the installer question from #2733: whether an explicit user-initiated "Install statusLine helper" may write a CodexBar-marked slot in Until then this remains consumer-only, with |
|
Taking this out of draft. Both P1 findings are fixed on What's done
On the runtime-proof request — I tried again and still cannot produce it from a fork, and I'd rather say so than post something weaker. I built and packaged 0.49.0 from this branch, enabled the feed, and wrote a valid drop observation. The app launches and reports What that means concretely: the enabled-feed card capture ClawSweeper is asking for needs a build signed with the release identity. One refresh cycle on such a build would settle it. Everything up to that point is covered by tests that I've verified fail without their fixes. Still open, and the reason this may not be mergeable yet The installer question from #2733: whether an explicit user-initiated "Install statusLine helper" may write a CodexBar-marked slot in Without it this is consumer-only: |
|
🦞👀 Pull request received. I will update this pull request when review starts. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 607b59cd61
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
All three findings were real and are fixed in [P2] Persist the statusLine opt-in toggle — fixed. Exactly right: the setter only touched [P2] Keep weekly-only data in the weekly lane — fixed. Also right, and the reasoning is the part I had missed: promotion is correct for a standalone snapshot, which is where I copied it from, but this snapshot is composed over a previous one where [P1] Verify account ownership before composing — fixed. The sharpest of the three. The account behind each polled snapshot is now recorded, and the feed may only compose when that account is still the active one. A snapshot the feed itself produced never becomes that evidence, so ownership cannot bootstrap from an unattributed row. Unknown on either side is treated as not-owned rather than assumed equal. Worth noting this is not hypothetical: the same class of mismatch is visible on stock 0.49.0 with none of this code — a two-account setup renders identity from an expired OAuth cache for account A beside usage from a CLI probe of active account B. This branch does not fix that, but it makes sure the feed cannot add to it.
|
|
@steipete — this is ready for a decision rather than a review, and it's a one-liner either way. Where it stands: implements your ruling from #2733 — off by default, labelled as the user's own statusLine config, fail-soft on drift, composes with rather than replaces OAuth/CLI. Rebased on 0.49.0. Codex's three findings are closed, ClawSweeper reports no actionable findings at patch quality 4/6, CI is green. The one open question is scope, not code: may an explicit, user-initiated "Install statusLine helper" write a CodexBar-marked slot in It matters because without it this is consumer-only: the docs describe the envelope and a working shim, but realistically almost nobody hand-installs one, so the feature ships inert. Three answers all work for me:
One caveat I won't paper over: the remaining No rush on my side — I'd just rather you close it than have it sit if the answer is no. |
|
The P1 is real and it was mine. Fixed in [P1] Preserve the statusLine opt-in on launch — fixedConfirmed exactly as described. The line came from my own commit if self.claudeUsageDataSource != .cli {
...
self.defaultsState.claudeWebExtrasEnabledRaw = false
self.defaultsState.claudeStatusLineFeedEnabledRaw = false // ← mine
}I pattern-matched the adjacent web-extras reset without checking that the two conditions are complements. The planner emits the The fix is the deletion. The reset is CLI-scoped by design and the statusLine line never belonged in it. On the missing regression test. The reset sits in the So I added Separately: this branch was failing the architecture gatekeeperWhile running the full suite I found The allowlist pins each guarded construct to an exact line with a text anchor. Earlier commits here inserted lines into
Worth flagging for its own sake: nothing caught this. Not ClawSweeper, not the Codex reviewer, and not CI, which runs only GitGuardian on this PR. It would have landed on main as a red architecture test. State on
|
Implements the owner ruling on steipete#2733: Claude Code publishes rate_limits to whatever statusLine command the user configured, and reading what it publishes does not cross the credential-ownership boundary the Keychain rules protect. Off by default. When enabled, a .statusline step joins the app Auto order behind .oauth and ahead of .cli, so it fills the gap while the polled sources cool down and never pre-empts a successful OAuth read. When disabled the plan is byte-identical to today: the step is inserted, not present-but-unavailable, because planner order is asserted directly by tests and shown in debug output. The source is never user-selectable. Pinning a feed that goes silent whenever the user is not running Claude Code would strand the card, so it is excluded from the picker and persists as Auto if it ever reaches settings. Fail soft throughout: bad JSON, an unknown envelope version, wrong types, an out-of-range percentage or a drifted schema all read as absence, which leaves the polled sources in place rather than surfacing an error. The status line is the user's own configuration and Claude Code owns the payload schema. Observations are matched to the reporting profile by CLAUDE_CONFIG_DIR and bounded to 15 minutes, so one account's numbers cannot render under another's card and a stale live value cannot outrank a fresh poll. The snapshot asserts no identity or plan, and the card labels the numbers as coming from the user's own statusLine config.
Describes the envelope, a minimal shim, and the profile/freshness rules, so the feed is usable by hand while the managed-install question is open.
The opt-in flag was read by the planner but nothing ever populated it: there was no SettingsStore property, no defaults key, and no UI control, so it was false in every build and the feed could not be switched on at all. Wires it end to end — stored default (off), settings snapshot, menu observation, and a Claude provider toggle — and adds a test that asserts the user-facing switch reaches the snapshot field the planner reads. The existing planner tests could not catch this: they construct ClaudeSourcePlanningInput directly, so they pass whether or not anything carries the user's choice into it. Removing the snapshot line again fails the new test with the exact symptom that shipped.
The parser defaulted a missing capturedAt to the parse time, so a drop file that had sat on disk for hours read as current and the staleness bound stopped protecting anything — a dead session could outrank a live poll indefinitely. An observation that cannot be aged is now absence. The previous test asserted the fallback as intended behaviour, which is why the suite stayed green; it now asserts the drop instead.
Future timestamps were accepted without bound to tolerate clock skew, so an observation dated arbitrarily far ahead stayed fresh forever — the same defect as a missing capture time, in the opposite direction. Skew is now tolerated up to five minutes and rejected beyond it. The previous test asserted the unbounded behaviour as intended, so the suite stayed green; it now covers both the tolerated and the rejected case.
Addresses both P1 findings on the draft, now that steipete#2675 has landed. The feed carries only the 5h/7d windows. Publishing its result whole blanked identity, plan, model-scoped weekly, Daily Routines, extra usage and cost — the opposite of the "composes with, never replaces" constraint in the owner ruling. Its windows are now merged over the last polled Claude snapshot, and a window the observation omits means "no update" rather than "cleared". Composition also carries the account guard the feed cannot supply itself. Matching CLAUDE_CONFIG_DIR cannot distinguish an account switch within one profile, so an observation is discarded outright when the active account has changed since the snapshot it would compose over. Publishing it there would render one account's windows beneath another account's identity. Follows the existing shape of this apply path, which already reconciles results against previous state for Codex, CommandCode, DeepSeek and reset backfill. The helper lives in its own file and the call site replaces an existing binding, so the refresh function stays within its length budget. The card note keeps precedence over the dataConfidence note added by steipete#2675: a composed snapshot inherits its confidence from the previous poll, so a prior CLI scrape would otherwise label live statusLine windows as CLI-sourced.
Persist the opt-in toggle. The setter only updated the in-memory defaults state, so the feed silently switched itself off on the next launch. It now writes the key and notifies the background-work path, matching the adjacent provider toggles. Stop promoting a weekly-only observation into the session lane. This snapshot is composed over a previous one where primary is the session row, so a promoted 7-day figure rendered as the 5-hour limit while the real weekly row survived beside it. A weekly-only payload is now absence rather than a lane-shifted guess. Verify ownership of the rows being composed over. The active-account observation only proves the account held still during a fetch; if the user switches accounts and the next refresh is served by the statusLine file, nothing in that fetch re-read the account, so stability alone would let the new account's windows sit beneath the old account's identity. The account behind each polled snapshot is now recorded and must still be active for the feed to compose over it, and a snapshot the feed itself produced never becomes that evidence.
The launch reset that clears Claude web extras outside CLI mode was also clearing the statusLine feed. The planner emits the feed's step only under `.auto`, which is exactly the branch that reset covers, so an enabled user lost the opt-in on every launch and the feature was unreachable after a restart. The reset is CLI-scoped by design; the statusLine line never belonged in it. No test caught this because the reset lives in the non-test branch of an `isRunningTests` gate, so the suite never executed it. Adding `writesLaunchResetsToRawState` — mirroring the existing `performInitialProviderDetection` seam — lets a test take the production path. Verified by reintroducing the defect and watching the new test fail on both assertions before it passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`cross provider case clusters are derived or specifically justified` passes on main and failed on this branch. The allowlist pins each guarded construct to an exact line with a text anchor, and earlier commits here inserted lines into UsageStore+Refresh.swift, UsageStore.swift and MenuCardView+ModelHelpers.swift, so 44 entries drifted off their anchors and every orphaned entry then cascaded into an unjustified-construct failure. - Recompute the 44 drifted line numbers against their anchors. - Justify the two Claude constructs in the composition file, which were added with no marker at all. The first reads better hoisted into a named local than as a comment buried in an argument list. - Update the MenuCardView cluster fingerprint: the statusLine note sits between the .kiro and .kilo checks, so the gatekeeper now sees one three-provider cluster where the entry expected two. The maintainer's reason text still describes it accurately and is left alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2208226 to
6267a29
Compare
|
Rebased onto current Only Green on
The two questions from my previous comment are unchanged: the release-signed runtime proof I can't produce from a fork, and the installer-scope decision. |
Implements the owner ruling on #2733. Draft, because two questions I asked on that issue are still open and both change the final shape — details at the bottom.
What it does
Claude Code passes a JSON object on stdin to whatever
statusLinecommand the user configured, and for Claude.ai-subscriber sessions it carriesrate_limitswith the 5h/7d windows. Those ride along with responses the session already made, so reading them costs nothing against the OAuth endpoint's budget.Per the ruling, all four constraints:
statusLineFeedEnableddefaults false. When off, the planner output is byte-identical to today — the step is inserted, not present-but-unavailable, because planner order is asserted directly by tests and shown in debug output.sourceLabel: "statusline"reaches the card, which notes "From your Claude statusLine config" (same seam as Kilo's "Using CLI fallback")..statuslinestep joins the app Auto order behind.oauth, ahead of.cli, exactly as the approved proposal described. A successful OAuth read still wins.The source is not user-selectable: it goes silent whenever the user isn't running Claude Code, so pinning it would strand the card. It's excluded from the picker (
userSelectableCases) and persists as Auto if it ever reaches settings.Safety
CLAUDE_CONFIG_DIR; a mismatch is dropped, so one account's numbers can't render under another's card (AGENTS.md:46).~/.claude/.Testing
make check→ 0 violations across 1810 files.mainat38ca30a. Residual failures under a wide filter are pre-existing order-dependent suites (ClaudeOAuthPromptCoalescingTests,ClaudeCLISessionTests,ClaudeOAuthRefreshFailureGateTests) — they fail onmaintoo and pass in isolation.Worth flagging one existing test I changed:
ClaudeLoginFlowPolicyTestsiteratedClaudeUsageDataSource.allCasesasserting each source round-trips through settings..statuslinedeliberately persists as Auto because it is never a selection, so iterating it is meaningless — it now iteratesuserSelectableCases. Behavioural assertions unchanged.Two open questions — why this is a draft
1. Who installs the shim? This PR is the consumer only; nothing writes to the drop directory yet.
docs/claude-statusline-feed.mddocuments the envelope and a minimal shim so it works by hand today. If you'd prefer a managed "Install statusLine helper" button (user-initiated, refusing a user-owned slot, clean uninstall), I'll add it — that's the difference between real adoption and near-zero.2. Sequencing vs #2675. That PR touches
MenuCardView+ModelHelpers.swift(where it adds the first card-facing source note — quite possibly the mechanism this label should reuse),ClaudeProviderDescriptor.swift, and theSettingsStorefiles. Happy to rebase onto it and adopt its note plumbing rather than run a parallel path.Marking ready once those are settled. If either answer reshapes it, I'd rather redo it than have you review the wrong thing.