Install the Collector
Two commands, two answers and a restart — and the restart is the step people skip.
The Collector is a Claude Code plugin, installed once per machine. It reports usage only — never a prompt, never a file's contents.
Node 22.18 or newer
Or 23.6, or any 24. The hooks run as node <file>.mjs with no build step and
import TypeScript directly. On an older Node the plugin installs, starts
cleanly and reports nothing; the session-start check names the problem.
Install it
/plugin marketplace add NotTahaAli/sessclone
/plugin install sessclone@sesscloneThe marketplace manifest lives in the SessClone repository, so there is no
second repository to add and nothing to clone. sessclone@sessclone is the
plugin named sessclone from the marketplace named sessclone; the bare
/plugin install sessclone works too while no other marketplace you have
added offers that name. Once sessclone is listed in Claude's own plugin
directory, the marketplace add step will no longer be needed —
/plugin install sessclone will be enough on its own.
Enabling the plugin prompts for two values, both optional, and answering them is the whole setup:
- Deployment URL — leave it empty to report to the hosted service at
https://sessclone.com. Self-hosted deployments enter their own address. - API key — issued in the dashboard under Keys, and shown once. See
API keys. Leave it empty in a Claude Code cloud
environment whose SessClone API credential adds it (see below); anywhere
else, leave it empty and reports go out with no
Authorizationheader, which a deployment answers the same way as an unknown key — refused.
Claude Code keeps the key in its secure credential store rather than in
settings.json, and hands
both to the hooks on every session afterwards. Nothing to export, nothing to
re-do per shell. To change an answer later,
run /plugin configure sessclone and start a new session.
This is also the only route that works in the desktop app, which runs Claude
Code with the environment a GUI application is given rather than the one your
~/.zshrc builds.
There is no environment variable for the API key — the Claude plugin
directory refuses a plugin that reads a credential from the user's
environment and sends it on, so SESSCLONE_API_KEY does not exist, and never
put a key in ~/.claude/settings.json's env either. The plugin's own setup
prompt above is the only way in for it.
Pointing at a different deployment
SESSCLONE_URL wins over the url answer given at the prompt — which is
how one terminal is pointed at a second deployment while the machine's own
answer stays as it is:
export SESSCLONE_URL=https://sessclone.example.comPut it in the profile your shell actually loads (~/.zshrc, ~/.bashrc)
and in the terminal you are in, or open a new one: editing a profile does
not change the shell that is already running. Check with echo $SESSCLONE_URL
in the terminal you will start Claude Code from. It goes in your shell's
environment, not in a file in the repository: the Collector reads the
environment only and never loads a .env.
An unset SESSCLONE_URL now reports to the hosted service
With neither the variable nor the prompt answer set, the Collector reports to
https://sessclone.com. Self-hosters must set one or the other, or a machine
quietly reports its usage to somebody else's deployment.
Claude Code's own settings file takes the same value, applied to every session and to the hooks it starts:
{
"env": {
"SESSCLONE_URL": "https://sessclone.example.com"
}
}On Windows that is %USERPROFILE%\.claude\settings.json. Create it if it is
not there; if it is, add the env key beside whatever it already holds. It is
strict JSON, so a trailing comma or a // comment is a syntax error.
Restart Claude Code
Hooks take effect on the next start, not in the session that installed them. The session you install in reports nothing, and that is expected rather than a broken install.
No Turn from before the restart is lost. The first session started after it
sweeps this machine: it drains anything the retry queue is holding, then
re-reads every recent transcript from its cursor and reports the Turns it
finds. Turns from the install session, and from every session before it that
Claude Code still has a transcript for (its own cleanupPeriodDays sweep
deletes them after 30 days by default), are backfilled that way. The sweep is
time-boxed and works newest-first, so a very large history is caught up across
several sessions rather than all in one.
What is not backfilled is a session event — the record that a turn ended on an API error, or that a session ended cleanly. Those read no transcript, so the retry queue is their only durability. Usage is always counted from the Turns themselves.
Check it worked
Start a session, send one turn, and look at Costs in the dashboard.
The first session after install, and every session while the deployment does
not accept the key (or the credential a cloud environment's proxy adds),
session start prints one line: sessclone connected: reporting to <Org>. or
sessclone is not connected: …. "Connected" prints once per key and
deployment; "not connected" repeats every session until it is fixed.
/sessclone-status shows what the last session start found, plus this
machine's side: the deployment, the key's first three characters and length,
whether it was connected and to which Org, this Device, how many of this
session's Turns are not sent yet, how many reports are waiting to send, and
the last push. /sessclone-sync drains the retry queue and resends unsent
Turns right away, without a Claude turn, and prints what it sent.
On Claude Code 2.1.287 or newer, a line above the prompt shows the same at a
glance: a green, yellow or red dot, whether the key is connected and to which
Org, whether this session is synced or how many Turns it is behind, what is
queued, and ↗, which opens this session in the dashboard. The bar and the
two commands are the plugin's
mod. Where mods
cannot load (an older Claude Code, or mods turned off), /sessclone:status
and /sessclone:sync do the same, except that status leaves out this
session's count and sync is carried out by the hook that ends the turn.
- Something arrives. Done. There is nothing else to install on this machine.
- The page still says it is waiting, and the key has never been used. The
Collector has not reached the deployment at all: wrong
SESSCLONE_URL, no network route to it, or the restart has not happened yet. - Turns stopped arriving after the deployment moved. A URL that redirects, such as an old address now forwarding to a new domain, delivers nothing: the Collector does not follow a redirect, because a 301 or 302 would turn the report into a GET, and a redirect to another origin drops the key. Set the Collector's URL, and in a cloud environment the credential's allowed website, to the new https address. Turns still on disk are sent on the next turn.
- The key has been used and no Turn has arrived. A Collector reached the
deployment with the key, so the key is not the problem. Usually no turn has
ended since the restart, or the cursor is already at the end of every
transcript; both resolve on the next turn. A genuine refusal is a
400from/api/ingest, visible in the deployment's own logs. - A misconfiguration is reported in the session itself. The session-start
check writes every problem it finds at once: a key that is not
sk_and 46 characters (a missing key is not a problem — it is optional), aSESSCLONE_URLthat is not anhttporhttpsURL, a Node older than 22.18, and a state directory that cannot be resolved.
From a clone of the repository, node scripts/verify-collector.mjs reads the
machine and prints what it found — the Node version, the state directory, the
cursor and queue files, the Device key and the transcripts on disk. It prints a
key's first three characters and its length and never more, so the output is
safe to paste to whoever is helping.
The Failures view on Costs answers a different question: turns that ended on a Claude API error — a rate limit, an overload, a billing problem. It is empty while the Collector is not reporting, and is not where a broken install shows up. The count beside the view is failed Sessions you have not looked at yet, not failure events: a rate-limit storm in one Session counts once. Mark viewed on a row, or Mark all viewed, clears it for you alone. A viewed failure stays on the list, and a new failure in that Session counts again.
In a Claude Code cloud environment
Claude Code on the web has no persistent shell to export a variable in, and the container is replaced under you. It is configured per environment, from claude.ai, before a session starts. Open the environment for editing at claude.ai/code.
-
Setup script. Paste these two lines. No
api_keyhere — the key is optional, and step 2 is what supplies the credential instead.claude plugin marketplace add NotTahaAli/sessclone claude plugin install sessclone --config url=https://sessclone.com -
API credential. Under API credentials, select Add credential (Claude's docs):
- Name:
SessClone - Credential type: Bearer
- Allowed websites:
sessclone.com - Custom headers: name
Authorization, prefixBearer, and your key from Keys as the value
- Name:
Anthropic's agent proxy adds that Authorization header to every request to
that host after it leaves the container. The Collector configured with no
api_key sends none itself, so the key never reaches the container, the
session, Claude, or the setup script, which anyone using the environment can
read. Session start reports "connected" once the proxy's credential is
accepted, exactly as it would for a key entered at the prompt.
The command and Allowed websites above name the deployment serving these
docs, from its NEXT_PUBLIC_APP_URL, so a self-hosted copy's docs already say
the right thing. If the credential is missing or wrong,
the deployment answers 401 and the next session start says sessclone is not
connected.
Claude offers API credentials on Pro and Max plans only, not yet on Team or Enterprise, and not on a self-hosted environment.
What differs in a cloud environment
- No Session ever records an end. Claude Code does not run the
SessionEndhook when a cloud container is archived, reclaimed or stopped, so the dashboard shows a cloud Session's last Turn instead of an end time. The Collector reports at every turn boundary, so the one loss is a turn still in flight when the container is killed. - Transcripts upload after every turn, when archival is on. The whole file goes each time and replaces the stored copy, which is then at most one turn behind.
- Every container collapses into one Device. The Device key is derived from
the account (
cloud:<account uuid>), so burning through containers does not litter the Devices list. SetSESSCLONE_DEVICEin the environment's variables to count several environments apart. - The state directory is inside the container. The cursor and the retry queue do not survive a reclaim.
A self-hoster's fork
The same two commands, pointed at your own repository:
/plugin marketplace add your-org/your-fork
/plugin install sessclone@sesscloneRename the marketplace in your fork — name in
.claude-plugin/marketplace.json — if anyone on your team might also have the
upstream marketplace added. Two marketplaces cannot share a name.
Nothing else in the manifest names the upstream repository. Your team's
SESSCLONE_URL is your deployment's NEXT_PUBLIC_APP_URL; nothing needs
editing inside the vendored plugin.