Setup

Setting up the daemon

claweed is the session broker — it runs on every box that hosts sessions. Install it, then run claweed setup once, as your own login. It takes no arguments and does two independent things: doctor --fix, then a report of your tenant's key state with the guidance for enrolling one.

$ curl -fsSL https://release.clawee.org/claweed/install.sh | sh
$ claweed setup            # first run, as your own login: doctor --fix, then tenant status + knock/approve guidance

The two halves are independent, and only the second is yours to complete: a host gap --fix could not repair is reported and setup carries on with the parts that need no privilege, so setup exits non-zero only when the tenant bootstrap itself failed. That bootstrap creates nothing — a tenant's trust list is built by a client knocking with its own clawee-generated key and you approving it here. If your tenant already has keys it says so and stops; if it has none it prints the two ways to get one:

tenant "alice" has no enrolled keys — knock from a clawee client and approve it here:
  claweed devices approve
or enroll one directly: claweed tenant add --data-dir ~/.clawee/data <pubkey|@file>

Under sudo it answers for you, not for root. A data root is per user, and since claweed v0.2.19 the resolution takes the tree of whoever the run is for: an explicit --data-dir, else the human the elevating tool recorded (SUDO_UID / DOAS_USER), else the account the process is — root's own included. Through v0.2.18 it refused there instead, which left one deployment with no repair path at all: on a host with no boot unit, sudo claweed doctor --fix resolved nothing, the data-dir row read ?, tenants and sessions went unmeasured, and the offered restart failed against a service that was not there.

sudo claweed auth is fixed with it. The tenant name used to come from the real uid while the data tree came from the elevation record, so the two named different accounts: the key landed under tenant root inside your ~/.clawee/data — an enrolment no child ever reads — and the directories created along the way stayed root-owned, locking you out of your own deployment. Both now come from one source, and a root process creating a tree inside your home hands back every directory it made.

A box you administer as root is a different case, and works: root is a login like any other, its tree is /root/.clawee/data (the installer creates it, and its uid is admitted to host sessions at install), and claweed setup run as root bootstraps root's own tenant exactly as it does for anyone else. Through v0.2.14 the older refusal caught root too — setup answered root is not a tenant, devices list said a root process owns no data root, and doctor could not even name the folder the installer had just made.

setup no longer imports ~/.ssh/authorized_keys. Through v0.2.8 it offered to enrol every key in the invoking user's authorized_keys as that user's first trusted device; v0.2.9 removed the import (operator ruling, 2026-08-24). It was the wrong key material: a clawee client presents a key it generated, and that is what claweed authenticates — the host's authorized_keys holds the client machine's ssh keys, which claweed never checks, and claweed separately puts its own key there for the pty session, a different direction entirely. Enrollment is knock + claweed devices approve, or a direct claweed auth / claweed tenant add, only.

Standard users, and the consented elevation

A standard (non-admin) user can run setup. Part of what doctor --fix repairs is root-owned — most often the host's spawn-allow list, which names the uids permitted to host sessions — and a user with no route to sudo cannot write it. claweed asks first, and the prompt names the write:

fix: permit uid 502 to host PTY sessions by adding it to /usr/local/clawee/etc/spawn-allow, as root through an elevation prompt? [y/N]

Answer y and claweed escalates through the OS, never holding the credential itself — the dialog, pkexec or su each collect the password on their own surface. The rungs are tried in order and the first that produces the write ends it: on macOS sudo -n, the macOS authorization dialog (GUI sessions only), then su <admin>; on Linux sudo -n, pkexec (GUI only), su, then su <admin>. The last rung is always the command printed for an administrator to run by hand, so a declined or unavailable elevation is never a dead end — the gap is reported and setup still exits 0.

The elevated write is one narrow verb, claweed policy allow-uid <uid>: the host's spawn policy, root-owned — which uids may host sessions on this box. It appends one uid and never truncates (the file usually already names other accounts, and a truncating write would revoke them), is idempotent, and leaves the file root-owned 0644. Mind the polarity before you run it: the list is an opt-in restriction — while the file is absent every uid is permitted, and this verb creates it (root-owned 0644) when it isn't there. On a host that has no allowlist yet, one append turns permit-everyone into permit-only-this-uid. Run it yourself when you would rather not elevate from inside doctor:

$ sudo claweed policy allow-uid 502
uid 502 added to /usr/local/clawee/etc/spawn-allow
$ sudo claweed policy allow-uid 502     # again — idempotent
uid 502 is already permitted by /usr/local/clawee/etc/spawn-allow

Enrolling a different device's key — not this host's own login — is a separate step: see Devices & pairing.

Health checks

Doctor & status

Both sides speak the same two verbs. On the host, claweed doctor [--fix] [--yes] diagnoses the deployment and repairs service-user-owned bits (--yes skips consent prompts); claweed status is its read-only alias — same report, the same offer to re-read as root what an unprivileged run could not, and no --fix.

claweed restart restarts the managed service. Run as an ordinary user on a host whose service is root's, it offers to re-run itself through the same consented elevation as doctor (below) once every plain attempt has failed for lack of privilege; without a terminal it only says why it needs root. Under a guard it skips signalling the payload's pid, so a failure for lack of root goes straight to that same consented offer. After any restart on a host with a boot unit it waits briefly (up to 10s) for the payload, and the guard if the unit runs one, to report the installed build, exits non-zero naming which one if not, and offers the elevation once more when a plain restart came back stale. The report's rows include the local link, shared uploads and — on macOS — the aqua host, and registered names the running daemon's own record of its register link.

On the client, clawee doctor [--fix] [--yes] diagnoses the local setup and always prints the clawee + claweed versions — even when nothing connects; clawee status probes the chain hop-by-hop (socket → daemon → relay → claweed → auth) and shows exactly where a connection stops.

claweed doctor, run on the session host, reports the installed daemon version alongside the version actually running — if they differ (payload, or the guard's digest), the row is a failure that names which and gives claweed restart.

Every row in the report carries one of four marks:

✓The check passed.
!Measured, but not a clean pass — a caveat, or a self-reconciling mismatch. Informational: never a failure, and never counted toward an exit code.
✗The check failed. Still information, not a verdict on the command: a ✗ row never makes doctor itself exit non-zero — the installer runs it as doctor … || true on exactly that contract.
?Not measured — this run could not read the input. Not a fault.

? joined the other three in claweed v0.2.12, splitting the unmeasured rows out of the caveat mark (printed ⚠ then, ! now — the report prints no emoji). The common one is the register socket, which is 0600 and root-owned by design, so an unprivileged doctor can never read it — that is the design working, not a defect. A report that rendered a ? prints the legend once, directly under the check rows and above the operational and versions sections, so you meet it in the same glance as the mark; a report with none of them omits it:

? register socket   /…/register.sock: needs root to read (0600 root-owned by design) — not a fault; re-run as root to confirm

? = not measured — this run could not read the input; not a fault

The other ? you can meet is the data dir row, when no data dir resolved at all — doctor under sudo with no --data-dir, or a login with no resolvable home. It prints the resolver's own reason and the flag that names a tree, and the tenants and sessions rows, which read the same root, point at it. Through v0.2.13 that state was mis-read as a missing directory: doctor --fix tried to mkdir an empty path and setup ended with data dir missing; create failed: mkdir : no such file or directory over a host with nothing wrong.

? data dir          none resolved — running under sudo, so the euid default would be root's own tree, not yours (sudo from uid 501): …; pass --data-dir <dir> to inspect one deployment
? tenants           no data root resolved (see the data dir row) — enrollment was not measured
? sessions          no data root resolved (see the data dir row) — no session record was read

Maintenance

Update

Both components self-update in place from the signed release channel:

$ clawee update             # shows the version gap; skips if current
$ clawee update --auto      # install the update, unattended
$ clawee update --dry       # report only
$ clawee update --force     # reinstall the latest regardless
$ claweed update            # same, for the daemon
$ claweed update --auto     # apply unattended; the installer restarts the service only if changed
$ claweed setup             # first run: doctor --fix, then tenant status + knock/approve guidance (see Setup)

claweed update never restarts the service itself: the installer restarts it, swaps the payload, or — when the unit runs claweed-guard — lets the guard move itself onto the new binary (a SIGHUP; the guard exits for the supervisor to start the installed build). Afterwards, and also when the release was already current, update waits briefly for the service to report the installed build (the payload's running version and, under a guard, the guard's own digest). If it does not, or the unit failed to start, update and the installer exit non-zero, naming payload or guard and claweed restart. The first update onto a guard that can move itself is still a full service restart; later guard changes need no restart command.

Live sessions: a guard change ends them the way a unit restart does on Linux (the unit's cgroup is stopped); on macOS the sessions survive.

upgrade.sh is not how you upgrade — install.sh is. It's a forcing entry point for the migration phase, and its argument is an inclusive floor: <version> means assume this host is below <version>, so every migration rung targeting that version or newer is selected — the recorded anchor is not read and the per-rung version gate is bypassed. Rungs targeting anything older are skipped as genuinely done. That inclusiveness is what the normal ladder gate can't express: it skips a rung targeting exactly the named line, so a same-version backfill never ran. It installs nothing and records nothing, and refuses only a floor newer than the ladder's newest target:
$ curl -fsSL https://release.clawee.org/claweed/upgrade.sh | sh -s -- <version>

Operations

Service unit

The daemon installs itself as a managed boot unit — a launchd .plist on macOS, a systemd .service on Linux. claweed service print renders that unit to stdout, given the same paths and run-as identity the installer already resolved — useful for inspecting what got installed, or for a manual install on a box the installer can't reach directly. --no-local renders a unit that binds no local link.

On macOS, claweed service print-aqua --host <path> renders the LaunchAgent .plist of the aqua session host — what runs a session created with clawee sessions new --aqua inside the gateway user's desktop login session instead of the system one, for work that needs the login keychain or a GUI prompt. That user must be logged in.

Operations

Local link & shared uploads

claweed serves a local link — a unix socket that a clawee on the same machine reaches without burrowee (the local box). The setting is persisted in the config root and survives updates; changing it needs root.

claweed local on|off|statusServe the local link, or not (off creates the config root's no-local marker); status is readable without root.
claweed socketPrint the local-link socket path (readable without root).
claweed serve --no-localRun without the local link; --socket now names the local-link socket path.

Several OS users on one host can share uploads: with sharing on, each user's uploads tree moves under one root-owned shared root, one sub-tree per user — readable by every account on the host, writable only by its owner.

claweed shared-uploads on [--root <dir>]Create the shared root and write the config root's marker naming it (root).
claweed shared-uploads offRemove the marker; files under the shared root stay where they are.
claweed shared-uploads statusReport which, the root and its owner and mode — readable without root.

Operations

Tenant management

claweed tenant manages your own tenant enrollment — the enrollment claweed setup reports on but no longer creates for you, and the same one claweed auth / claweed tenant add sugar over for a second device (see Devices & pairing).

Upgrading is one-way. Once a v0.2.x clawee writes config.json or a session's meta.json in the multi-user schema, an older clawee binary refuses the file outright rather than silently misreading it — there is no downgrade path. Rolling a fleet back means rolling its config back with it. On the daemon side, the instance id lives in the system config root (moved there by the v0.2.0 migration ladder) and is an auth-challenge input: a regenerated value silently refuses every already-enrolled device. claweed never generates one on its own — only the installer's migration adopts it — so treat that file as load-bearing, not disposable.

Reference

Troubleshooting

Quick diagnostic rows, condensed from doctor/status on both sides:

clawee doctor [--fix] [--yes]Diagnoses the local setup and repairs what it can, and always prints the clawee + claweed versions — even when nothing connects.
claweed doctorRun on the session host — reports the installed daemon version alongside the version actually running; if they differ the row fails, naming payload or guard and claweed restart.
clawee status / claweed statusRead-only aliases of doctor — same report, no --fix.
claweed restartRestart the managed service — offering the consented elevation when it needs root.
clawee statusProbes the chain hop-by-hop (socket → daemon → relay → claweed → auth) and shows exactly where a connection stops.
claweed setupFirst run for the invoking user: doctor --fix, then that user's tenant status with knock/approve guidance. It enrols no keys itself.
claweed policy allow-uid <uid>Permits one uid to host sessions on this box — appends it to the root-owned spawn-allow list, creating it when absent. This is the write doctor --fix elevates. The list is an opt-in restriction: with no file present every uid is permitted, so on a host without one the first run narrows it to just that uid.

Reference

CLI reference

Verbatim help pages for the daemon-operations verbs, generated by claweed docs from the binary's own help renderer.

claweed setup

first-time setup for the invoking user — doctor --fix, then tenant status + knock/approve guidance

claweed setup — first-time setup for the invoking user — doctor --fix, then tenant status + knock/approve guidance

Usage:
  claweed setup

claweed doctor

diagnose the deployment; --fix repairs service-user-owned bits

claweed doctor — diagnose the deployment; --fix repairs service-user-owned bits

Usage:
  claweed doctor [flags]

  doctor
           [--allowlist <path>]              spawn-allow uid list path
           [--data-dir <dir>]                data dir (default: installed boot unit)
           [--fix]                           repair failed checks (asks consent)
           [--register-socket <path>]        gateway register socket path (default: installed boot
                                             unit)
           [--service-home <dir>]            service home dir (see --service-uid)
           [--service-uid <uid>]             service uid (default: the --data-dir owner; set by a
                                             root re-read)
           [--service-user <user>]           service user (see --service-uid)
           [--sessions-verdict <verdict>]    relayed sessions verdict (root re-read; display only)
           [--socket <path>]                 local-link unix socket path (default: installed boot
                                             unit)
           [--system-uid-allowlist <path>]   system-uid opt-in path
           [--yes]                           skip consent prompts (unattended --fix)

claweed status

alias of doctor (read-only) — same report, no --fix

claweed status — alias of doctor (read-only) — same report, no --fix

Usage:
  claweed status [flags]

  status
           [--data-dir <dir>]           service data dir (default: installed boot unit, then the
                                        tree of the user this run is for — under sudo the human who
                                        elevated, otherwise this account (root's own included))
           [--register-socket <path>]   gateway register socket path (default: installed boot unit)
           [--socket <path>]            local-link unix socket path (default: installed boot unit)

claweed update

update claweed from release.clawee.org

claweed update — update claweed from release.clawee.org

Usage:
  claweed update [flags]

  update
           [--auto]    apply unattended; restart only if changed
           [--dry]     print the update plan only, then exit
           [--force]   reinstall everything (binary + spawn policy + boot-unit) + restart

claweed service print

render the boot unit — a launchd .plist / systemd .service — to stdout

claweed service print — render the boot unit — a launchd .plist / systemd .service — to stdout

Usage:
  claweed service print [flags]

  print
          [--claweed <path>]           path of the binary the unit execs: claweed-guard, or claweed
                                       for a kit without one (required)
          [--group <group>]            run-as group (optional)
          [--home <dir>]               service user's home dir — the unit's HOME (always required)
          [--kind <kind>]              unit kind: launchd | systemd (required)
          [--log <path>]               launchd stderr/stdout log path (optional)
          [--no-local]                 render serve --no-local: bind no local link (claweed local
                                       off is the persisted setting)
          [--register-socket <path>]   claweed --register-socket path (production)
          [--root-scheme]              root daemon, no run-as user (excludes --user/--group; --home
                                       still required)
          [--socket <path>]            claweed --socket path (dev)
          [--user <user>]              run-as user (required unless --root-scheme)

claweed service print-aqua

render the aqua session host's LaunchAgent .plist (macOS) to stdout

claweed service print-aqua — render the aqua session host's LaunchAgent .plist (macOS) to stdout

Usage:
  claweed service print-aqua [flags]

  print-aqua
               [--host <path>]   aqua host binary path — the installed host beside claweed
                                 (required)

claweed tenant

manage YOUR OWN tenant enrollment

claweed tenant — manage YOUR OWN tenant enrollment

Usage:
  claweed tenant <command>

Commands:
  add                        enroll a trust key for YOURSELF
        <pubkey>             the public key line itself
        @<file>              read the public key from the named file
        [--data-dir <dir>]   claweed data dir (required; `claweed auth` resolves a default)

claweed policy

host spawn policy (root-owned)

claweed policy — host spawn policy (root-owned)

Usage:
  claweed policy <command>

Commands:
  allow-uid                          permit one uid to spawn sessions on this host
              <uid>
              [--allowlist <path>]   spawn-allow uid list path

claweed policy allow-uid

permit one uid to spawn sessions on this host

claweed policy allow-uid — permit one uid to spawn sessions on this host

Usage:
  claweed policy allow-uid <uid> [flags]

  allow-uid
              <uid>
              [--allowlist <path>]   spawn-allow uid list path

claweed local

serve the local link on this host, or not (persisted; root to change)

claweed local — serve the local link on this host, or not (persisted; root to change)

Usage:
  claweed local <on|off|status>

  local
          on       serve the local link (removes the config root's no-local marker)
          off      do not serve the local link (creates the marker; survives updates)
          status   report which — readable without root

claweed socket

print the local-link socket path (readable without root)

claweed socket — print the local-link socket path (readable without root)

Usage:
  claweed socket

claweed shared-uploads

share every tenant's uploads under one root-owned tree (persisted; root to change)

claweed shared-uploads — share every tenant's uploads under one root-owned tree (persisted; root to change)

Usage:
  claweed shared-uploads <on|off|status> [flags]

  shared-uploads
                   on               create the shared root and write the config root's marker naming
                                    it
                   off              remove the marker; files under the shared root stay where they
                                    are
                   status           report which, the root and its owner/mode — readable without
                                    root
                   [--root <dir>]   the shared root dir (with on)

See the full generated references: clawee CLI reference · claweed CLI reference.

Docs synced 2026-10-17 against clawee v0.3.46 · claweed v0.3.29 — a partial pass over the guard-aware update and restart, the tab title that shows a session asking for you, and the installer exit codes, on top of the v0.3.42 catch-up. See docs_sync.json for what is not covered.