Remote access setup reference
This page is for the person setting up Shiba or diagnosing a connected computer. To pair an already configured installation, use the Remote access guide.
Turn it on
Section titled “Turn it on”Remote Access is off by default. The /api/local/* and /api/computers* routes return 404 until both of these are true:
SHIBA_LOCAL_RUNTIME=1is set on the Worker.- The
LocalDispatchDurable Object is bound.
Pairing tokens are signed with PAIRING_SECRET. If that is unset, the Worker signs with LOCAL_ADAPTER_TOKEN instead. If neither is set, minting a pairing token returns 503. With Remote Access off, the dashboard’s Remote Access view says the local runtime is disabled.
Cloudflare Access
Section titled “Cloudflare Access”Alchemy deploys create a second Access application (MachineBypass) that covers /api/local/* and the other machine-caller routes with a bypass policy, because a daemon cannot complete an interactive Access login — it authenticates inside the Worker with its adapter bearer. If you deploy with wrangler or front the Worker with your own Access configuration, create the equivalent yourself: an Access application on <worker-host>/api/local/* with a bypass / everyone policy. Without it the daemon’s polls get the Access HTML login page instead of the API, and pairing fails. The bypass does not open the Worker — every /api/local/* route still requires a valid adapter bearer token; it only removes the IdP challenge.
Pair a machine
Section titled “Pair a machine”-
In the dashboard, open Remote Access and click Add computer.
-
Copy the connect command. It looks like this:
Terminal window node scripts/shiba-local-daemon.mjs --connect https://shiba.example.com --pair <token> -
Run it from a checkout of this repository on the machine you want to pair.
The pairing token works once and expires after 15 minutes; the modal shows a countdown. The daemon sends the token to POST /api/local/pair. The Worker checks the token’s HMAC signature and expiry, then burns its nonce, so the same token can’t be used twice. On success the Worker returns a new adapter token and a machine id, and the daemon starts polling.
If the token was expired, already used, or mistyped, the daemon exits with a message telling you to mint a new one. Mint a new token and run the command again.
Files on the machine
Section titled “Files on the machine”The daemon keeps its state in ~/.shiba-local/. Set SHIBA_LOCAL_RUN_ROOT to use a different directory.
| File | Contents | Mode |
|---|---|---|
config.json |
{workerUrl, adapterToken, machineId}, written by --connect/--pair |
0600 |
machine-id |
A random UUID, created once and reused across restarts | 0600 |
The directory is created with mode 0700. Pairing again overwrites config.json and tightens its permissions to 0600.
The older environment-variable setup still works. If SHIBA_WORKER_URL and LOCAL_ADAPTER_TOKEN are set, they take precedence over config.json. In that mode the daemon authenticates with the deployment-wide bearer, not a per-machine token.
Heartbeat and status
Section titled “Heartbeat and status”At startup the daemon checks which harnesses are installed by running claude, codex, opencode, and agy with --version. Each check has a 5-second timeout. Only the names of harnesses that respond are reported; version strings are not.
The daemon then sends a heartbeat to POST /api/local/heartbeat every 30 seconds. Each heartbeat includes the machine id, hostname, platform (process.platform-process.arch, such as darwin-arm64), daemon version, harness list, and the id of the run in progress, if any.
The dashboard polls /api/computers every 10 seconds and shows one of three statuses:
| Status | Meaning |
|---|---|
busy |
The last heartbeat reported an active run. |
idle |
The last heartbeat arrived within 75 seconds and reported no active run. |
offline |
No heartbeat for more than 75 seconds. |
Expect some lag. A machine can show idle or busy for up to about 30 seconds after the real change, plus up to 10 seconds of dashboard polling. A machine that stops reporting shows offline only after 75 seconds, which is two missed heartbeats plus slack.
A failed heartbeat caused by a network error is logged, and the daemon tries again on the next tick. Records for machines that have not sent a heartbeat in 7 days are deleted. A deleted machine has to be paired again.
Revoke a machine
Section titled “Revoke a machine”Click Revoke on a machine and confirm. This calls POST /api/computers/revoke, which deletes the machine record, including the stored hash of its adapter token. The machine’s next heartbeat or claim then gets a 401. The daemon logs adapter token revoked; re-pair and exits with code 78, so a process supervisor can treat it as a configuration error instead of restarting it in a loop.
Things revocation does not do:
- The Worker can’t cancel a run that is already in progress on the machine. The daemon only exits after its next heartbeat or claim gets a
401, which can take up to about 30 seconds. - It can’t revoke a daemon running in environment-variable mode. That daemon uses the deployment-wide
LOCAL_ADAPTER_TOKEN, so you have to rotate that secret to cut it off. - It does not delete
~/.shiba-local/on the machine. Remove the directory yourself if you are retiring the machine.
Desktop app
Section titled “Desktop app”Shiba Desktop (macOS only) wraps the same dashboard in an app and adds one-click pairing. It only makes outbound calls and adds no approval path: local runs still need the approval card in the dashboard.
Install
Section titled “Install”Shiba Desktop is a local macOS prototype. If your operator provides a build, follow their installation instructions. Public signed distribution is not established by these setup notes.
If you build locally with pnpm --filter @shiba/desktop dist, the unsigned app is killed at launch on Apple silicon (Code Signature Invalid) because the fused binary keeps an invalid signature — re-sign it ad-hoc once:
pnpm --filter @shiba/desktop dist:local # builds, then ad-hoc signs the .appFirst run
Section titled “First run”The app asks for your Shiba URL, then loads the dashboard. Sign in as usual. Only that origin and your sign-in providers load inside the app; other links open in your browser. Change Shiba URL… in the tray menu reopens the prompt.
Connect this computer
Section titled “Connect this computer”Open Remote Access and click Connect this computer. The app pairs this machine to the Shiba URL you configured and starts the daemon, so you don’t copy a command or token. The machine then appears in the fleet list as This computer. Disconnect this computer in the tray menu unpairs it.
Tray states
Section titled “Tray states”| State | Tray label | What it means |
|---|---|---|
| Signed out | Sign in to Shiba | You aren’t signed in to the dashboard. |
| Disabled | Local runs disabled on this Shiba | The Worker has the local runtime off. |
| Not connected | Not connected | This computer isn’t paired. |
| Paired elsewhere | Paired to another Shiba | This computer is paired to a different URL. Reconnecting asks you to confirm. |
| Connected, idle | Connected · idle | The daemon is polling. |
| Busy | Running · owner/repo | A local run is in progress. |
| Offline | Offline · retrying | Heartbeats are failing. The daemon retries by itself. |
| Revoked | Revoked · reconnect | The machine was revoked. The app doesn’t restart the daemon; connect again. |
| Conflict | Another daemon is running | A CLI or other daemon holds this machine’s lock. The app doesn’t restart it. |
| Crashed | Daemon crashed · View logs | Five abnormal exits within 10 minutes. The app stops restarting it. |
Other abnormal exits restart the daemon after 1s, 2s, 4s and so on, up to 60s. The harness list can show agy. It is listed but can’t run local tasks.
The app writes <userData>/logs/main.log and daemon.log. On macOS <userData> is ~/Library/Application Support/Shiba. Each file rotates at 5 MB and keeps one .1 copy. View logs in the tray menu opens the folder.
Notifications
Section titled “Notifications”The app notifies you when an approval is waiting and when a run finishes. Clicking either opens the dashboard. Notifications show only the title and owner/repo, and you can never approve or reject from one. Toggle them with Notifications in the tray menu.
Start at login and quitting
Section titled “Start at login and quitting”Start at login in the tray menu launches the app hidden at login. Closing the window hides the app to the tray. Quit from the tray menu or with Cmd+Q. If a local run is in progress, the app asks first. Quitting stops the run and reports it failed.
Uninstall
Section titled “Uninstall”Quit the app, delete Shiba from Applications, and remove ~/.shiba-local to drop the pairing and machine id. Revoke the machine from the dashboard if you want its token invalidated on the Worker.
Security model
Section titled “Security model”- The approval gate does not change. Every local run still requires the approval card and the same approval checks as any other run. Pairing a machine only gives the daemon a way to receive work you have already approved.
- Provider credentials never pass through the Worker. The machine’s harnesses use the credentials already on the machine. The only credential the Worker issues is the adapter token, which is a Shiba bearer token, not a provider key.
- The Worker never stores adapter tokens in plain text. It keeps only a SHA-256 hash, and the dashboard never shows the token. The plain token exists only in the pairing response and in the machine’s
config.json. - Pairing tokens are short-lived and single-use. Each token is signed with HMAC-SHA256, expires after 15 minutes, and its nonce is deleted the first time it is used.
- Only the dashboard can start local work. Local runs are created from the dashboard only. The
/api/computers*routes require dashboard sign-in, the daemon bearer only reaches/api/local/*, and no chat surface can reach either set of routes.
Limits
Section titled “Limits”- This is built for a single tenant. If
PAIRING_SECRETis unset, pairing tokens are signed with the deployment’sLOCAL_ADAPTER_TOKEN. - Checking a daemon’s token reads through the whole fleet on every daemon call. That is fine for a handful of machines and has not been measured for large fleets.
- Status lags reality by up to about 40 seconds for a change and 75 seconds for going offline. Use the run timeline, not the fleet view, to see what a task is doing.
- The connect command assumes the machine has a checkout of this repository and Node.js 22.12.0+ installed.
Return to Remote access.
Troubleshooting (desktop + daemon)
Section titled “Troubleshooting (desktop + daemon)”| Symptom | What to check |
|---|---|
| Tray stays on Sign in to Shiba after you signed in | The app URL must match BETTER_AUTH_URL (better-auth) or your Access session must cover the configured origin. Close and reopen the window, or use Change Shiba URL… if you pointed at the wrong deployment. |
| Local runs disabled on this Shiba | Set SHIBA_LOCAL_RUNTIME=1 and bind LocalDispatch on the Worker, then reload Remote Access. |
| Connect this computer fails immediately | Mint a fresh pairing token (15-minute TTL, single use). Ensure PAIRING_SECRET or LOCAL_ADAPTER_TOKEN is set so pairing-token does not return 503. |
| Pairing command or daemon logs show HTML | Add an Access Bypass (or equivalent) for <your-host>/api/local/* so the daemon reaches the Worker API, not the IdP login page. |
| Fleet list empty but tray says connected | Wait for the next heartbeat (up to ~30s) and fleet poll (10s). Revoke stale rows if you reinstalled without revoking. |
| Local run queued but never starts | Pair only one machine for testing; choose Local (Paired computer) and the correct hostname in the composer so targetMachineId matches. Approve on the dashboard card — notifications only open Approvals. |
| Another daemon is running | Stop a CLI shiba-local-daemon.mjs or another Shiba Desktop instance; only one process may hold daemon.lock. |
For the full manual acceptance checklist (signing, fuses, notifications), see VERIFICATION/desktop.md at the repository root.
