sessclone

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@sessclone

The 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 Authorization header, 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.com

Put 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:

~/.claude/settings.json
{
  "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 400 from /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), a SESSCLONE_URL that is not an http or https URL, 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.

  1. Setup script. Paste these two lines. No api_key here — 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
  2. API credential. Under API credentials, select Add credential (Claude's docs):

    • Name: SessClone
    • Credential type: Bearer
    • Allowed websites: sessclone.com
    • Custom headers: name Authorization, prefix Bearer, and your key from Keys as the value

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 SessionEnd hook 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. Set SESSCLONE_DEVICE in 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@sessclone

Rename 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.

On this page