Skip to content

feat(bin): add an opt-in Claude account pool chosen per launch from live quota - #1

Merged
Nitsur10 merged 3 commits into
mainfrom
fm/claude-account-switch-2
Oct 1, 2026
Merged

Nitsur10 merged 3 commits into
mainfrom
fm/claude-account-switch-2

Conversation

@Nitsur10

@Nitsur10 Nitsur10 commented Oct 1, 2026

Copy link
Copy Markdown
Owner

What this does

Adds an opt-in Claude account pool (config/claude-accounts) so a home with two Claude Max logins can spread worker launches across both by live usage, instead of pinning every launch to one login.

  • At each Claude ship or scout launch, each pooled account is checked with claude auth status --json (must be signed in with an email) and read once with quota-axi --provider claude --no-credential-refresh --max-age 5m --full --json, bounded to 20 seconds and never with --profile-only, so a Keychain-backed login is readable.
  • Accounts are skipped with a note: when signed out, duplicated (same email as an earlier account), read as a different email (the read fell back to another login), exhausted, under the fixed 15 percent five-hour guard, under the fixed 10 percent weekly floor, unreadable, or (for the pages account) under the weekly reserve (default 20). reserve= is the only tunable.
  • Eligible accounts rank by quota-axi's all_models spendPriority, then weekly percent left, then file order. If every account is skipped after a reading, the launch refuses. If no signed-in account can be read at all, the launch takes the ordinary default login exactly as without a pool and says so; it never falls to the pages account on an unknown quota.
  • Secondmate and raw Claude launches take the pages account, so a session that may publish claude.ai pages runs on the account that owns them.
  • fm-spawn.sh --account <root|email> and fm-control.sh <id> relaunch --account <root|email> override the choice (sign-in check still applies; quota gates do not).
  • The chosen account is recorded as account= and account_email= on the spawned line and task record. A relaunch stays on the recorded account while it is eligible, otherwise chooses again; a Claude task with no account= counts as on the ordinary login. A running conversation never changes account.
  • With no config/claude-accounts, every launch is exactly unchanged, and an existing config/claude-account pin always wins over a pool.

Docs: docs/configuration.md "Claude account pool (config/claude-accounts)". Tests: tests/fm-worker-account.test.sh (selection, reserve, override, skip-on-signed-out, unreadable-quota paths, duplicate email, no-pool path) and tests/fm-control-relaunch.test.sh (relaunch stays on the recorded account, legacy record without account=).

Example pool file for this Mac:

account=ordinary
account=/Users/e8e/.claude-max1
pages=ordinary
reserve=20

How it reaches this Mac

This PR targets the fork's main (Nitsur10/firstmate), not upstream. The live home at /Users/e8e/code/firstmate is currently at fd325b1b, the same commit this branch is based on, but its main tracks origin/main (upstream kunchenguid/firstmate). The ordinary update path pulls upstream only, so after this merges the home needs a one-time fast-forward from the fork (git fetch fork && git merge --ff-only fork/main on the home's main, done by firstmate with no live crewmates), followed by a re-read. From then on the home carries a commit upstream does not have, so later upstream updates arrive as a merge or rebase onto fork main rather than a pure fast-forward until this is offered upstream (a separate decision). Nothing changes behaviour until a config/claude-accounts file is written.

Profile parity needed before ~/.claude-max1 joins the pool

Not applied by this PR (out of scope; firstmate applies it by hand). Read-only comparison of ~/.claude against ~/.claude-max1, which today holds only .claude.json and backups/ (no settings.json, no skills):

  • settings.json hooks:
    • PreToolUse matcher Bash -> /Users/e8e/.agents/hooks/deny-dangerous.sh (the dangerous-command blocker).
    • SessionStart -> gh-axi, chrome-devtools-axi, and bash '/Users/e8e/.claude/hooks/herdr-agent-state.sh' session (absolute path, so it works from any profile once the hook entry exists).
  • Other settings.json keys to carry: model, modelSettings, skipDangerousModePermissionPrompt, agentPushNotifEnabled (theme and tui are cosmetic).
  • User-level skills: ~/.claude/skills/no-mistakes and ~/.claude/skills/synced.
  • Plugins: ~/.claude/plugins/ (store, synced).
  • Copy or symlink these; never copy .credentials.json, the Keychain entry, or .claude.json account state.

Related, outside this repo: ~/.local/bin/fm-usage reads ~/.claude-max1 with --profile-only, which shows nothing for a Keychain login; drop that flag there. Verification once nitesh@e8e.au is signed in: CLAUDE_CONFIG_DIR=~/.claude-max1 quota-axi --provider claude --no-credential-refresh --full must name nitesh@e8e.au; the pool also enforces this by skipping a read whose email does not match.

Judgment calls

  • Weekly floor and five-hour guard are constants (10 and 15 percent), with reserve= the only tunable: narrower config, fewer ways to misconfigure the guard.
  • Unreadable quota is never treated as usable; if nothing is readable the launch falls back to the ordinary default login, never the pages account, so a quota outage cannot drain the reserve.
  • A Claude task record without account= is treated as on the ordinary login at relaunch, so tasks started before the pool keep their session history and prompt cache.
  • Profile-sync automation and the fm-usage fix were deferred; parity is listed above for a hand-applied step.
  • The pool is not inherited into secondmate homes; a secondmate takes the pages account.
  • Ranking reuses quota-axi spendPriority rather than a new score, with weekly percent left and file order only as tie-breaks.

Reviewer quiz

  1. What does this change do? With config/claude-accounts present and no pin, each Claude worker launch picks the pooled login with the best live runway (spendPriority), skipping signed-out, duplicate, mismatched, unreadable, or low accounts and keeping a weekly reserve on the pages account; the choice is recorded as account= and kept on relaunch.
  2. What does it deliberately not do? It never switches a running conversation, never signs in or out, never edits ~/.claude* profiles or syncs their hooks and skills, and changes nothing when the pool file is absent or a config/claude-account pin exists.
  3. What could break? A pooled profile missing the default profile's hooks runs workers with fewer guardrails (hence the parity list); a malformed pool file refuses every Claude launch from the home until fixed; a slow quota read adds up to 20 seconds per pooled account at launch.

…ive quota

config/claude-accounts lists pooled Claude logins; each Claude ship or
scout launch picks the eligible login with the highest quota-axi
spendPriority, after a five-hour guard, a weekly floor, and a weekly
reserve on the pages account. Each member's email is verified through
claude auth status --json and the quota-axi reading, so one login is
never counted twice. A signed-out, duplicate, misread, or guarded member
is skipped with a note; only an empty pool refuses. --account names one
pooled account on fm-spawn and fm-control relaunch, the choice is
recorded as account= and account_email=, and a relaunch stays on its
recorded account while it remains eligible. A present config/claude-account
pin still wins, and a home without the pool file launches unchanged.
@Nitsur10
Nitsur10 merged commit b863a1a into main Oct 1, 2026
18 of 19 checks passed
Nitsur10 added a commit that referenced this pull request Oct 9, 2026
Bring the fork up to date with upstream main again while keeping the
opt-in Claude account pool from fork PR #1.

Five files conflicted, each where both sides added an item to the same
place, so every resolution keeps both:
- bin/fm-spawn.sh usage lines list the fork's --account and upstream's
  --herdr-resume-lock-wait.
- bin/fm-control.sh's relaunch header and resolve_relaunch_profile run the
  fork's pin-or-pool account check and then upstream's worker tool
  exclusion check, both before the old agent stops.
- docs/agent-control.md and docs/configuration.md name both the Claude
  account pool and worker tool exclusions.
- tests/fm-control-relaunch.test.sh runs both the pool relaunch tests and
  the tool exclusion relaunch tests.
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