Skip to content

Claude: opt-in statusLine usage feed - #2769

Open
luisgonzaleznf wants to merge 9 commits into
steipete:mainfrom
luisgonzaleznf:feat/claude-statusline-optin-feed
Open

Claude: opt-in statusLine usage feed#2769
luisgonzaleznf wants to merge 9 commits into
steipete:mainfrom
luisgonzaleznf:feat/claude-statusline-optin-feed

Conversation

@luisgonzaleznf

Copy link
Copy Markdown

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 statusLine command the user configured, and for Claude.ai-subscriber sessions it carries rate_limits with 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:

Constraint How
Off by default statusLineFeedEnabled defaults 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.
Labeled as the user's own config sourceLabel: "statusline" reaches the card, which notes "From your Claude statusLine config" (same seam as Kilo's "Using CLI fallback").
Fail soft on drift Bad JSON, unknown envelope version, wrong types, out-of-range percentage, missing windows — all read as absence, leaving the polled sources in place. Never an error on the card.
Composes, never replaces A .statusline step 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

  • Profile attribution. Observations are matched to the reporting session's CLAUDE_CONFIG_DIR; a mismatch is dropped, so one account's numbers can't render under another's card (AGENTS.md:46).
  • Freshness bound. 15 minutes — a stale live value must never outrank a fresh poll. Future timestamps count as fresh so clock skew can't blank the feed.
  • No identity. The snapshot asserts no account, plan, model-scoped weekly, Daily Routines or extra usage; those keep coming from the polled sources.
  • No credential surface touched. Nothing here reads the Keychain, changes prompt policy, or writes under ~/.claude/.

Testing

  • 18 new tests: fail-soft parsing (11 malformed shapes), planner ordering, opt-in gating, non-selectability, profile mismatch, staleness, clock skew, snapshot mapping.
  • make check → 0 violations across 1810 files.
  • No regressions vs main at 38ca30a. Residual failures under a wide filter are pre-existing order-dependent suites (ClaudeOAuthPromptCoalescingTests, ClaudeCLISessionTests, ClaudeOAuthRefreshFailureGateTests) — they fail on main too and pass in isolation.

Worth flagging one existing test I changed: ClaudeLoginFlowPolicyTests iterated ClaudeUsageDataSource.allCases asserting each source round-trips through settings. .statusline deliberately persists as Auto because it is never a selection, so iterating it is meaningless — it now iterates userSelectableCases. 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.md documents 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 the SettingsStore files. 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.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. labels Aug 8, 2026
@clawsweeper

clawsweeper Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge. Reviewed August 12, 2026, 12:22 PM ET / 16:22 UTC.

ClawSweeper review

What this changes

The 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
Reviewed head: 6267a29d877d2a1106af195ca7178d690dabf617

Review scores

Measure Result What it means
Overall readiness 🧂 unranked krab (1/6) Targeted tests are substantial, but the patch has a blocking fallback defect and lacks real behavior proof.
Proof confidence 🧂 unranked krab (1/6) Needs real behavior proof before merge: No redacted after-fix release-signed trace or card capture demonstrates the enabled feed through the real Claude refresh pipeline. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Patch quality 🦪 silver shellfish (2/6) 1 actionable review finding remain.

Verification

Check Result Evidence
Real behavior Needs proof Needs real behavior proof before merge: No redacted after-fix release-signed trace or card capture demonstrates the enabled feed through the real Claude refresh pipeline. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed 3 items Current main and release status: The PR head is not contained in current main, and no local release tag contains it; the statusLine implementation remains unmerged.
Fallback is skipped after a rejected feed result: The composition helper publishes a feed-only snapshot when no previous snapshot exists, then rejects later feed results when ownership is absent. The fetch pipeline already returns a successful statusLine result before that rejection, so it cannot continue to CLI.
Repository policy applies: The full repository policy permits a user-enabled statusLine JSON feed only when it is off by default, clearly attributed, fail-soft, and account-siloed; this review applies that boundary to the new fallback path.
Findings 1 actionable finding [P1] Preserve fallback when composition rejects the feed
Security None None.

How this fits together

CodexBar’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]
Loading

Before merge

  • Add real behavior proof - Needs real behavior proof before merge: No redacted after-fix release-signed trace or card capture demonstrates the enabled feed through the real Claude refresh pipeline. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
  • Preserve fallback when composition rejects the feed (P1) - The first feed-only refresh is published without ownership evidence. On a later refresh, the same feed succeeds in the pipeline but returns nil here because the prior rows are unowned; the pipeline has already stopped, so CLI is never tried. Make an uncomposable observation fail through before it becomes a successful outcome, and cover consecutive refreshes.
  • Resolve merge risk (P2) - An accepted statusLine fetch that is later rejected for missing ownership evidence prevents the established CLI fallback from running.
  • Resolve merge risk (P1) - No release-signed after-fix trace shows the enabled source through the real Claude refresh boundary.
  • Complete next step (P2) - Repair the concrete fallback defect and provide real behavior proof before merge review.
  • Improve patch quality - Fix the rejected-observation fallback path with a pipeline-level regression.
  • Improve patch quality - Provide redacted release-signed runtime evidence after the fix.

Findings

  • [P1] Preserve fallback when composition rejects the feed — Sources/CodexBar/UsageStore+ClaudeStatusLineComposition.swift:45-48
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Patch surface 27 files changed; 1,188 additions and 74 deletions The feature crosses settings, source planning, snapshot storage, menu attribution, documentation, and tests.
Production versus tests production +566/-21, tests +542/-53, docs +80/-0 The broad cross-layer change needs pipeline-level validation in addition to parser and helper coverage.

Root-cause cluster

Relationship: fixed_by_candidate
Canonical: #2733
Summary: This PR is the candidate implementation of the opt-in statusLine feed direction; the remaining defect is within this patch.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge-risk options

Maintainer options:

  1. Repair rejected-observation fallback (recommended)
    Ensure an observation without a safely owned prior snapshot falls through to CLI or web rather than ending the refresh as a no-op.
  2. Hold for runtime evidence
    Keep the PR unmerged until a release-signed run proves the repaired source order and card attribution.

Technical review

Best 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:

  • [P1] Preserve fallback when composition rejects the feed — Sources/CodexBar/UsageStore+ClaudeStatusLineComposition.swift:45-48
    The first feed-only refresh is published without ownership evidence. On a later refresh, the same feed succeeds in the pipeline but returns nil here because the prior rows are unowned; the pipeline has already stopped, so CLI is never tried. Make an uncomposable observation fail through before it becomes a successful outcome, and cover consecutive refreshes.
    Confidence: 0.95

Overall correctness: patch is incorrect
Overall confidence: 0.95

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against c4ed34d0e44a.

Labels

Label justifications:

  • P1: The introduced control-flow defect can suppress the established Claude CLI fallback for users who enable this feature.
  • merge-risk: 🚨 compatibility: Automatic source ordering can replace a usable fallback outcome with a no-op when the feed cannot be safely composed.
  • merge-risk: 🚨 auth-provider: The new account-scoped feed rejects unowned data, but the rejection currently fails to preserve the provider fallback path.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦪 silver shellfish.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: No redacted after-fix release-signed trace or card capture demonstrates the enabled feed through the real Claude refresh pipeline. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Evidence

What I checked:

  • Current main and release status: The PR head is not contained in current main, and no local release tag contains it; the statusLine implementation remains unmerged. (6267a29d877d)
  • Fallback is skipped after a rejected feed result: The composition helper publishes a feed-only snapshot when no previous snapshot exists, then rejects later feed results when ownership is absent. The fetch pipeline already returns a successful statusLine result before that rejection, so it cannot continue to CLI. (Sources/CodexBar/UsageStore+ClaudeStatusLineComposition.swift:47, 6267a29d877d)
  • Repository policy applies: The full repository policy permits a user-enabled statusLine JSON feed only when it is off by default, clearly attributed, fail-soft, and account-siloed; this review applies that boundary to the new fallback path. (AGENTS.md:46, c4ed34d0e44a)

Likely related people:

  • steipete: Current-main blame attributes the adjacent Claude refresh publication and strategy paths to this area’s recent work; the related merged fallback work was also authored by steipete. (role: recent area contributor; confidence: medium; commits: 330ae4384b18, 0954a74a0b9d; files: Sources/CodexBar/UsageStore+Refresh.swift, Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (17 earlier review cycles; latest 8 shown)
  • reviewed 2026-08-09T12:42:54.457Z sha 607b59c :: needs real behavior proof before merge. :: none
  • reviewed 2026-08-09T14:16:14.731Z sha 607b59c :: needs real behavior proof before merge. :: [P3] Pass the opt-in into Claude debug planning
  • reviewed 2026-08-09T18:22:15.997Z sha 607b59c :: needs real behavior proof before merge. :: [P1] Reconcile a pre-refresh account switch before composition | [P3] Pass statusLine state into Claude debug planning
  • reviewed 2026-08-09T18:36:32.893Z sha 607b59c :: needs real behavior proof before merge. :: [P1] Verify snapshot ownership before composing live windows | [P2] Persist and activate the statusLine opt-in | [P2] Keep a weekly-only observation in the weekly lane
  • reviewed 2026-08-09T19:07:11.214Z sha dcc46c8 :: needs real behavior proof before merge. :: none
  • reviewed 2026-08-09T20:11:52.180Z sha dcc46c8 :: needs real behavior proof before merge. :: none
  • reviewed 2026-08-12T15:06:59.729Z sha dcc46c8 :: needs real behavior proof before merge. :: [P1] Preserve the statusLine opt-in on launch
  • reviewed 2026-08-12T16:05:17.567Z sha 2208226 :: needs real behavior proof before merge. :: none

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Aug 8, 2026
@luisgonzaleznf

Copy link
Copy Markdown
Author

Both blockers confirmed against the source — thanks, these were real.

1. "The opt-in cannot currently be enabled" — fixed in 2380450.

You were right, and it was worse than a missing control: statusLineFeedEnabled was read by the planner but never populated. No SettingsStore property, no defaults key, no UI. It was false in every build, so the feature was inert.

My 18 tests all passed because they construct ClaudeSourcePlanningInput directly and never traverse settings — they tested the planner, not the feature. Now wired end to end (stored default off, snapshot, menu observation, provider toggle) with a test that asserts the user-facing switch reaches the snapshot field the planner reads. I checked it fails when the snapshot line is removed, reproducing the exact symptom that shipped.

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. ClaudeOAuthFetchStrategy.snapshot(from:) builds ProviderIdentitySnapshot from accountEmail / accountOrganization / loginMethod and carries extraRateWindows. The statusLine payload has none of those, so a successful step returns a snapshot with nil identity and no extras — and the store takes it wholesale. Identity, plan, model-scoped weekly, Daily Routines and extra usage would blank whenever the feed served the card. That is "replaces", which is exactly what the owner ruling forbids.

I haven't fixed it because I don't think it can be fixed correctly inside the strategy: ProviderFetchContext carries no prior snapshot, so nothing at that layer knows what to preserve. It needs a merge seam in the store — set a partial result over the last good Claude snapshot rather than replacing it — and that lives in UsageStore+Refresh.swift, which #2675 is actively changing.

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:

  • Wait for Claude: consented direct keychain read + CLI usage fallback (#2634) #2675, then add the merge on top of its plumbing (also resolves the source-note/label question in your recommendation).
  • Or land a narrower first cut where the step is only eligible when the polled sources already produced identity this cycle — no merge seam needed, but it would not serve the expired-OAuth case, which is most of the value.

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 ~/.claude/.

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.

@luisgonzaleznf

Copy link
Copy Markdown
Author

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 2cca07e.

Correct and worse than it looks. The parser defaulted a missing capturedAt to parse time, so a drop file that had sat on disk for hours read as current and the 15-minute bound stopped protecting anything — a dead session could outrank a live poll indefinitely. An observation that cannot be aged is now absence, and the docs say so.

Worth flagging why the suite never caught it: my own test asserted the fallback as intended behaviour (a missing capturedAt falls back to now...). The test encoded the bug. It now asserts the drop.

[P1] Merge statusLine windows with the existing Claude snapshot — confirmed, not fixed.

Agreed, and it's the blocker that matters. ClaudeOAuthFetchStrategy.snapshot(from:) builds ProviderIdentitySnapshot from accountEmail / accountOrganization / loginMethod and carries extraRateWindows; the statusLine payload has none of them, so a successful step returns nil identity and no extras and the refresh pipeline takes it wholesale.

It can't be fixed inside the strategy — ProviderFetchContext carries no prior snapshot, so nothing at that layer knows what to preserve. It needs a partial-result merge seam in UsageStore+Refresh.swift, which #2675 is actively changing. I'd rather build it on that PR's plumbing than land a parallel merge path beside it.

[P1] Bind observations to the active Claude account — confirmed, not fixed.

Also right, and I under-specified it: matching CLAUDE_CONFIG_DIR alone cannot distinguish a claude-swap within one profile, so a stale observation from the previous account could render under the new one. That's the siloing rule in AGENTS.md:46.

The blocker is placement, not intent: ClaudeActiveAccountProbe is private inside Sources/CodexBar/, and this strategy lives in CodexBarCore, so the active-account UUID isn't reachable from where the decision has to happen. Fixing it means either lifting that probe into Core or threading the identity through ProviderFetchContext — both in files #2675 touches. Same reasoning as above: I'd rather do it once, after.

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, make check clean across 1810 files, no regressions vs main. Staying a draft until #2675 lands and the owner rules on installation.

@luisgonzaleznf

Copy link
Copy Markdown
Author

8a92b80 fixes the timestamp cap. The two P1s stay open for the reasons below.

[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 (clock skew does not blank a live feed), so the suite stayed green through the defect. That is twice in this PR that a test of mine encoded an assumption instead of checking it.

[P1] Merge the partial feed with the verified Claude snapshot — still deferred.
[P1] Bind observations to the active Claude account — still deferred.

Both confirmed, neither guessed at. Reasons unchanged: the merge needs a partial-result seam in UsageStore+Refresh.swift, and the account binding needs ClaudeActiveAccountProbe (currently private in Sources/CodexBar/) reachable from CodexBarCore, or the identity threaded through ProviderFetchContext. Both live in files #2675 is rewriting.

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 oauthAccount.accountUuid in ~/.claude.json and the claude-probe log line. Happy to file that separately if useful; it is not caused by this branch, but it is the same attribution gap one layer down.

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.

@luisgonzaleznf
luisgonzaleznf force-pushed the feat/claude-statusline-optin-feed branch from 8a92b80 to 607b59c Compare August 9, 2026 12:20
@clawsweeper clawsweeper Bot added P1 Urgent regression or broken agent/channel workflow affecting real users now. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. and removed P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Aug 9, 2026
@luisgonzaleznf

Copy link
Copy Markdown
Author

Rebased onto main at 0.49.0 and both P1 findings are fixed, now that #2675 has landed.

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 applyProviderRefreshSuccess stays inside its body-length budget — it was already sitting exactly at 150 lines.

[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. CLAUDE_CONFIG_DIR cannot distinguish a swap within one profile and the feed carries no identity of its own, so dropping it is the only safe answer — publishing would render one account's windows beneath another's identity.

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 ClaudeOAuthActiveAccountObservation exists. Doing it in CodexBarCore was not possible.

One interaction worth flagging

A composed snapshot inherits dataConfidence from the previous poll. If that poll was a CLI scrape (.percentOnly), #2675's new card note would label live statusLine windows as "Usage via Claude CLI (limited detail)". The statusLine note therefore has to precede that check; it does, and the ordering is now documented at the call site so a refactor cannot silently invert it.

Verification

  • 6 new composition tests. Verified they fail without the fix, with the reported symptom exactly: accountEmail → nil, loginMethod → nil, tertiary → nil, extraRateWindows → nil.
  • 26 statusLine tests green; make check clean across 1839 files.
  • No regressions vs main at 6daa2b9. The differences under a wide filter are pre-existing order-dependent suites (ClaudeUsageTests, ClaudeCLITimeoutRetryTests, ClaudeOAuthPromptCoalescingTests, ClaudeCLISessionTests) — 59/59 pass in isolation across two runs, and main's own baseline fails three of the same family.

Still open

Only the installer question from #2733: whether an explicit user-initiated "Install statusLine helper" may write a CodexBar-marked slot in ~/.claude/settings.json, refusing to touch a user-owned entry. ClawSweeper's recommendation there is Allow conservative managed install, and I'll implement it in that exact shape on your word.

Until then this remains consumer-only, with docs/claude-statusline-feed.md documenting the envelope and a hand-installable shim. Staying a draft until you rule on that.

@luisgonzaleznf

Copy link
Copy Markdown
Author

Taking this out of draft. Both P1 findings are fixed on 607b59c (rebased onto 0.49.0), and the code is reviewable.

What's done

  • Merge instead of replace — the feed's windows compose over the last polled snapshot, so identity, plan, model-scoped weekly, Daily Routines, extra usage and cost survive. An omitted window means "no update", not "cleared".
  • Account binding — an observation is discarded when the active account changed since the snapshot it would compose over. CLAUDE_CONFIG_DIR cannot detect a swap within one profile and the feed has no identity of its own, so dropping is the only safe answer.
  • 6 composition tests, verified to fail without the fix with the exact reported symptom (accountEmail → nil, loginMethod → nil, tertiary → nil, extraRateWindows → nil); 26 statusLine tests total; make check clean across 1839 files; no regressions against main at 6daa2b9.

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 git=607b59c, but no Claude usage refresh runs at all in an ad-hoc-signed build — CodexBar's own cache items are ACL-bound to the release signature, so the provider never gets far enough to reach the planner. That is the same wall as before, one layer earlier.

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 ~/.claude/settings.json, refusing to touch a user-owned entry. ClawSweeper's recommendation is Allow conservative managed install — empty-or-CodexBar-marked slots only, empty-after-uninstall treated as opt-out, user-owned left alone. I'll implement exactly that shape on your word.

Without it this is consumer-only: docs/claude-statusline-feed.md documents the envelope and a working shim, but realistically few people will hand-install one. If you'd rather this not land until the installer exists, say so and I'll put it back to draft — marking it ready is me asking for a decision, not claiming it's finished.

@luisgonzaleznf
luisgonzaleznf marked this pull request as ready for review August 9, 2026 18:19
@clawsweeper

clawsweeper Bot commented Aug 9, 2026

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@clawsweeper clawsweeper Bot added merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. and removed merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Aug 9, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread Sources/CodexBar/UsageStore+ClaudeStatusLineComposition.swift Outdated
Comment thread Sources/CodexBar/SettingsStore+Defaults.swift Outdated
Comment thread Sources/CodexBarCore/Providers/Claude/ClaudeStatusLineDropStore.swift Outdated
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. label Aug 9, 2026
@luisgonzaleznf

Copy link
Copy Markdown
Author

All three findings were real and are fixed in dcc46c8. Two of them were mine encoding an assumption into a test, which is why the suite stayed green through both.

[P2] Persist the statusLine opt-in toggle — fixed. Exactly right: the setter only touched defaultsState, so the opt-in was lost on relaunch and the feed silently switched itself off. It now writes the key and calls noteBackgroundWorkSettingsChanged(), matching claudeWebExtrasEnabled. This probably also explains why a local proof run showed the feed doing nothing.

[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 primary is the session row. A promoted 7-day figure rendered as the 5-hour limit while the real weekly row survived beside it. I took the "drop the partial shape" option — a weekly-only payload is now absence rather than a lane-shifted guess. My test had asserted the promotion as intended behaviour; it now asserts the drop.

[P1] Verify account ownership before composing — fixed. The sharpest of the three. claudeOAuthActiveAccountObservation proves the account held still during this fetch, and I treated that as proof about the rows being composed over. If the user switches accounts and the next Auto refresh is satisfied by the statusLine file, nothing in that fetch re-read the account, so the guard passes and the new account's windows land under the old account's identity.

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.

make check clean across 1839 files; 29 statusLine tests green; no regressions vs main at 6daa2b9 (remaining differences are pre-existing order-dependent suites that pass 55/55 in isolation across two runs).

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. and removed P1 Urgent regression or broken agent/channel workflow affecting real users now. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. labels Aug 9, 2026
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Aug 9, 2026
@luisgonzaleznf

Copy link
Copy Markdown
Author

@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 ~/.claude/settings.json — refusing to touch a user-owned entry, and treating an empty slot after uninstall as opt-out? ClawSweeper's recommendation is to allow exactly that shape.

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:

  1. Yes to the guarded installer — I'll add it in that exact shape and this becomes usable.
  2. No, paste-only — say so and I'll close this; a feature nobody enables isn't worth the planner and labelling complexity it costs you.
  3. Not now — also fine, just say and I'll stop pinging.

One caveat I won't paper over: the remaining needs proof label wants a release-signed runtime trace, and I can't produce one from a fork — an ad-hoc build's cache items are ACL-bound to the release signature. Everything below that line is covered by tests I verified fail without their fixes. If you want that trace before merging, it needs one refresh cycle on a signed build.

No rush on my side — I'd just rather you close it than have it sit if the answer is no.

@clawsweeper clawsweeper Bot added merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Aug 12, 2026
@luisgonzaleznf

Copy link
Copy Markdown
Author

The P1 is real and it was mine. Fixed in fb5114cf, and chasing it turned up a second defect on this branch that no reviewer had flagged — details below, because it's the more embarrassing of the two.

[P1] Preserve the statusLine opt-in on launch — fixed

Confirmed exactly as described. The line came from my own commit 54bfe18d, titled — with some irony — "Make the statusLine feed toggle actually reachable".

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 .statusline step only under case .auto, and .auto != .cli is true — so the reset fired on precisely the mode that consumes the feed. Every launch, for every Auto user. The persistence fix in dcc46c8 wrote the value to UserDefaults correctly, and then this cleared the in-memory copy on the next load, so the toggle read back off and the planner omitted the step. Your "central restart defect" phrasing is right: the feature was unreachable after any restart.

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 else of an isRunningTests gate, so the suite never executed that branch — which is why this survived two review cycles with green tests. A plain delete plus a test would have been theatre: the test would pass before and after, since neither branch touches the flag once the line is gone.

So I added writesLaunchResetsToRawState, mirroring the performInitialProviderDetection: Bool = !SettingsStore.isRunningTests seam already on that initializer, which lets a test take the production path. Then I verified the test actually has teeth rather than assuming it: reintroduced the exact line, watched an enabled feed survives the launch reset that clears Claude web extras fail on both assertions, removed it, watched it pass. The pre-existing reachability test passes straight through the bug — that gap was the whole problem.

Separately: this branch was failing the architecture gatekeeper

While running the full suite I found cross provider case clusters are derived or specifically justified failing. It passes on main at c4ed34d0 and failed at dcc46c83, so it is this branch's doing, not pre-existing. Fixed in 2208226f.

The allowlist pins each guarded construct to an exact line with a text anchor. Earlier commits here inserted lines into UsageStore+Refresh.swift, UsageStore.swift and MenuCardView+ModelHelpers.swift, drifting 44 entries off their anchors; each orphaned entry then cascaded into an unjustified-construct failure, ~90 in total. Three parts to the repair:

  • Recomputed the 44 drifted line numbers against their anchors (scripted, nearest-occurrence for duplicate anchor text). The three it could not resolve are the synthetic Sources/App/Shared.swift fixtures inside the gatekeeper's own unit tests, left untouched.
  • Added the two missing // Provider-specific by design: markers in UsageStore+ClaudeStatusLineComposition.swift. I had introduced those constructs with no justification at all. The first reads better hoisted into a named local than as a comment buried in an argument list.
  • Updated the MenuCardView+ModelHelpers.swift:233 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. I changed only the counts and fingerprint — the reason text still describes the cluster accurately, so I left it alone.

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 2208226f

  • Full suite green — 20 shards, ~2,090 test executions, zero failures.
  • make lint — 0 violations across 1,839 files.
  • Gatekeeper suite — 38/38.

What I still can't give you

The proof ask is unchanged and I want to be straight rather than let two fix commits imply otherwise: I cannot produce a redacted release-signed runtime trace from a fork. That needs someone who can build and run this signed. Everything above is source-level reasoning plus tests, and I've said which parts are which.

@steipete — your recommendation in the review body ("accept documented manual setup; keep any managed installation flow as a separately reviewed proposal") matches what this PR does, and it is the only open question left besides the proof. A one-line yes or no on that is all I need; if the answer is no, I'll close this myself rather than leave it sitting.

luisgonzaleznf and others added 9 commits August 12, 2026 18:02
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>
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Aug 12, 2026
@luisgonzaleznf
luisgonzaleznf force-pushed the feat/claude-statusline-optin-feed branch from 2208226 to 6267a29 Compare August 12, 2026 16:17
@luisgonzaleznf

Copy link
Copy Markdown
Author

Rebased onto current main (70 commits) — head is now 6267a29d. My previous push had flipped the PR to mergeable: false, because the 44 allowlist line corrections collided with main's copy of the same file. It is mergeable: true again.

Only ProviderArchitectureGatekeeperTests.swift conflicted. Rather than hand-merge 44 line numbers, I took main's version of that file wholesale and re-derived both changes on top of it, then re-checked that the MenuCardView+ModelHelpers.swift cluster still computes to ["kiro@0", "claude@9", "kilo@13"] on the rebased tree instead of assuming the offsets carried over — main had moved that file, so the anchor line changed from 233 to 256 even though the offsets happened to hold.

Green on 6267a29d:

  • Full suite — 25 shards, ~2,930 test executions, 0 failures
  • make lint — 0 violations across 1,854 files
  • Gatekeeper — 38/38, and ClaudeStatusLine* 30/30

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.

@clawsweeper clawsweeper Bot added P1 Urgent regression or broken agent/channel workflow affecting real users now. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. P2 Normal priority bug or improvement with limited blast radius. labels Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P1 Urgent regression or broken agent/channel workflow affecting real users now. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant