# Verification and troubleshooting

## Verification

```bash
tests/run-tests.sh            # sandboxed compatibility tests, no quota
python3 tests/test_selector.py # unified selector contract
python3 tests/test_autoresume.py # auto-resume classifier/relaunch unit tests (also run by run-tests.sh)
npm run test:commands         # packed npm commands; run npm install --ignore-scripts first
claude-accounts verify        # real matrix: `claude -p "reply OK"` per authed account
claude-accounts verify --quick # auth presence/expiry only, no inference
codex-accounts verify         # real codex exec call per account
codex-accounts verify --quick  # local Codex auth check, no inference
claude-accounts limits        # live per-bucket usage incl. the Fable bucket
claude-accounts health        # limits + full verify + shim-on-PATH probe, logs to health.log, notifies on failure (Mac)
python3 lib/shim_path.py "$(npm root -g)/claude-multiacc"   # just the probe; exit 1 on a bypass
```

Verified end-to-end on both machines (2026-07-13): full matrix PASS, 20-invocation shim
spread clean, real project `-p` through the shim OK on Mac (`~/pipeline`) and server
(`/opt/inapp-onboarding`), uninstall→stock→reinstall roundtrip byte-identical, three
server systemd services (`vpn-server-management`, `inapp-onboarding`, `vpn-seo-panel`)
repointed via `CLAUDE_BIN=/usr/local/bin/claude` and restarted healthy.

## Troubleshooting

- **`claude` resolves to the real binary, not the shim** — the launch then never
  reaches the pool: the real client starts on the machine's own `~/.claude` login, and
  from ssh/tmux (where that login sits in a locked Keychain) it opens on
  "Not logged in · Please run /login" (my-mini, 2026-09-17). `claude-accounts status`
  and the weekly `health` probe every login shell mode (`zsh -li`/`-lc`, `bash -li`/`-lc`,
  `sh -lc`) with your own rc files and report `BYPASSED -> <path>` for any that resolve
  something other than the shim. Fix: `claude-multiacc install` rewrites the rc blocks
  (`~/.zshenv`, `~/.zprofile`, `~/.zshrc`, `~/.profile`, bash rc files) — each carries
  a prompt hook that re-asserts the shim dir before every prompt — then open a new
  shell; an already-open shell keeps the PATH it has. To check one shell by hand:
  `zsh -lic 'whence -p claude'` / `bash -lc 'type -P claude'`.
- **An account never gets picked** — `claude-accounts status`: no auth on this machine,
  a dead login (`selectable: NO`, see `claude-accounts expired`), or an active `.limited`
  marker (shows bucket + minutes to reset).
- **"Failed to authenticate: OAuth session expired and could not be refreshed"** — the
  login behind that run is dead. Run `claude-accounts expired` to see which, then
  `claude-accounts relogin`. Accounts in that state are excluded from selection
  automatically, so this only surfaces when *every* account needs a re-login (the shim
  then falls back to the machine's own `~/.claude` login) or when it was pinned with
  `CLAUDE_ACCOUNT`.
- **"Please run /login · API Error: 401 OAuth access token is invalid"** — the portable
  setup-token is rejected even though its presence and `claude auth status` look healthy.
  The shim now runs a private first-use inference, parks a rejected token, and reselects
  before direct, TUI, or `--resume` work sees the 401. Run `claude-accounts verify` to
  check every token immediately; `claude-accounts expired` reports token-only accounts
  as `UNVERIFIED` until that proof exists.
- **A session restarted itself, or did not** — that is [auto-resume](AUTORESUME.md).
  `grep autoresume ~/.claude-accounts/selection.log` shows each step: `hold` means no
  other account had headroom, and `giveup` gives the reason nothing was relaunched. A
  launch outside tmux, with piped stdio, or with flags the relaunch cannot rebuild gets no
  watcher at all. `touch ~/.claude-accounts/autoresume.off` turns it off, running
  sessions included (codex: `~/.codex-accounts/autoresume.off`).
- **Everything marked limited** — the shim still runs: the still-serving limited accounts
  go through the same two cuts (session gate, then strict best-weekly) and one is handed
  out anyway; check `selection.log` for `all-limited fallback=` lines.
- **Sync fails** — `tail ~/.claude-accounts/sync.log`; it's ssh/rsync to the manifest's
  `server` (BatchMode — needs key auth).
- **A service bypasses the shim** — it spawns an absolute path. Point its env
  (`CLAUDE_BIN` etc.) at `/usr/local/bin/claude`. Docker-internal `claude` installs
  (e.g. openclaw live-test containers) are out of scope for a host shim.
- **`add` sign-in says "Sign in again to continue"** — that's Claude's step-up
  authentication, not a tool error: granting a long-lived token needs a *recent* sign-in.
  Open a fresh incognito window, sign in to the target account at claude.ai **first**,
  then paste the link and approve. The approval scope is `user:inference`
  (*"Contribute to your Claude subscription usage"*) — that's correct and minimal.
- **Limits look stale** — launchd: `launchctl list | grep claude-multiacc`;
  cron: `crontab -l | grep claude-multiacc`. The shim also kicks a refresh when data
  is >3 min old.

## Security notes & known risks

- Tokens and credentials live as 0600 files under `~/.claude-accounts` (and
  `/root/.claude-accounts` on the server). Server compromise = account access.
- `selection.log` records timestamps, accounts and cwd, and for auto-resume also
  session ids and error classes. It never records prompt or transcript text.
- Rotating multiple subscriptions to spread usage may be flagged by anti-abuse systems;
  accounts can be banned for limit circumvention. Known and accepted by the operator.
- The usage endpoint is the internal one `/usage` consumes; if it changes shape, limit
  tracking fails open (accounts stay available, error-retry is the backstop).
