Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/docs/2-handbook/swarm/claude-login.md
iris 5f5933fde9 docs, swarm ui: a deleted login now logs the agent out, and the dialog and docs say so
With the harness poll, an agent that holds a session logs out within 5
minutes of its stored login being deleted. The confirmation and the
claude-login page said the agent keeps its session.
2026-11-09 23:03:08 +01:00

7.5 KiB

Claude login from the swarm UI

An operator logs an agent in to claude from the agent's card in the swarm UI. No hive is in the path: the login runs in the swarm's swarm-claude-login service, the credentials go to the agent's own leaf in the secret store, and the agent's harness installs them from there.

Where it runs and how it's enabled: services.md.

The service runs the claude-code that its host's services.hyperhive.deploy.hive-controller.claudeCodePackage pins for agents, and its container's own nixpkgs' where the host pins none.

What happens on a login

  1. The agent publishes needs_login in its turn-state header, and its card shows login needed with a log in button.
  2. log in opens a dialog, which calls POST /api/agents/<agent>/claude-login. swarm-controller relays the start as a request on the queue subject $SWARM.claude-login.request.
  3. The minter runs claude auth login with HOME and CLAUDE_CONFIG_DIR in a fresh scratch directory, over plain stdio pipes, and answers with the first https:// URL it prints. The dialog shows that URL.
  4. The operator opens the URL, signs in to the agent's own claude account, and pastes the code the page shows into the dialog, which posts it to POST /api/agents/<agent>/claude-login/code.
  5. The minter writes the code to claude's stdin and waits for it to exit. It stores the .credentials.json claude wrote at swarm/agents/<agent>/claude-credentials (field value), as a new version over any stored before, and removes the scratch directory.
  6. The agent's harness reads that leaf under the agent's own certificate: every 30 seconds while it waits for a login, every 5 minutes while it has a session, and once as a paused agent resumes. When the stored version is newer than the one it installed last, it writes the file to ~/.claude/.credentials.json, records the version, and, for an agent that waited, resumes its turn loop as it does after a login from the agent's web UI.

A started login waits 15 minutes for its code, then the minter stops it. Starting another login for the same agent replaces the running one. Each step has a minute to finish; a refused code fails the login, and the dialog shows what claude printed.

The stored login

The agent's card shows when the swarm stored its login for it, or that none is. The swarm UI reads it with GET /api/agents/<agent>/claude-login, which the minter answers from the leaf's metadata: it can say the login exists and when it changed, and never reads the credential.

delete login on the card calls DELETE /api/agents/<agent>/claude-login/stored. The minter removes the leaf and every version of it, not just the newest: an older version is still a session. An agent that already holds a session logs out too, the next time it reads the store: within 5 minutes, or when a paused agent resumes (see the harness's rule).

Why the harness takes only a newer version

claude rotates the refresh token in .credentials.json as it uses the session, and the agent can't write the store. After the first refresh the stored copy is stale. A harness that installed it whenever the bytes differed would replace the agent's live session with a stale copy and log the agent out. The harness therefore compares the store's KV version with the last version it installed (claude-credentials-version under the harness dir):

  • it installs a newer version, that is, a new login;
  • it leaves an equal or older version alone, even when the agent needs a login again, so it never puts back a session that has gone bad;
  • it logs the agent out when the store holds nothing although a version was installed: the operator deleted the stored login. A read that failed never does it, since an unreachable store says nothing about the login. The logout is the one /api/logout does, and the recorded version is forgotten so the next login installs.

Credentials

Principal Grant
swarm-claude-login, in its container create, update on swarm/agents/+/claude-credentials; read, delete on that leaf's metadata; read on swarm/services/<minter client id>/oidc/client
the agent, under its own certificate read on its own swarm/agents/<agent>/*, which covers the leaf

The minter has no read on the leaf's value and nothing else under an agent. Every hive's policy reads swarm/agents/*, so every hive can read every agent's claude session, as it can read the GitHub token.

The operatorsOnly authelia rule on the swarm domain, not the controller, keeps these routes to operators: the controller checks only that the gateway named a user. Without that rule any account authelia admits could store its own claude session as any agent's, so no agent account may exist before the rule reaches the swarm.

The minter refuses a step whose agent name isn't an identifier before it makes a directory or runs claude.

Caveat

The installed-version record lives under the harness dir and the credentials file under ~/.claude. If only the record survives a rebuild, the agent ignores the stored version until the operator logs it in again, which writes a newer one.

What stays

The agent's own web UI login (/login) still works, unchanged. It goes away once the operator has confirmed the swarm login works.

Observability

The minter pushes OTLP metrics through its container's forwarder to the swarm collector, labelled service.name=hyperhive-claude-login. With no OTEL_EXPORTER_OTLP_ENDPOINT it exports nothing. The client-certificate gauges (bao.client_cert.*) come with them.

Metric Unit Kind Attributes
hyperhive.claude_login.steps — counter step: start, code, cancel, status, delete, invalid; outcome: started, stored, cancelled, login, deleted, failed
hyperhive.claude_login.step.duration s histogram step
hyperhive.claude_login.sessions.expired — counter —
hyperhive.claude_login.sessions.active — gauge logins waiting for a code, as of the last step

invalid is a request that didn't decode or named no agent. A start spawns claude auth login and a code stores the credentials, so those two are the slow steps. expired counts logins stopped because their code never came.