Skip to content

Multiple machines

If you use coding agents on several machines, one machine’s reports can include the others. Each machine keeps parsing its own logs; this machine fetches what they count over ssh and keeps a copy, so reports work offline and stay fast.

  1. Install llm-usage-metrics on both machines.
  2. Connect once with ssh laptop, using whatever you type to reach that machine, and check that it logs in without asking for a password.
  3. On this machine:
Terminal window
llm-usage machine add laptop # or: machine add laptop me@192.168.1.20
llm-usage # every report now includes the laptop

The first machine add parses all of that machine’s logs and can take a minute.

machine add takes a name and an ssh destination. The name is how reports refer to the machine: 1-32 lowercase letters, digits, or dashes. The destination is anything ssh accepts: host, user@host, or a Host alias from ~/.ssh/config, whose settings (port, key, ProxyJump, ControlMaster) all apply. Without a destination, the name is used as one, which suits an ssh alias or a host name:

Terminal window
llm-usage machine add laptop # ssh laptop
llm-usage machine add work me@10.0.0.5 # a name of your choice for another destination

Install llm-usage-metrics on the other machine first. ssh must log in without a password prompt: key login, or a key loaded in ssh-agent.

machine add fetches the machine’s usage once and only then adds it to your config file, keeping your comments:

[machines.laptop]
ssh = "laptop"

If node comes from a version manager (nvm, fnm, volta, mise), a non-interactive ssh command usually cannot find llm-usage. machine add then asks the machine’s login shell where it is and saves a command that puts that directory on PATH. To set it yourself:

Terminal window
llm-usage machine add vps --command "/home/me/.local/bin/llm-usage"

Every report includes the machines you added. When a machine’s usage was last fetched more than 10 minutes ago, the report fetches it again while it parses this machine’s logs, waiting at most 30 seconds. A machine that is off or unreachable does not stop the report: it uses the usage fetched last time and says so, and waits 10 minutes before trying again. Until a fetch succeeds, each report names the error, for example laptop: last sync failed (ssh failed: ...); using its usage from 2 days ago. Run llm-usage sync laptop to retry. It is a warning once the usage is a day old.

Terminal window
llm-usage daily # this machine and the others, fetched when due
llm-usage daily --no-sync # use what was fetched last, without connecting
llm-usage sync # fetch every machine now

The stderr summary says which machines a report includes and when each was last fetched:

ℹ Machines: laptop (synced 4 min ago); left out 1,204 events already counted.

--machine limits a report to some machines; local is this one:

Terminal window
llm-usage monthly --machine laptop
llm-usage monthly --machine local,vps

Reports combine every machine into the same rows. To see each machine apart, --by-machine splits each source row of hourly, daily, weekly and monthly by machine, and events names the machine of each event from another machine:

Terminal window
llm-usage monthly --by-machine # claude (local), claude (laptop), ...

Until you add a machine, every row is (local). Share reports (--share) can filter and stack by machine.

The summary (llm-usage with no command) splits this month’s cost by machine under its table, for example This month by machine: local $412.10 (81%), laptop $96.70 (19%). A month without a known cost is split by tokens instead. --json lists each period’s machines under machines.

A report pointed at a custom directory (--claude-dir, --source-dir) counts only that directory, without other machines, unless you name them with --machine. statusline includes other machines too, from their last fetch; it never connects to them, so a status bar stays fast.

  • llm-usage sync runs llm-usage machine export on each machine over ssh. The machine refreshes its own event store and sends only the files that changed since the last fetch, in a versioned format.
  • What it sends is what that machine’s own reports count, history included, for the sources its own config selects. Costs are calculated here, with this machine’s pricing.
  • The export carries the machine’s hostname, each file’s absolute path there (for example /home/<user>/.claude/projects/…), and each event’s session id, model and absolute repository root. They are stored in machines/<name>.db on this machine.
  • The export also sends each file’s tool calls and turns (name, status, ids, times, role, model and text length; never message text or a tool’s input and output), so tools and session --timeline show them for other machines too. A machine running an older version sends usage only: its usage merges as before, and its tool calls and turns appear after it is updated and synced again. The first sync after this machine upgrades from 0.11.0 or older fetches every file once, since the older version dropped the tool calls it received. A file with tool calls but no usage is not sent.
  • Each machine’s usage is kept in its own file, machines/<name>.db, next to this machine’s event store; your own ledger never mixes with it. A failed fetch keeps the previous copy, and machine list shows the error.
  • The same session can reach several machines (a copied ~/.claude, a synced or shared home directory). An event already counted, from this machine or one added before, is counted once. A session continued on another machine keeps its new turns. The Machines: note says when another machine runs a different llm-usage-metrics version.
  • An export records the parser version of each source. When the machine that served a session first reads its source with another parser version, or either side did not record one, the copies may not match exactly. Turns are then matched by timestamp and model first, then by timestamp alone, since the two parsers can name a model differently. So two turns in the same millisecond each match their own copy, but a turn only the other machine has is dropped when it shares a millisecond with an unmatched local turn. The copy already counted wins, turns only the other machine has are kept (the Machines: note counts the dropped ones in what it left out), and a warning names the sources and both versions (unrecorded for a missing one) until llm-usage is updated on both machines. A disabled machine gets a note instead.
  • Tool calls and turns follow the same rules per session: one already counted from this machine or an earlier one is left out, matched exactly, or by kind and timestamp when the parser versions differ.
  • A machine last synced by a version that did not record parser versions is matched by timestamp in the same way, and a note asks you to sync it after updating llm-usage there.
  • Repository paths are those of the machine the usage came from: efficiency --repo-dir and session --by-repo treat the same repository at another path as a different one.
Terminal window
llm-usage machine list # each machine, when it last synced, how much is cached
llm-usage sync laptop --full # fetch every file again
llm-usage machine remove laptop # delete its config table and its cached usage

llm-usage doctor shows the same machines next to your sources, from what is cached; it never connects to them.

To stop fetching a machine but keep its cached usage in reports, set enabled = false in its table; the Machines: note then says (disabled, usage from 3 h ago).

sync and machine add print what failed and how to fix it:

Message Fix
ssh could not log in without a prompt Set up key login (ssh-copy-id laptop) or load your key into ssh-agent. Syncs never prompt.
ssh does not know this host yet Connect once with ssh laptop to check and accept its host key.
llm-usage was not found Install llm-usage-metrics there, or set command to how it starts (see Set up).
too old to export usage Update llm-usage-metrics on that machine.
no complete export within 30s The machine needs longer than a report waits (a large first parse); run llm-usage sync <name>, which waits as long as it takes.
the export ended early The connection dropped or the other machine stopped; run llm-usage sync again. Nothing was changed.
Another llm-usage run is editing Another machine add or remove holds the config’s .lock file. If none is running, one crashed: delete the named .lock file.

After a node upgrade, a saved command with a version in its path may stop working; remove the machine and add it again to detect the new path.