Skip to content

feat(fallback): name chain members clauth cannot measure - #116

Open
stex wants to merge 4 commits into
uwuclxdy:mommyfrom
stex:feat/chain-unmeasured-members
Open

stex wants to merge 4 commits into
uwuclxdy:mommyfrom
stex:feat/chain-unmeasured-members

Conversation

@stex

@stex stex commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

operator asked: "Does it even make sense to put a Claude account into the fallback chain when there's no way to measure how much limit is left?" (asked in German). After your answer to question 3 on #110, the ask became: a warning when such a member joins the chain, plus a marker reusing an existing one, with the chain order left as it is.

This marks chain members clauth has no way to measure, says what that does to the chain, and warns when one joins. The walk doesn't change.

Which members. fallback::no_usage_source is true for a subscription member (no base_url, no api_key) without an OAuth login behind it, which is what a profile holding only a claude setup-token mint looks like. collect_tokens never polls it. The predicate is pure, so the render path can ask it per frame.

What that does today, unchanged by this PR and now said out loud: with no reading, the walk counts the member as having headroom and reaches it once no freshly read member has room. Once it is active, nothing reads it as exhausted (reading_is_actionable has no status entry to act on), so clauth never switches away from it on its own. That is the part an operator can't guess, so every surface below says it (shared as fallback::no_usage_hazard(), the way uncapped_spend_fix() is shared).

The marker is a new lowest BlockedReason rung, NoUsage. It fires only while such a member has no reading at all, so a reading from anywhere (the probe, later) retires it, and every other rung outranks it.

  • The Fallback selector and the Overview chain row carry ⋯, stale's glyph at stale's hue. The walk treats the two alike: neither is ever fresh, so both wait for the pass that accepts any freshness. auth broken and key rejected share × the same way.
  • The member card opens on [ no usage yet ], the word start --auto --explain already prints for an unread account. I moved that string into format::DIAG_NO_USAGE so both read one constant. The fix line under it is add an oauth login with clauth login <name> so clauth can read its usage, the split the setup-token login already suggests ("for usage polling, also add an OAuth pair later").
  • The help modal's glyph legend gets a ⋯ no usage yet row.

The warnings.

  • On the Fallback tab, + add on such a member raises a confirm built like the mixing one: clauth can't read this account's usage. / once it's active, clauth won't switch away from it on its own. When an add would also mix api-key and oauth accounts, the mixing confirm shows, unchanged, and the marker says the rest once the member is in.
  • The daemon logs one line at boot beside the uncapped-spend warning: clauth daemon: can't read the usage of <names>. once one is active, clauth won't switch away from it on its own. add an oauth login with `clauth login <name>` so clauth can read it. A headless run or a chain edited by hand hears it at the next start; a reload of profiles.toml stays quiet, like the uncapped-spend line. Disabled members are left out.

What stays the same: chain order, both walks, every switch decision, every state on disk. BlockedReason is render-only. I added NoUsage to every_blocked_reason_variant_stays_coupled_to_candidate_excluded as not dead-first, with a fixture. the_walk_treats_an_unmeasured_member_like_any_member_without_a_fresh_reading checks next_target and the scheduler's snapshot walk land on the unmeasured member when nothing is fresh, and on the fresh member when one is. an_unmeasured_active_member_is_never_switched_away_from pins the hazard the copy names, on decide_auto_switch and on the snapshot walk.

User-visible changes, as CONTRIBUTING asks: the confirm copy, the pill and fix line, the legend row, the daemon log line, and a paragraph in wiki/Auto-Switch.md under "The decision" (its own commit). No key binding or default changed.

Two existing tests changed. fallback_add_enter_commits_directly_when_add_would_not_mix used two blank profiles without a login, and the candidate now raises the new confirm, so both got an OAuth login. The help legend pin covered the first 14 marks, so it now covers the 15th.

Questions where I picked something you may want otherwise:

  1. You asked to reuse an existing marker. The word (no usage yet) and the glyph (⋯) are reused; the rung exists so the pill can print that word, since Stale's stale data / last usage check failed would be false for a member that was never polled. Shared ⋯, or a glyph of its own?
  2. The fix line points at clauth login <name> for an OAuth pair beside the setup token. Is that advice you want clauth to give a setup-token user, or should it wait for the probe?
  3. API-key members are left out: their capacity is spend or a balance, and a window a provider doesn't publish has no line to cross. That leaves a generic endpoint, a balance-only provider and an Anthropic api key with no endpoint unmarked. Fine?
  4. A subscription profile with no login at all is flagged too, since the predicate can't tell it from a setup-token one without a disk read per frame. Fine?

agent

  • Claude Code (claude-opus-5-5), operated by @stex. I'm a worker session briefed by Jenny, his assistant, who filed Feature: usage for setup-token profiles from the rate-limit headers of a probe, only while the 5h window is open #110.
  • ran: cargo fmt --all -- --check clean; cargo clippy --locked --all-targets --all-features -- -D warnings clean on each of the four commits; cargo test --locked --all-features on the last commit: 5146 passed, 0 failed, 1 ignored. I broke the change on purpose fifteen ways (drop the rung; let a reading not retire it; drop each of the predicate's three checks; move the rung to the top; skip the confirm; swap its precedence; list disabled members; never log; drop the serve() call; let the walk exclude unmeasured members; change the glyph, the word or the hazard copy) and fourteen turned tests red. The survivor: deleting the warn_if_chain_has_no_usage_source(&config) call in serve() stays green, the same gap the warn_if_spend_is_uncapped call beside it has. Operator verification: unknown.
  • unsure: questions 2 to 4; an env-routed profile ([env] ANTHROPIC_BASE_URL without base_url) counts as a subscription member here; a profile whose login is removed while the TUI runs may keep its last reading and so hide the marker until a restart; I haven't traced that.

@stex
stex marked this pull request as ready for review October 8, 2026 11:38
stex added 4 commits October 8, 2026 13:41
A subscription member with no OAuth login behind it (a setup-token mint) never gets a usage reading: the walk counts it as having headroom, and once it is active nothing switches off it.
The Fallback and Overview rows now mark it with stale's `⋯`, and its card opens on `no usage yet`, the word `start --auto --explain` already prints for an unread account, with `clauth login <name>` as the fix. Display only: no walk reads the new rung.
The Fallback tab's add picker raises a confirm for such a member, as it does for an add that mixes api-key and oauth accounts. When both apply, the mix confirm shows. The hazard it names lives beside `no_usage_source` so the daemon can say it the same way.
One log line beside the uncapped-spend warning, with the same hazard and fix, so a headless run or a hand-edited chain hears it at the next start. Disabled members are left out.
@stex
stex force-pushed the feat/chain-unmeasured-members branch from ac8cc54 to 40bc102 Compare October 8, 2026 11:42
@stex

stex commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

The macOS spec is flaky btw, that's why it failed here, but not on my other PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant