# Managing your devices

Source: https://codeherder.com/docs/devices/

How to manage your devices — names, load, concurrency, and usage limits.

A **device** is a machine running `ch device-server` for CodeHerder. The device runs the actual agent processes — checking out branches, executing the configured AI binary, and reporting the results. Every session happens on a device.

This page covers day-to-day device management. For the first-time steps — connecting a device and linking it to a workspace — see [How do I add a device?](https://codeherder.com/docs/adding-a-device/) for the focused version, [Launch a device on AWS](https://codeherder.com/docs/launch-a-device-on-aws/) if you’d rather not run one yourself, or the [Quickstart](https://codeherder.com/docs/quickstart/) for the full path from a fresh account.

## Online and Offline

A device shows **Online** when its `ch device-server` process is running and connected to CodeHerder. Stop or kill that process and the device goes **Offline**. The engine places no new work on an Offline device; tasks that were waiting for a slot on that device queue until the device comes back Online.

The current state of each device is visible under **Set up → Devices** in the sidebar.

## Naming a device

By default, a device is identified by its **hostname** — whatever its operating system reports. That works fine for a laptop or a named server, but it’s not much help on a `--docker` fleet device (see [Docker container (trusted mode)](https://codeherder.com/docs/running-the-device-server/#docker-container-trusted-mode)), where the hostname is just a generated container ID.

Give a device a name of your own choosing at any time — it’s safe to do even while the device is busy. Renaming only changes the label: the device keeps its ID, its connection, its workspace links, and anything already running on it keeps running without interruption.

```
ch device edit <deviceId> --name "prod-runner-1"
```

The name replaces the hostname as the label a device is shown under — the devices list, a device’s detail page, session and sandbox listings, and anywhere else `ch` or the web app prints a device. It replaces the hostname as a CLI reference too: once a device has a name, use that name (not its hostname) with `ch device show`, `ch agent placement`, and anywhere else you’d point at it by name. See [Referring to things on the command line](https://codeherder.com/docs/using-the-cli/#referring-to-things-on-the-command-line) — and keep names distinct, since two devices sharing a name makes that reference ambiguous the same way any other duplicate name does.

The Devices page’s search box is the one place both still work: it matches on either the name or the hostname, so a device stays findable by its old hostname even after you’ve named it. If you can’t see host details (see [Who sees host details](https://codeherder.com/docs/devices/#who-sees-host-details)), search matches the name only.

An unnamed device is labelled with its hostname, so everyone who can see the device sees that label. If you don’t want that, give the device a name.

A name is trimmed of surrounding whitespace and capped at 128 characters. Clear it to go back to displaying the hostname:

```
ch device edit <deviceId> --name ""
```

**Web app:** open the device’s detail page (**Set up → Devices → [the device]**) and edit the **Name** field directly. The **Hostname** field below it always shows the raw reported hostname, named or not.

Only a workspace owner or admin can rename a device — the same rule that applies to changing its capacity, pausing (draining) it, or deleting it. Registering the device yourself carries that same permission, but only for as long as you still hold at least admin rank; a plain member who registered a device doesn’t otherwise get a bypass. A member outside that role sees the device’s name and capacity as plain text rather than editable fields, and doesn’t see the Enable/Disable control on the device’s row. See [Why a control is missing](https://codeherder.com/docs/members-and-teams/#why-a-control-is-missing).

## Device health and readiness checks

In addition to the Online/Offline connection state, CodeHerder continuously checks whether a device is actually ready to run work. These **readiness checks** run on the device every few minutes and report back to the platform.

### Health states

Each device has one of three health states, shown as a badge in the **Health** column in **Set up → Devices**:

| State | Badge colour | What it means |
| --- | --- | --- |
| **healthy** | green | All checks are passing — the device is ready for new sessions. |
| **degraded** | amber | At least one check is in a warning state, or has failed in a way that doesn’t block work. Nothing here prevents the device from running tasks. |
| **unhealthy** | red | At least one check has failed in a way that blocks new work. The engine will not assign new sessions to this device until it recovers. |

When a device can’t be staffed, a **Not staffable** badge appears next to the health badge in the devices list. Both badges show together — they are two separate facts, so one never replaces the other. The badge means at least one check is blocking new work on that device. It is tied to the unhealthy state and disappears as soon as the failing check is resolved.

### Viewing the health snapshot

Click a device’s row in **Set up → Devices** to open that device’s page. The **Health** panel on that page shows:

- The current health state and when the snapshot was last taken.
- Any failing or warning checks, each with a short summary, detail text, and a remediation tip.
- Every passing check, in the same flat list. A passing check shows its status and summary only, with no detail text and no remediation tip.

Only the device’s owner and workspace admins see the checks. Everyone else sees the health badge and the notice “Check details are visible only to the device owner and workspace admins.” Ask the owner or an admin to read the checks for you. See [Who sees host details](https://codeherder.com/docs/devices/#who-sees-host-details).

### Who sees host details

Only the device’s owner, and admins and owners of a workspace the device is linked to, see the device’s hostname, operating system, boot time, launcher version, check details, probe-derived capabilities, and acknowledged secrets. In the web app, each hidden field reads “Visible only to the device owner and workspace admins.” In the CLI, `ch device show` prints `host details: host details are visible only to the device owner and workspace admins`, and `ch device list` prints `—` under HOSTNAME and OS.

Everyone else still sees the device’s name, online state, health badge, CPU, RAM and disk readings, version, drain state and capacity. The full list of limited details is in [What only admins can see](https://codeherder.com/docs/members-and-teams/#what-only-admins-can-see).

If no health snapshot is available yet (the device has just been registered and has not reported its first check), the panel shows a message to that effect — the device is not unhealthy, it just has not reported yet.

Each row in the snapshot carries one of four outcomes:

| Outcome | What it means |
| --- | --- |
| **OK** | The check ran and confirmed everything is fine. |
| **Warn** | The check ran and found a non-blocking issue worth a look. |
| **Fail** | The check ran and found a blocking issue — see the “Blocks new work?” column below. |
| **Skipped** | The check did not run this round, so it verified nothing. Treat a skipped row the same as an unknown, never as a pass — it is not the same as OK. |

### Readiness checks

The device server runs the following checks and reports the results to the platform. The **Check** column uses the exact label shown in the **Health** panel.

| Check | What it verifies | Blocks new work? |
| --- | --- | --- |
| Clock skew | The device’s clock stays close to CodeHerder’s. | No — warns, but does not block work. |
| Device registration | The device’s [device token](https://codeherder.com/docs/device-tokens/) is present, valid, and accepted by CodeHerder. | Yes |
| Disk headroom | Free space on the session-root filesystem. Warns when free space is low; fails when critically low. | Only when critically low |
| Docker ready | Devices that run each stage in its own container ([how to enable](https://codeherder.com/docs/agent-isolation/#option-2-a-fresh-container-per-stage)) only: the Docker daemon is reachable and the task image is available locally. | Yes |
| GitHub auth | The GitHub CLI (`gh`) is installed and signed in. | No — warns if missing or not signed in, but does not block work. |
| Git credentials | Git can authenticate to each repository the workspace uses (one row per repo). | Yes |
| Git tokens | The device holds at least one [git token](https://codeherder.com/docs/git-tokens-on-a-device/) added with `ch device git-token create`, and reports how many it holds per host. It counts tokens — it does not test whether one works. | No — this check only ever reports OK; it does not warn or block work. |
| Harness auth | Each installed harness is actually signed in, not just present — one row per harness. Warns instead when a signed-in credential is close to expiring. | Yes, for work that needs that harness |
| Harness ready | At least one of the five harnesses — Claude, Codex, Cursor, OpenCode, or Pi — is installed. See [Choosing the coding-agent CLI your agents run](https://codeherder.com/docs/harnesses/) for the full list. | Yes |
| PID capacity | The device has enough headroom left to start new processes. | Only when that headroom is critically low |
| Session capacity | How many session-worktree slots — and, if a disk budget is configured, how much session disk — are in use against the device’s limits. | Yes, once a limit is reached |
| Session root | The session-root directory is configured, exists, and is writable. | Yes |
| Spawn probe | A brief test process launches cleanly through the same machinery real sessions use. | Yes, on failure |
| Tunnel connected | The device’s live connection to CodeHerder is up. | Yes |
| Version current | The running `ch` version matches the latest published release. | No — warns if an update is available, but does not block work. |
| Worktree slots | Whether the slots counted as in use are still tied to a running session, or left over from one that has already ended. | Only if left-over slots persist for a long time despite automatic clean-up — see [Reclaiming stuck worktree slots](https://codeherder.com/docs/devices/#reclaiming-stuck-worktree-slots) below. |

A few of these only appear once a condition is met — their absence isn’t a problem, it just means the condition hasn’t happened yet:

- **Docker ready** shows up only on devices that [run each stage in its own container](https://codeherder.com/docs/agent-isolation/#option-2-a-fresh-container-per-stage); a device that runs sessions as host processes has no Docker row at all.
- **Clock skew** and **Spawn probe** appear only after the device has connected to CodeHerder and completed its first check of that kind — before that, the row is simply not present yet.
- **Harness auth** appears only once at least one harness is installed; if none is, **Harness ready** is already failing and covers the problem.
- **Git tokens** appears only once you’ve added at least one [token](https://codeherder.com/docs/git-tokens-on-a-device/) on that device with `ch device git-token create`; a device with none added has no row at all.
- **PID capacity** appears only on devices that report their process usage to CodeHerder, which is most common on devices that run inside a container. A device that does not report it simply has no row. A device that reports its process usage but has no limit set still shows the row, reporting OK with no limit configured.
- **Harness ready** and **Harness auth** both cover Pi along with the other four harnesses, so a device with only Pi installed and signed in passes both checks — see [Choosing the coding-agent CLI your agents run](https://codeherder.com/docs/harnesses/) for how Pi is signed in, since it has no login command of its own.

If a harness binary is installed on the device for reasons unrelated to CodeHerder — say, you use it directly yourself — and you don’t want to sign it in just to satisfy **Harness auth**, exclude it from the check entirely with `ch device-server --harness-exclude <name>` (or `CH_DS_HARNESS_EXCLUDE=<name>`, comma-separated for more than one). The excluded harness is then treated exactly as if it weren’t installed at all — it no longer blocks staffability. `--harness-include` / `CH_DS_HARNESS_INCLUDE` does the opposite: probe only the named harnesses, ignoring every other one.

Checks run automatically on the device; you do not need to trigger them manually. Most checks re-run roughly every five minutes (or immediately on restart), so a fixed issue is usually reflected in the platform within a few minutes. **Spawn probe** is the exception — it’s a heavier canary that runs about once an hour, so give it a bit longer to pick up a fix.

## Starting the device server

The device server is the long-running supervisor that accepts agent work and keeps CodeHerder informed of the device’s health:

```
ch device-server
```

You don’t have to run this by hand just to use `ch start` on your own machine — it starts a device server for you whenever one isn’t already running. See [Start a session in your own checkout](https://codeherder.com/docs/local-sessions/) for that path.

Run this command once per device in a dedicated terminal, or configure it as a persistent background service that starts at login and survives reboots — see [Running the device server as a service](https://codeherder.com/docs/running-the-device-server/) for step-by-step instructions for Linux (systemd) and macOS (launchd). To run the device server inside Docker instead — keeping your host files out of its reach — see **Docker container (trusted mode)** in the same guide. That mode is not a boundary against a hostile agent. By default it also reports device metrics — CPU, RAM, and disk load — automatically every 30 seconds. Pass `--no-metrics` if you prefer to opt out of metrics reporting entirely.

Leave the server running for as long as you want the device to be available for agent work.

The device server also checks for updated versions of `ch` automatically — roughly once an hour — and restarts itself with the new binary when a newer release is found. By default, running agent sessions are not interrupted during an auto-update. See [Updating the CLI](https://codeherder.com/docs/updating/) for how to tune the cadence, disable auto-update, change that session behavior, or troubleshoot a device that’s stuck on an old version.

## Inspecting your devices

### List all devices

```
ch device list
```

This shows all devices in your workspace with their current Online/Offline state, name and hostname (hostname and OS show `—` unless you are the device’s owner or an admin), CPU and RAM readings, and the team they belong to.

### Show one device in full

```
ch device show <deviceId>
```

Prints every field for a single device you can see — name and hostname (if you can’t see host details, a notice replaces them), workspace and team, drain state, CPU/RAM readings, and the last time metrics were reported.

### Check recent load

```
ch device metrics
ch device metrics <deviceId> --minutes 60
```

Shows a min/avg/max/now breakdown of CPU, RAM, and disk load over a recent window (default: 30 minutes). With no target it reports your own device. Useful when the device is Online but tasks seem slow or are queuing unexpectedly.

## Changing a device’s settings

An owner or admin of the device’s workspace can change six device settings from the **Settings** section of the device’s detail page. The settings cover isolation and input timing. You can also read them with `ch device show`. See [Changing a device’s settings](https://codeherder.com/docs/device-settings/).

## Concurrency and capacity

CodeHerder runs more than one task on a device at the same time, up to a configurable limit. The effective number of concurrent sessions on a device is the **smaller of two values**:

- The **per-device limit** — set on the device’s detail page: **Set up → Devices → [the device] → Capacity → Max concurrent sessions**, or from the CLI with `ch device edit <deviceId> --max-sessions 12` — by the device’s owner or a workspace admin (see [Naming a device](https://codeherder.com/docs/devices/#naming-a-device) above for who that is). If nobody has ever set this value, CodeHerder assumes 8. Raising this allows more parallel work on that machine; lowering it frees resources for other processes. The CLI applies the same permission rule and writes the same value the detail page shows — it’s another way to set the number, not a different number.
- A **server-wide ceiling** controlled by whoever operates your CodeHerder server, via the `CH_MAX_DEVICE_SESSIONS` setting. The shipped default is 4, but an operator commonly raises it for their deployment — including on the hosted cloud, which does not run the shipped default. There is no single number that holds for every deployment.

Because the effective limit is the smaller of the two, raising the per-device value above the server ceiling has no practical effect until the operator also raises the ceiling. Read both live numbers, and the effective limit they produce, from:

- **Set up → Devices → [the device] → Capacity**, which prints a line like “Effective limit: N sessions — min(this value, the server’s global ceiling of M, set via CH_MAX_DEVICE_SESSIONS)”; or
- `ch device show <deviceId> --json`, whose `effectiveMaxConcurrentSessions` and `sessionCeiling` fields carry the same two numbers. (The human-readable `ch device show` output does not print them.)

For example, a device with a per-device limit of 8 on a server whose ceiling is 50 has an effective limit of 8 — the per-device value binds. A device with a per-device limit of 12 on a server whose ceiling is 4 has an effective limit of 4 — the ceiling binds instead.

When a device reaches its effective limit, additional tasks queue automatically and start as soon as a running agent finishes. No manual intervention is required.

This running-session limit is a different axis from disk capacity. If you run a self-managed device server, it also enforces a separate local cap on how many session worktrees it holds on disk — `--max-session-worktrees` (or the `CH_MAX_SESSION_WORKTREES` environment variable), default 8. Where those worktrees are stored is a third, independent setting, `--session-root`. See [Running the device server as a service](https://codeherder.com/docs/running-the-device-server/) for all three flags. A device can be at its session-concurrency limit while nowhere near its worktree-slot limit, or the reverse; the two numbers do not track each other.

## Reclaiming stuck worktree slots

Each running session on a device occupies one of its session-worktree slots. A slot normally frees within moments of its session ending, and the next queued task claims it right away — most of the time there is nothing to do here.

Occasionally a slot does not free automatically — for example, if the device restarted while a session was still running. When enough of these build up, the device can look fully busy even though nothing is actually running on it, and tasks that should start just sit queued.

To tell which situation you are in, expand the device’s row in **Set up → Devices** and check the **Worktree slots** check in the Health snapshot panel (see [Device health and readiness checks](https://codeherder.com/docs/devices/#device-health-and-readiness-checks) above). Its summary reports how many slots are orphaned or reclaimable:

- **None reported** — the device is genuinely busy. Tasks queue and start as soon as a running session finishes; no action needed.
- **Some reported** — those slots belong to sessions that have already ended. Force an immediate clean-up instead of waiting for the next automatic pass:

```
ch device reclaim <deviceId>
```

This is fire-and-forget: the command only confirms CodeHerder accepted the request, and the device clears the affected slots in the background. Check the **Worktree slots** check again a few minutes later to confirm the counts dropped.

A few things to know before you run it:

- The device must be **Online** and connected — see [Online and Offline](https://codeherder.com/docs/devices/#online-and-offline) above. Reclaim has nothing to act on when the device is Offline.
- Only the device’s owner or a workspace admin can trigger a reclaim; other members are refused.
- It is a CLI-only action — there is no equivalent button in the web app.

## AI limits

When a device runs agents on a Claude credential — a subscription plan (Max, Pro, Team) or an enterprise/organization API credential — the device server probes it for current usage every few minutes and reports the data to CodeHerder. A device can hold more than one Claude credential at once — see [AI credentials on a device](https://codeherder.com/docs/device-ai-credentials/) for adding a second. The **AI limits** section appears on the device detail page when usage data has been received.

### Viewing AI limits

Open **Set up → Devices**, click on the device row to open its detail page, and look for the **AI limits** section. It shows one card per credential — labelled with the name you gave it, if it’s not the device’s only one. For each card, the section shows:

- The provider name with a plan badge (for example, **claude** with a **max** badge).
- A usage meter for each rate-limit window — the percentage consumed and, when available, how long until the window resets. See [AI usage limits](https://codeherder.com/docs/ai-usage-limits/) for the full list of meters and which ones pause the device.
- A **Capped** badge, with the cap shown alongside each capped window’s meter, if you’ve set a per-window usage cap on that credential — see [AI credentials on a device](https://codeherder.com/docs/device-ai-credentials/).
- A **Not reporting** badge, with the meters hidden, if that credential’s usage probe hasn’t reported in a while — the row instead reads “Usage probe stopped reporting. Last probed …”.
- Otherwise, a “Probed …” timestamp for the most recent successful check.

Meter values refresh as the device server probes the provider, approximately every five minutes by default.

The section only appears when the device has reported usage data. If you do not see it, the device server may not have signed-in credentials for any AI provider on that machine.

### How usage limits affect task placement

CodeHerder stops routing new tasks to a device once every fresh Claude credential on it has a capacity meter — a subscription pool, a spend cap, or the account-blocked verdict — at 100%, with none of them yet reset. A device with a single credential pauses as soon as that one hits its limit; a device with several keeps taking work as long as at least one still has headroom. A per-minute throughput meter at 100% never triggers this. The device stays **Online** and **healthy**; sessions already running are not interrupted. Placement resumes automatically once a meter resets and the next probe reports headroom.

When more than one device is eligible for a task, CodeHerder also leans toward whichever has more Claude headroom left across its credentials — so a device nearing its limit gradually takes less new work even before it pauses outright.

If tasks aimed at a device stop starting and nothing else seems wrong, look for a **Claude exhausted** badge next to the device’s health badge on the devices list (or **Claude subscription exhausted** on its detail page) — that’s the answer to “why isn’t this device being used”. For the full picture — every meter, why the pause needs every credential exhausted, why switching launch configs doesn’t help, and how to keep work moving with a second credential or a second device before the reset — see [AI usage limits](https://codeherder.com/docs/ai-usage-limits/) and [AI credentials on a device](https://codeherder.com/docs/device-ai-credentials/).

### Disabling or tuning the probe

The usage probe runs automatically alongside the device server. To disable it entirely, set the environment variable `CH_AI_LIMITS_PROBE=0` before starting the device server. To change how often it checks, set `CH_AI_LIMITS_PROBE_INTERVAL` to a Go duration (for example, `CH_AI_LIMITS_PROBE_INTERVAL=10m` for every ten minutes). See [Running the device server as a service](https://codeherder.com/docs/running-the-device-server/) for how to apply environment variables to a service-managed device server.

## Running more than one device

A workspace can have several devices registered at once. When the engine places a new task, it picks any Online device that:

1. is linked to the task’s workspace (or an ancestor group) and has a free slot, and
2. has the tooling the work requires (such as the git-host CLI for `code` and `merge` stages).

Adding more devices (or raising per-device limits on existing ones) increases the number of tasks that can run in parallel. The engine spreads work across all eligible devices automatically — you do not need to route tasks to specific machines manually.

## Sharing a device across workspaces

A workspace admin can run code on any device linked to the workspace. See [Who can run code on your device](https://codeherder.com/docs/device-trust/).

A device is owned by the person whose `ch device-server` first registered it and can be linked to **several workspaces at once** — useful when you own more than one workspace and want them all to share the same hardware instead of running a separate device server per workspace.

A device serves a workspace when it is **linked** to that workspace. If `CH_WORKSPACE_ID` (or `--workspace`) is set when `ch device-server` self-registers on first run, that workspace becomes the device’s first — and primary — link; self-registering with no workspace set leaves the device unlinked until you link it explicitly. Either way, you add further links yourself with the commands below. A device you [launch on AWS](https://codeherder.com/docs/launch-a-device-on-aws/) from a workspace’s Devices page is linked to that workspace from the start; the commands below add further links if you want it to also serve another workspace.

### See which workspaces a device serves

```
ch device workspaces list <deviceId>
```

Prints every workspace the device is linked to — its primary link plus any extra ones. A bare `ch device workspaces <deviceId>` (no op) lists too. The device’s detail page in the app shows the same list.

### Link a device to another workspace

Use the `ch` CLI. This command needs the device’s full UUID from `ch device list` — the device isn’t in the target workspace yet, so there’s no name there to match. Refer to the workspace by its name, slug, slug path, or UUID (from `ch workspace list`):

```
ch device workspaces create <deviceId> --workspace <workspace>
```

For example, to let a device also serve the FreeTier workspace:

```
ch device workspaces create 019e58e6-3f9a-7b12-a845-2c3d4e5f6789 --workspace FreeTier
```

Once linked, the engine treats the device as a candidate for that workspace’s tasks exactly as it does for any other linked workspace — placement, staffing, and the tunnel all honour the link. The device then appears in `ch device list --workspace <workspace>` for the newly-linked workspace, and the device’s detail page in the app lists every workspace it is linked to.

You do **not** need to restart or reconfigure `ch device-server` — linking takes effect immediately on the server side.

### Unlink a device from a workspace

```
ch device workspaces delete <device> --workspace <workspace>
```

You cannot unlink a device’s last remaining workspace — it must always stay linked to at least one. Any other workspace link can be removed at any time.

### What a shared device shares

The device owner decides whether a device serves more than one workspace. CodeHerder does not decide it.

The list of what workspaces on one device share is in [Self-hosted device isolation](https://codeherder.com/docs/self-host-device-isolation/#what-a-shared-device-shares).

### What else a shared workspace needs

Linking the device is only the hardware half. For the engine to actually run a workspace’s tasks on a shared device, that workspace also needs:

- **An agent** with capabilities matching the work (agents are created per workspace — see the agents guide).
- **The repository registered** in that workspace, and the device set up with git credentials and the host CLI — see **What the device needs** in [Connecting repositories](https://codeherder.com/docs/repositories/).

When all three are in place — a linked device, a capable agent, and a reachable repo — tasks filed in the shared workspace start running on the device automatically.

## Pausing a device

Sometimes you want to stop new work landing on a device without touching anything already running on it — before a reboot, a maintenance window, or while you look into an issue. **Draining** a device does exactly that: new placements stop immediately, while any sessions already running there keep going until they finish. The device’s Online state and health badge do not change; only new placement pauses.

Pausing needs the device’s owner or a workspace admin — the same rule as renaming a device or changing its capacity (see [Naming a device](https://codeherder.com/docs/devices/#naming-a-device), above).

**Web app:** In **Set up → Devices**, click **Disable** on the device’s row. A **disabled** chip appears on the row once it takes effect. Click **Enable** on the same row to resume placement.

**CLI:**

```
ch device disable <deviceId>
ch device enable <deviceId>
```

`ch device disable` prints a confirmation and, since the whole point is to leave running work alone, also lists anything still in flight there:

```
ch device disable <deviceId>
```

```
Device my-device (019e58e6-3f9a-7b12-a845-2c3d4e5f6789) enabled=false.
no live sessions on this device
```

To check whether a device is currently paused, look at the **STATE** column in `ch device list` — it reads `drained` for a paused device — or run `ch device show <deviceId>`, which prints `drained: yes (since <when>)`. `ch device list --disabled` narrows the whole list to every device that’s currently off, drained and personal alike — see [Listing enabled and disabled records](https://codeherder.com/docs/using-the-cli/#listing-enabled-and-disabled-records).

Plain `disable` applies to every workspace. To turn a device off for one workspace only, see [Turning a device off for one workspace](https://codeherder.com/docs/devices/#turning-a-device-off-for-one-workspace) below.

Pausing is fully reversible: run `ch device enable <deviceId>` (or click **Enable**) whenever you’re ready, and the device resumes taking new work. Archiving (below) is reversible too, but it goes further — pausing just stops new placements, while archiving takes the device out of the fleet entirely and revokes its tokens.

### Turning a device off for one workspace

Plain `disable` pauses a device in every workspace that uses it. Sometimes you only want it gone from one. A device that a group shares with its workspaces is the usual case: one workspace wants to keep its work off that machine while the others carry on. For that, turn the device off for just that workspace:

```
ch device disable <deviceId> --workspace <workspace>
ch device enable <deviceId> --workspace <workspace>
```

`<workspace>` is a workspace name, slug, slug path or ID. The command confirms both the workspace setting and the fleet-wide one:

```
Device my-device (019e58e6-3f9a-7b12-a845-2c3d4e5f6789) enabled=false in workspace 019e5a01-7c2d-7e40-b1f3-5a6b7c8d9e01 (fleet-wide enabled=true).
```

A few things to know:

- **CLI only.** The web app has no control for this yet.
- **Admins and owners only.** You need to be an admin or owner of that workspace. Being the device’s owner is not enough, and an agent session can’t run it.
- **New work only.** The workspace stops placing new tasks, stages and dev sessions on the device. Sessions already running there keep going.
- **It reaches workspaces below.** The setting covers the workspace and every workspace nested under it. If you set it on a group, all of its descendants skip the device.
- **Nothing else changes.** The device stays linked, the fleet-wide pause stays as it was, and every other workspace keeps using the device.

To see it, run `ch device list` in the workspace. The **STATE** column reads `excluded here` for a device the workspace has turned off, including when a group above it did so. `ch device show` and `ch device list --disabled` look only at the fleet-wide pause, so they don’t show it.

To turn the device back on, run `ch device enable <deviceId> --workspace <workspace>` against the workspace or group that turned it off. Running it from a workspace below a group that excluded the device doesn’t lift the group’s exclusion. Run it against the group instead.

This differs from [Remove from workspace](https://codeherder.com/docs/devices/#remove-from-workspace). Removing a link only works where the device is linked, so it can’t stop a workspace from using a device it inherits from a group. The Placement report shows an excluded device as `device_excluded_by_workspace`; see [The Placement report](https://codeherder.com/docs/placement/).

### Personal devices

Running `ch start` on a machine that has no device registered yet registers one automatically, as a **personal** device — see [Start a session in your own checkout](https://codeherder.com/docs/local-sessions/#setting-up-the-first-time). A personal device is registered disabled, exactly like a paused one, so the engine never hands it someone else’s tasks; it’s there to run your own `ch start` sessions.

A personal device looks like a paused device almost everywhere, with two tells. The **STATE** column in `ch device list` reads `personal` instead of `drained`, and `ch device show <deviceId>` prints a `personal:` line in place of `drained:`. In the web app, the device’s row and its detail page carry a **Personal** badge instead of the **disabled** chip.

Sharing a personal device with the rest of the workspace uses the same command as resuming a paused one:

```
ch device enable <deviceId>
```

This clears the personal mark for good, not just for now — once you enable a personal device, it’s an ordinary device from then on. Disable it again later and it comes back as a plain paused device, not personal again. The permission rule is the same as pausing any other device: the device’s owner, or a workspace owner or admin.

## Retiring a device

When you stop using a device in a workspace — or want to decommission the machine entirely — CodeHerder gives you two distinct removal paths. Choose based on how completely you want to remove it.

### Remove from workspace

Removes the device from **one** workspace while leaving its links to any other workspaces intact. The device is not archived, and the removal is **reversible** — you can re-add the device to the workspace at any time with `ch device workspaces create`.

**Web app:** In **Set up → Devices**, click **Remove** on the device’s row. On the device’s detail page you can also click the **×** next to any workspace chip in the **Workspaces** row.

**CLI:**

```
ch device workspaces delete <device> --workspace <workspace>
```

If the workspace you are removing is the device’s **last** remaining link, the operation is blocked — a device must always stay linked to at least one workspace. To fully retire the device, archive it instead (below).

### Archive the device

Archiving takes the device out of the fleet entirely, across every workspace it’s linked to: it drops out of every default device list and out of placement, and **every one of its device tokens is revoked** in the same step. Any live tunnel connection to the device is closed. It’s reversible — restoring brings the device back, though it needs a fresh token to reconnect (see below).

**Web app:** Open **Set up → Devices**, click on the device to open its detail page, then click **Archive** at the top of the page. The confirmation names the consequence — tokens revoked, re-enrolment needed — before you commit. Click **Restore** on the same button once you’re ready to bring it back.

**CLI:**

```
ch device archive <deviceId>
ch device restore <deviceId>
```

**`ch device delete` is retired.** It used to be a frozen alias of `archive` — soft-archiving the device instead of destroying anything — but that made `delete` ambiguous with kinds where it really does destroy. `ch device archive` above is the one spelling now; typing `ch device delete` prints `Did you mean: ch device archive ?` instead of running anything.

Two things to check before you archive:

- **Stop any live sessions first.** CodeHerder refuses the archive while sessions are running on the device; the refusal tells you sessions are still live, but not which ones. Pause the device first with `ch device disable <deviceId>` — it lists the sessions still running there — then archive once they’ve finished.
- **You need workspace owner or admin.** Registering the device yourself doesn’t grant this on its own — it takes admin rank too. Members without owner or admin rank cannot archive a device, and only a human caller can (an agent’s own session credential is refused).

**After you restore, re-enrol the device.** Archiving revokes every device token the device held, and restoring does not bring them back — a restored device needs a fresh token before it can reconnect:

```
ch device rotate-token <deviceId>
```

Save the printed token on the device (the command prints the exact command to run there), then start or restart `ch device-server` on it. Nothing about the device’s identity, workspace links, or history changes — only the credential.

### Finding an archived device

Archived devices are hidden from the regular list by default. To see only the archived ones:

```
ch device list --archived
```

`ch device list --all` shows every device instead, active and archived together, if you want the wider view. Either way, the **STATE** column reads `archived` (taking priority over `drained`) so you can tell an archived device apart from one that’s merely paused. See [Listing archived records](https://codeherder.com/docs/using-the-cli/#listing-archived-records) for how this flag works across CodeHerder. `ch device show <deviceId>` still resolves an archived device and prints its archived timestamp.

**Web app:** in **Set up → Devices**, pick **Archived** (or **All**) from the Live / Archived / All picker — archived rows appear dimmed with an **Archived** badge.

## When a device goes offline during a task

Sometimes a device loses its connection while an agent works on a task. CodeHerder recovers the task without doing the work twice. The times below are defaults. They count from the last message CodeHerder got from the device.

1. **The device shows Offline.** This takes up to 105 seconds if the network drops with no warning. It is immediate if the device closes the connection.
2. **The agent run shows Lost.** This takes about 90 to 120 seconds more. The task stays with the device.
3. **CodeHerder retires the session.** This takes about 5 minutes after the last sign of life. The session key stops working at that moment. The old agent cannot change the task.
4. **CodeHerder moves the task to another device.** It waits 5 minutes first, in case the device comes back. Set `CH_DEVICE_AFFINITY_GRACE` to change this wait. Set `CH_SESSION_LEASE_TTL_SECONDS` to change step 3.
5. **The device comes back.** CodeHerder tells the device to stop the old agent process. Only one session works on the task.

The old agent can still push to the git host while the device has no link to CodeHerder but can reach the git host. CodeHerder does not control the git host. It cannot stop these pushes. Check the task branch if a device was offline for a long time.

## Troubleshooting

**The device never appeared at all — not even Offline.** It was never registered. Registration only happens the first time you run `ch device-server` in a terminal and confirm the one-time prompt — a background service or container started before that first interactive run just idles, with no error and nothing to show. See [How do I add a device?](https://codeherder.com/docs/adding-a-device/) for the registration prompt and [Running the device server as a service](https://codeherder.com/docs/running-the-device-server/) for the register-first order when using a service.

**The device shows Offline.** Start or restart `ch device-server` on that machine. The device switches to Online as soon as the server connects.

**The device used to be there and is gone from the list now.** It’s most likely archived, not deleted. Find it with `ch device list --archived` (or pick **Archived** from the Live / Archived / All picker in **Set up → Devices**), then restore it, rotate a fresh token, and restart `ch device-server` on it — see [Archive the device](https://codeherder.com/docs/devices/#archive-the-device) above.

**The device is Online but no agent is picking up work.** Check the following:

1. **Is the agent eligible on this device?** An agent is eligible on any device linked to its workspace (or an ancestor group) whose capabilities the device covers — no separate assignment step. Run `ch agent placement <agentId>` to see whether this device is eligible and, if not, why — see [The Placement report](https://codeherder.com/docs/placement/) for the full reason list and what to do about each one. See also [Quickstart](https://codeherder.com/docs/quickstart/) and [Agents and the CLI](https://codeherder.com/docs/agents-and-cli/) for the full setup steps.
2. **Is the device at its capacity limit?** Open the device’s detail page (**Set up → Devices → [the device]**) and check the **Capacity** panel for its per-device **Max concurrent sessions** limit — see [Concurrency and capacity](https://codeherder.com/docs/devices/#concurrency-and-capacity) above for how that combines with the platform-wide ceiling. If the device is full, tasks queue until a slot opens — that is normal, and no action is needed while sessions are genuinely running there. If it stays full with nothing actually running, some slots may be stuck rather than in use — see [Reclaiming stuck worktree slots](https://codeherder.com/docs/devices/#reclaiming-stuck-worktree-slots) below.
3. **Does the device have the tooling the work requires?** Some workflow stages need specific host tools — for example, the `code` and `merge` stages need the git-host CLI (`gh` for GitHub, `glab` for GitLab) to open and land pull requests. If the device is missing a required tool, the engine cannot run that stage there. Install the missing tool on the device; the device picks it up on its next connection. For a full explanation of stage capability gaps and how to fix them, see [Agents and the CLI](https://codeherder.com/docs/agents-and-cli/) and [Why isn’t my task moving?](https://codeherder.com/docs/task-not-moving/).
4. **Is the device paused (drained)?** A paused device stays Online and healthy, but the engine skips it for new placements. Check the **STATE** column in `ch device list` — it reads `drained` for a paused device — then resume placement with `ch device enable <deviceId>` or the **Enable** button in **Set up → Devices**. See [Pausing a device](https://codeherder.com/docs/devices/#pausing-a-device) above. If the state reads `excluded here`, the workspace turned the device off for itself. See [Turning a device off for one workspace](https://codeherder.com/docs/devices/#turning-a-device-off-for-one-workspace).

## Related guides

- [Changing a device’s settings](https://codeherder.com/docs/device-settings/) — change six device settings from the web app and see when each takes effect
- [The Placement report](https://codeherder.com/docs/placement/) — see exactly which devices could run a task or agent, and why the others can’t
- [AI credentials on a device](https://codeherder.com/docs/device-ai-credentials/) — give a device more than one Claude credential
- [How do I add a device?](https://codeherder.com/docs/adding-a-device/) — the focused first-run steps to connect a machine
- [Launch a device on AWS](https://codeherder.com/docs/launch-a-device-on-aws/) — launch a device in your own AWS account instead of running one yourself
- [MicroVM runners](https://codeherder.com/docs/microvm-runners/) — let CodeHerder launch AWS devices for you automatically when your fleet runs short; a runner-launched device shows up here like any other
- [Running the device server as a service](https://codeherder.com/docs/running-the-device-server/) — set up device-server as a persistent background service
- [Monitoring your agents](https://codeherder.com/docs/monitoring-agents/) — check fleet health and staffing on the Agents page
- [Updating the CLI](https://codeherder.com/docs/updating/) — keep ch and the device server binary current
