Secrets on a device
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 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 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) — 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).
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 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.
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 setagain, listing every referenced key with--secret— this covers both an unacknowledged@secret:reference and an unacknowledged secret variable 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 — set a secret variable at group, workspace, device, or agent scope; also gated by this same acknowledged-secrets list
- Secrets — the workspace-level alternative: CodeHerder holds the encrypted value itself.
- Agents and the CLI — launch configs, multiple configs and ordering, and where an agent runs.
- Managing your devices — device state and troubleshooting.
Last updated