routing · larcane97 — Claude Code skill for account selection and setup
This skill helps run Claude Code or Codex through clausona routes, selecting an account by quota usage. Use it to inspect routing decisions or spread runs across accounts.
npx skills add larcane97/clausona --skill routing -a claude-codeWhat routing does
The skill covers creating and editing clausona routes, checking why an account was selected, and setting directory rules and defaults. A route groups accounts and selects one according to quota usage; launch a tool with clausona run --route <name>. Use clausona route list --json or clausona route explain <name> --json to inspect the configuration and selection.
Selection considers the 5H and 7D quota windows. When a model is named in the run arguments, its limit is considered too. Available strategies include round-robin, headroom, and expiring; if every account reaches 100% or is skipped, the run exits with code 75. The --wait option waits for a quota reset. This skill depends on clausona routes and accounts being configured, since it selects from those accounts.
What it helps with
- launch Claude Code using an account with quota remaining
- spread concurrent runs across several accounts
- find out why a route selected a particular account
- wait for a quota reset before starting a run
Example prompts for the agent
- Run Claude Code through the
workroute and choose the account with lower quota usage. - Explain why the
workroute selected this account for modelopus. - Set up a route for the accounts used in this directory and show its selection rules.
How to install routing
1Skills CLI (Vercel)
npx skills add larcane97/clausona --skill routing -a claude-code2Manually: put the files in place
~/.claude/skills/routing/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/routing/
1Skills CLI (Vercel)
npx skills add larcane97/clausona --skill routing -a codex2Manually: put the files in place
.agents/skills/routing/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add larcane97/clausona --skill routing -a cursor2Manually: put the files in place
.cursor/skills/routing/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install larcane97/clausona routing2Manually: put the files in place
.github/skills/routing/SKILL.md1Skills CLI (Vercel)
npx skills add larcane97/clausona --skill routing -a gemini-cli2Platform CLI
gemini skills install https://github.com/larcane97/clausona --path plugins/routing/skills/routingManually: put the files in place
.devin/skills/routing/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add larcane97/clausona --skill routing -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about routing
How do I install routing in Claude Code?
Run npx skills add larcane97/clausona --skill routing -a claude-code. The skill file will be available at ~/.claude/skills/routing/SKILL.md.
What does routing do?
It helps manage clausona routes and launch Claude Code or Codex using an account selected by quota usage. It also explains directory rules and defaults.
How does routing choose an account for opus?
When the run arguments specify a model, its quota limit is included in the account comparison. Inspect the predicted choice with clausona route explain <name> --model opus --json.
Source text
SKILL.md · MIT
---
name: routing
description: Run Claude Code or Codex on whichever clausona account has quota left, using clausona routes, and create, change or explain those routes, and the dir rules and defaults that pick one for a plain `clausona run claude`, for the user. Use when the user says "run this on an account with room", "spread these across my accounts", "make a route of my work accounts", "use my work accounts in this repo", "why did it pick that account", "wait until an account frees up", when a run stops on a usage limit (of the plan or of one model, such as Opus), when writing a script or cron job that runs on a route, or when starting several workers that should not all land on one account.
---
# Routing runs across clausona accounts
clausona keeps several Claude Code and Codex accounts on one machine. A **route** is a named
group of those accounts and a rule for picking one of them by how much of its plan quota is
used. `clausona run --route <name>` starts the tool on the account the rule picks right now.
(`csn` is an alias for `clausona` that only the user's interactive shell has. Run `clausona`.)
Run `clausona route --help` once before anything else. It lists every command, option and exit
code of the installed version. Where it differs from this skill, it wins.
`clausona route` with no arguments opens the Routes screen, a TUI meant for a person at a
terminal. Do not run it; use the subcommands below. (In a shell without a terminal it only prints
the help.) A user who would rather look through routes on that screen can run it themselves.
## What decides the pick
- An account's **usage** is the higher of its 5H and 7D windows: 0% 5H with 99% 7D is 99%.
That number, and nothing else, is ranked and shown.
- When the tool's arguments name a model (`claude --model opus`, `codex -m gpt-5.3-codex-spark`,
also after `exec`), the account's limit on that model joins the max: Claude's is weekly, and a
Codex one can have a 5-hour and a weekly window, of which the higher counts (named in the note
and `usage.window` as `GPT-5.3-Codex-Spark 5H` or `… 7D`). A model falls under
a limit when the limit's name (`Opus`, `GPT-5.3-Codex-Spark`) is a word of the model's name,
in any case: `opus`, `claude-opus-4-5`, `opus[1m]` fall under `Opus`; `gpt-5.3-codex` does not
fall under `GPT-5.3-Codex-Spark`. A run that names no model counts no model's limit, even if
the account's default model has one: pass `--model` when the work needs a model with its own
limit. `-c model=…`, a Codex config profile and `opusplan` are not read.
- clausona stops at the first stage that finds someone:
1. **pool**: `from` members under `maxUsage` (default 80), chosen by the strategy.
2. **fallback**: the first `fallback` member, in listed order, under `maxUsage`.
3. **overflow**: every member is at `maxUsage` or over it, so the one with the most left
(lowest usage) is taken, pool or fallback, at 90% or 99% alike.
4. **nobody**: every member is at 100% or skipped. Nothing is launched, exit code **75**.
With `--wait`, it waits for the soonest reset instead and starts again at 1 (see
[Waiting](#waiting-for-a-free-account)).
- Strategies: `round-robin` (default; the account picked longest ago, a never-picked one
first), `headroom` (lowest usage), `expiring` (lowest usage among accounts whose weekly limit
resets within 24 hours; with none, as `headroom`).
- Signed-out, expired and unreadable accounts are skipped at every stage, and so is one whose
stored sign-in is another account's.
- An API profile in a route (named exactly; no pattern matches one) is ranked by its spend as a
share of its limit: a clausona budget (`clausona route budget`), else the limit set on its
provider key. One with no limit, whose provider reports no spend, or whose spend could not be
read is skipped. It is billed per use.
- Resumed runs (`-c`/`--continue`, `-r`/`--resume`, `--from-pr`, `codex resume` or `fork`) use
only accounts that share sessions: the primary account and profiles with merged sessions. If
none of them can take it, the account that holds the session has to be run by name.
## Looking before acting
```bash
clausona route list --json # routes, their settings with defaults, their members, dir rules
clausona route explain <name> --json # every member's usage and status; launches and records nothing
clausona route explain <name> --model opus --json # as a run of that model would rank it
clausona route explain --json # what clausona run claude and codex would take in this directory
clausona list --json # every profile, with its email and quota
clausona route budget --json # every API profile: its limit, whose limit, spend and usage
```
`explain --json` gives:
- `settings`: the route with its defaults. `settings.tool` is `claude`, `codex` or `all` (a route
over the accounts of both tools).
- `outcome`: `{"kind": "picked", "id", "stage", "reason"}`, or `{"kind": "none"}` with
`soonest` (`{"id", "at"}`) when a reset time is known. `explain` exits 0 even when nobody could
be picked, so read `outcome.kind`.
- `members`, one per account: `status` is `picked`, `eligible` (under the cut, not picked),
`over-limit` (at or above `maxUsage`) or `skipped`. A skipped one has `skipReason`:
`signed-out`, `expired`, `other-account` (its stored sign-in is another account's),
`no-reading`, `not-registered`, `keeps-own-sessions`, or for an API profile `no-spend-limit`,
`spend-not-reported` or `spend-unread`. Each also has `usage`,
`fiveHour`, `sevenDay` and `lastPickedAt`, `scoped` (`{"label", "usedPercent", "resetsAt"}`,
its limit on the `--model` model, of a Codex limit with two windows the higher, or null) and
`budget` (an API profile's `{"usedPercent", "spentUsd", "limitUsd", "source", "per",
"resetsAt"}`, or null). `usage.window` is `5H`, `7D`, the name of that limit (its label for
Claude, `Opus`; its label and window for Codex, `GPT-5.3-Codex-Spark 7D`), or `budget`. The top
level has `model`.
`route list --json` gives each route's `members`, `freeNow` (`{"free", "members"}`) and `next`
(as `outcome`), and `problems`: what is wrong with each route in `routes.json` that cannot be
used, and with each dir rule or default that cannot be read. Such a route is left out, and only
commands that use it fail; tell the user, and point them to `clausona route edit` rather than
editing the file yourself. It also gives `dirs` (`{"~/work": {"claude": "work"}}`), `defaults`
(`{"claude": "main"}`) and `ruleWarnings`: rules that name a route that is gone or is for the
other tool, which a run skips.
With `--json`, a failing `route` command prints `{"version": 1, "error", "message"}` on stdout.
`error` is `usage`, `not-found` (no such route) or `failed`; a `routes.json`, or a route named,
that cannot be used adds `file` (its path) and `problems`.
Answer "why did it pick X" from `explain`: `outcome.stage` and `outcome.reason` for the pick, and
each other member's `status`, `skipReason` and usage. `explain` takes the same field options as a
run (`--strategy`, `--max-usage`, …) and `--resume`, to show what a run like that would get.
## Creating or changing a route for the user
`route add` and `route set` never ask, in a terminal or not. They write at once, and `route add`
then prints the route it made. So the asking is yours to do, before you write:
1. Show the user which accounts the route would hold. Rank it unsaved and show them the result:
`clausona route explain --tool <claude|codex|all> --from '<patterns>' [--exclude '<patterns>']`.
For a change, show the route as it is (`route explain <name>`) and as it would be.
2. Ask before including accounts that look like someone else's or shared: another person's name
or email, `*-share`. Leave them out until the user says yes.
3. `*` takes every subscription account of the tool, including ones added later. When the user
described a subset ("my work accounts"), use names (`work,work2`) or an email pattern
(`*@example.com`) instead.
4. Never put an API profile in a route unless the user names it: it is billed per use. A
pattern never matches one, so `*` is safe; it joins only by its exact name (`claude:glm`).
It also needs a spend limit: `route add` and `route set` refuse one without, naming
`clausona route budget <profile> --usd 20 --per month`. Set a budget only with the amount and
period the user gives. A budget stops new routed sessions, not a running one. Run an API
profile by name (`clausona run claude:glm`) only when the user asks for it.
5. Wait for the user's OK, then write with the CLI. Never edit `~/.clausona/routes.json`
yourself, and do not use `route edit`: it opens an editor for a person.
```bash
clausona route add <name> [--tool claude|codex|all] --from '<patterns>' [--exclude '<patterns>'] [--fallback '<patterns>'] [--strategy <s>] [--max-usage <n>]
clausona route set <name> [--from …] [--exclude …] [--fallback …] [--strategy …] [--max-usage …] [--add …] [--drop …] [--no-fallback]
clausona route rename <old> <new>
clausona route remove <name>
```
- `--tool` is the route's tool: `claude` (the default), `codex`, or `all` for one route over the
accounts of both tools. Without `--tool`, `--from` entries that all carry one tool's prefix
(`codex:a,codex:b`) make a route for that tool, and prefixes of both tools make an `all` route.
Check `clausona list --json` for which tools have accounts. `route set` cannot change the tool.
- `--yes` is accepted and does nothing. You do not need it.
- Patterns are comma-separated profile names or globs (`team-*`, `claude:team-*`). A pattern
with an `@` matches account emails. Matching ignores case. Quote the list.
- On an `all` route a name without a prefix matches in both tools: `personal` is
`claude:personal` and `codex:personal`. Use the prefixed id to mean one account.
- In `route set`, each field option replaces its whole field: `--exclude old` makes the exclude
list just `old`. To add to a list, pass its current entries too (from `route list --json`).
- `--add` and `--drop` change `from` one entry at a time. `--drop` removes only an entry written
in `from`, so it cannot take one account out of `*` or `*@example.com`. Use `--exclude` for
that.
- `rename` and `remove` act at once. Ask before removing a route. `rename` also rewrites the dir
rules and defaults that name it; `remove` also removes them, and says which.
- A route name starts with a letter or a digit, and the rest is letters, digits, `.`, `_` and
`-`. Names and patterns that look like an API key or a token are refused, and clausona does
not print them back. Never put a key in either.
## Running
```bash
clausona run --route <name> -- -p "<prompt>" # Claude Code, non-interactive
clausona run --route <name> -- exec "<prompt>" # a Codex route
clausona run codex --route <name> -- exec "<prompt>" # an all route: name the tool
clausona run claude --from '<patterns>' -- -p "<prompt>" # an unsaved route
clausona run --route <name> --strategy headroom -- -p "…" # change a field for this run only
```
- Routing options (`--route`, `--from`, `--exclude`, `--strategy`, `--max-usage`, `--fallback`,
`--wait`) go before the tool's own arguments. A `--` ends them, and everything after it goes to the tool
untouched (`--model`, `--permission-mode`, `--output-format json`, …).
- The field options need `--route` or `--from`. On their own they are refused with exit 1.
- On an `all` route (`settings.tool` is `all`), put the tool word before `--route` whenever you
pass arguments: `clausona run claude --route <name> -- -p "…"`. Only that tool's accounts are
ranked. Arguments without a tool word are refused with exit 1, because Claude Code and Codex
take different ones. `route explain` and `route pick` narrow the same way with `--tool`.
- Next to `--route`, `--exclude x` leaves `x` out for this run on top of the route's own
excludes. The other field options replace their field for this run. Nothing is saved.
- Never put a profile right after routing options or a tool word. These are refused, because the
tool would get the name as its prompt: `clausona run --route main claude:work`,
`clausona run claude claude:work`. To run one account, name it alone
(`clausona run claude:work`). If a profile is named like one of the tool's commands (`exec`,
`mcp`), put `--` before the command: `clausona run codex -- exec …`.
- `clausona run claude` (or `codex`) with no routing options takes a route from the user's
[dir rules and defaults](#dir-rules-and-defaults): the deepest dir rule for the current
directory, then the tool's default, then the active profile. `clausona route explain --json`
with no name says which. With no tool word and no `--route` or `--from`, it is refused.
- Naming a profile (`clausona run claude:work`) always runs it, whatever its quota.
- A routed run names the account it picked, and why, on stderr. stdout is the tool's own.
- Without a terminal an unknown `--route` creates nothing: it exits 1 with
"Create it: clausona route add <name>". Treat that as a new route: show the members and ask
first, as above.
- `clausona run --route <name>` with no tool arguments is an interactive session. Give that
command to the user instead of starting it from your shell.
## Waiting for a free account
`--wait` makes "nobody is free" a wait instead of an exit 75, for `run` and `route pick` alike:
```bash
clausona run --route <name> --wait -- -p "<prompt>" # waits up to 5h
clausona run --route <name> --wait=2h -- -p "<prompt>" # or 30m, 1h30m: after `=`, never a space
id=$(clausona route pick <name> --wait=1h --model opus) # an orchestrator's turn, waiting
```
- It sleeps until 30 s after the soonest reset of an account at 100%, reads quota again at
least every 30 minutes, and then ranks again. stderr says `waiting until 14:05 for claude:work
(5H resets) …`; stdout stays the tool's, or `pick`'s id or JSON.
- If the soonest reset is past the cap, or no reset time is known, it exits 75 at once, and the
message says the cap that would reach it (`--wait=48h`). Ctrl-C exits 130; nothing is launched
or recorded.
- Several waits that wake together take different accounts, as picks always do.
- Your own shell tool call times out long before a wait of hours ends. Use `--wait` in a script,
in cron, or in a command you give the user; from your shell, only with a cap shorter than your
tool's timeout, or run it in the background. Ask before starting a wait of more than a few
minutes.
A cron or script recipe, for the user:
```bash
clausona run claude --route nightly --wait=4h -- -p "review yesterday's commits" > review.md
case $? in 0) ;; 75) echo "no account came free within 4h" >&2 ;; *) exit 1 ;; esac
```
cron has a short `PATH` (give it one that reaches `clausona`, `claude` and `node`) and no
terminal, so an unknown route exits 1 instead of asking.
## Dir rules and defaults
A dir rule gives a directory a route per tool: in that directory and below, a plain
`clausona run claude` (or `codex`) with no `--route` or `--from` runs on it. The deepest rule
for the current directory wins. A default does the same everywhere no rule holds. With neither,
the run takes the tool's active profile.
```bash
clausona route explain --json # here: what each tool's run takes, and why
clausona route dir # every dir rule and default
clausona route dir ~/work work # in ~/work and below, run claude takes route work
clausona route dir ~/work any --tool codex # an all route fills both tools unless --tool says one
clausona route dir ~/work --clear [--tool codex]
clausona route default main # elsewhere, run claude takes route main
clausona route default --clear --tool claude # --tool is required to clear (claude, codex or all)
```
- These change what every plain run in that directory does, the user's own included. Say what
will change (`route explain` from that directory shows what runs now) and wait for the user's OK
before you set or clear one.
- A route fills the slot of its own tool. An `all` route fills both, or the one `--tool` names. A
route of the other tool is refused. The route must exist first.
- A path may be relative, absolute or start with `~`; it is stored with `~` when it is under
home. A rule matches its directory through symlinks, and ignoring case on macOS and Windows.
- `route explain --json` with no name gives `{"directory", "runs": [...]}`, one entry per tool
(`--tool` limits it to one). A routed entry is the `explain --json` of its route, plus `tool`
and `skipped`. Its `resolvedBy` is `dir:<path>` (the rule's path as stored, with `~`) or
`default`. An entry with no route is `{"tool", "route": null, "resolvedBy": "active",
"profile", "skipped"}`, `profile` null when the tool has no active profile. `skipped` lists
the rules passed over as `{"rule", "reason"}`, `rule` being `dir:<path>` or `default`.
- A rule whose route is gone, cannot be used or is for the other tool is skipped: the run says so
on stderr and goes on to the next rule. A rule that cannot be read at all is named by
`route list` (in `problems`); a run only says that there is one. If `routes.json` cannot be read at all, a plain run
says so in one line and runs the active profile; a run with `--route` fails as before.
## Several workers at once
Take one turn per worker on a round-robin route, and start each worker on its own pick:
```bash
id=$(clausona route pick main); code=$? # one profile id on stdout; records the turn
clausona run "$id" -- -p "$(cat <brief file>)" # start this worker in the background
```
Do both for each worker in turn, and check `code` every time: 0 starts the worker on `$id`; 75
means nobody is free, so start no more workers (next section); anything else is an error to show
the user.
- Each pick records the turn, so the next pick takes the next account under the cut. Never start
every worker on one `pick` result, and never on ids copied from `explain`, which records nothing.
- With fewer accounts under the cut than workers, later picks come back to an account already in
use. Once every account is over the cut, a pick (stage `overflow`) takes the one with the most
left, not the next turn. Tell the user which workers share an account.
- One `clausona run --route main -- -p …` per worker also takes its own turn. Use `pick` when you
want the id before you start.
- On an `all` route a pick can be an account of either tool. Pick with `--tool claude` (or
`codex`) so every id is of the tool your worker command is written for.
- On a `headroom` or `expiring` route, picks made close together land on the same account
(readings are cached for 5 minutes). Add `--strategy round-robin` to each `pick`, or use a
round-robin route.
- `pick --json` prints `{"profile", "route", "stage", "usage", "reason"}`. When nobody is free it
still prints JSON, with `"profile": null` and `soonest` (`{"id", "at"}`, or null), and exits 75.
- When workers run a model with its own limit, pick with `--model` too
(`clausona route pick main --model opus`), so a spent Opus limit is not picked for an Opus worker.
- To wait for a free account instead of stopping at 75, pick with `--wait` (above).
## When nobody is free (exit 75)
Nothing was launched. Every account of the route is at 100% of a window, or was skipped. The
message on stderr lists each account with its usage and when it resets, or why it was skipped,
and marks the soonest reset (with `--json`, `soonest.at`). Tell the user which account frees up
first, and when, and which skipped ones `clausona login <profile>` would bring back. Workers
already started keep running. If the work can wait, offer the same command with `--wait` (or the
longer `--wait=<n>h` the message names) for the user to run.
A higher `--max-usage` does not help: past the cut, any account under 100% is already taken. Do
not run an account by name to get past the limit unless the user says so. If they do, name the
risk: that account is at its limit, so the session may stop at once.
## When a run stopped on a usage limit
If it was a limit on one model (Claude's Opus limit, say), start it again with that model named
(`-- --model opus`), so the account whose limit on it is spent is not picked again.
Otherwise start it again on the route. The spent account is now at or above the cut, so another one is
picked. But readings are cached for up to 5 minutes, and with every account over the cut, a
cached reading under 100% can pick the spent one again: if `explain` would still pick it, or
shows it `eligible`, add `--exclude` with it for this run. The route's own excludes still apply.
To carry on the same session, pass `-c` or `--resume` after `--`; that run only goes to accounts
that share sessions.
© larcane97 · MIT · larcane97/clausona
Similar skills
All skills for Agent setup: memory, context, orchestration →routing by other authors comes first.