# Secrets on a device

Source: https://codeherder.com/docs/device-secrets/

Reference a credential that lives only on a device's own secret store, and get the device owner's acknowledgement it needs before an agent can run.

A **device-side secret reference** is a second way to give an agent a credential, alongside the workspace [Secrets](https://codeherder.com/docs/secrets/) covered elsewhere. Instead of storing the value in CodeHerder, you leave it on the device itself and put only a reference to it in the agent’s launch config.

## When to use this instead of a workspace secret

| aspect | Workspace secret + Credential ref | Device-side reference |
| --- | --- | --- |
| Where the value lives | Encrypted in CodeHerder | Only on the device, in its own secret store |
| Setup | Once, works on every device the agent runs on | Once per device |
| Best for | A credential you’re fine handing to CodeHerder | A credential you don’t want CodeHerder to hold at all |

See [Secrets](https://codeherder.com/docs/secrets/) for the workspace-secret path. The rest of this page covers the device-side one.

## How it works

In an agent’s launch config, an environment variable can hold a value of the form `@secret:<key>` instead of a literal value — for example, `MY_SERVICE_TOKEN=@secret:my_service`. CodeHerder stores that string as-is; it is a reference, not a credential. The device resolves it to the real value only at the moment a session starts, reading it from its own secret store. The value itself never reaches CodeHerder — not in the config, not in logs, not anywhere.

The reference can live in any of the agent’s launch configs (see **Running more than one launch config** in [Agents and the CLI](https://codeherder.com/docs/agents-and-cli/)) — at spawn time CodeHerder checks whichever config is actually running against the device’s acknowledged-secrets list, not just the top one.

## Step 1 — put the value on the device

Run this on the device itself, not from your own machine:

**macOS:**

```
security add-generic-password -U -s codeherder-device -a my_service -w
```

Leaving off a trailing value after `-w` makes `security` prompt for it (twice, to confirm) instead of taking it as an argument, so it never lands in your shell history. `-U` lets you re-run the command later to update the value.

**Linux:**

```
printf %s "$TOKEN" | secret-tool store --label codeherder service codeherder-device key my_service
```

**Headless devices or CI:** set an environment variable on the device server’s own process — `CH_SECRET_<KEY>` with the key upper-cased, for example `CH_SECRET_MY_SERVICE`. CodeHerder checks this before the operating system’s secret store. This is the simplest path on a machine with no interactive keychain, at the cost of the value sitting in the device server’s own environment instead of a dedicated secret store. Use a key made of letters, digits, and underscores so its upper-cased form is a valid environment variable name.

Device-side references only resolve on **macOS and Linux**. On any other platform, a session that needs one fails immediately.

## Step 2 — reference it in the launch config

**Web app:** open the agent’s detail page, find the **Launch configs** panel, and edit the config’s **Env** field — one `KEY=value` per line, using `@secret:<key>` as the value:

```
MY_SERVICE_TOKEN=@secret:my_service
```

**CLI:**

```
ch agent config edit <agentId> --harness claude --env MY_SERVICE_TOKEN=@secret:my_service
```

`--env` is repeatable if the config needs more than one reference — it replaces the config’s whole env set on any call where you pass it, so list every env var you want the config to keep, not just the new reference. `--harness` and every other launch-config field are left untouched unless you also pass their own flag (see **What each edit actually changes** in [Agents and the CLI](https://codeherder.com/docs/agents-and-cli/#what-each-edit-actually-changes)).

## Step 3 — get the reference acknowledged

Every key any of the device’s agents’ launch configs reference with `@secret:` must be explicitly acknowledged before a session referencing it can start. This is a deliberate checkpoint: it stops a config edit from quietly reaching into a device’s secret store for a credential nobody agreed to hand over. The acknowledgement lives on the **device**, not on any one agent — set it once and every agent that runs there shares it.

The same list gates one more thing: a [secret variable](https://codeherder.com/docs/variables/) whose key looks like an environment variable name is injected into a session on this device only once its key is on this same acknowledged list — even though it isn’t an `@secret:` reference at all. Add its key alongside your `@secret:` keys the same way.

Only the **device’s owner** or a **workspace owner** can give this acknowledgement — a workspace admin is not enough.

```
ch device secrets set <deviceId> --secret my_service
```

Pass `--secret` once per key you want acknowledged. This is a **whole-set replace, not a diff** — it sets the device’s acknowledged-secrets list to exactly the keys you pass, so include every key already acknowledged that you still want, not just the new one. Omit `--secret` entirely to clear the list. See the current list with:

```
ch device secrets list <deviceId>
```

Only the device’s owner and workspace admins see this list. Everyone else gets a notice instead — see [Who sees host details](https://codeherder.com/docs/devices/#who-sees-host-details).

**Adding a reference later:** if you edit a config to add a new `@secret:` reference after the device was already acknowledging others, the next session refuses to start until you re-run `ch device secrets set` with the new key included alongside the existing ones.

## When it goes wrong

- **Session doesn’t start, and the device needs acknowledgement:** run `ch device secrets set` again, listing every referenced key with `--secret` — this covers both an unacknowledged `@secret:` reference and an unacknowledged [secret variable](https://codeherder.com/docs/variables/) key.
- **Session doesn’t start, and a key can’t be found on the device:** the failure reports that a secret reference couldn’t be resolved on that device, but it does not say which key. Check which `@secret:<key>` references the spawning launch config uses, confirm each one is stored on that device (Step 1), and try again.

## Related guides

- [Variables](https://codeherder.com/docs/variables/) — set a secret variable at group, workspace, device, or agent scope; also gated by this same acknowledged-secrets list
- [Secrets](https://codeherder.com/docs/secrets/) — the workspace-level alternative: CodeHerder holds the encrypted value itself.
- [Agents and the CLI](https://codeherder.com/docs/agents-and-cli/) — launch configs, multiple configs and ordering, and where an agent runs.
- [Managing your devices](https://codeherder.com/docs/devices/) — device state and troubleshooting.
