Workspaces
A workspace is the tenant boundary for Connect. It owns the machines you can reach, the API key (or OAuth token) that lets you reach them, and the relay server those calls flow through. Most people start with one workspace; teams that work across companies, environments, or relay deployments end up with several.
What lives in a workspace
When Connect resolves a target, it does so inside the active workspace. That means a workspace owns:
- Machines. Every registered host is scoped to exactly one workspace at a
time.
cmdop connect --listonly shows machines in the current workspace. - API key. A single workspace-scoped credential, optionally overridden by
--api-keyper call. - Server (optional). A per-workspace gRPC override for on-prem relay deployments — most users leave this blank and use the default cloud relay.
- Sync metadata.
RemoteID,RemoteName, andLastSyncedAtare populated when you runcmdop connect workspace syncagainst an OAuth session.
The struct is defined in internal/connect/workspace/types.go:20–28.
API keys belong to one workspace. To talk to machines in workspace B you need a key for B (or you sign in with OAuth, which carries the active workspace in the token).
The local store
Workspaces live in ~/.cmdop/ssh_workspaces.json with mode 0600. The file
is per-user, never synced over the network, and only writable by the daemon
or the CLI under your euid. The schema is intentionally tiny:
{
"active": "production",
"workspaces": [
{
"name": "production",
"api_key": "ck_live_***REDACTED***",
"server": "",
"remote_id": "ws_8f23...",
"remote_name": "Acme Production",
"last_synced_at": "2026-04-22T11:04:31Z"
},
{
"name": "staging",
"api_key": "ck_test_***REDACTED***"
}
]
}The filename still says ssh_workspaces.json for historical reasons — the
package was renamed ssh → connect but the on-disk format kept the old
name to avoid a breaking migration. It may be renamed in a future release;
treat the path as an implementation detail.
workspace.DefaultStore() lazily loads this file on first access and keeps
a process-wide singleton. See internal/connect/workspace/store.go.
Listing and switching
The CLI exposes the workspace lifecycle under cmdop connect workspace
(short alias ws):
# What workspace am I in right now?
cmdop connect workspace
# All workspaces, with the active one marked.
cmdop connect workspace list
# Switch the active workspace.
cmdop connect workspace use staging
# Drop a local workspace (does not touch the server).
cmdop connect workspace remove old-pocWhen you run workspace use, the daemon notices the change at its next
relay handshake and reconnects with the new WorkspaceID. Sessions opened
under the previous workspace are not visible from the new one.
Switching the active workspace causes the daemon to drop and re-establish
its relay connection. Any in-flight cmdop connect attach or ssh_session
session against the previous workspace will end.
Sync from the server
If you signed in with cmdop login, the OAuth token can pull the
authoritative workspace list from the dashboard:
cmdop login # one-time
cmdop connect workspace syncsync calls /apix/machines/workspaces/, upserts every workspace it sees
(matching by RemoteID, then by Name), and preserves any locally stored
API keys so you do not lose your per-workspace credentials. Workspaces that
exist locally but not on the server are left untouched — sync never deletes.
OAuth tokens have a 72-hour lifespan with refresh; if sync reports an
auth error, run cmdop login again.
Per-call overrides
Every cmdop connect subcommand accepts overrides that bypass the active
workspace for that one invocation:
# Use a different stored workspace just for this call.
cmdop connect --workspace staging vps-bmw
# Inject a raw API key.
cmdop connect --api-key ck_live_xxxxx exec prod-api-1 -- uptime
# Point at an alternate gRPC relay.
cmdop connect --server grpc.cmdop.internal:443 --listThese overrides are most useful for CI scripts, multi-tenant tooling, and support sessions where you do not want to touch the operator’s saved state. They are fully described in credential-resolver.
Per-workspace API key management
A workspace’s API key is treated as a discrete piece of state. You can inspect it, rotate it, or remove it without recreating the workspace:
# Show the active workspace's key (masked).
cmdop connect key get
# Replace it.
cmdop connect key set ck_live_new_token
# Remove it (forces fall-through to env / OAuth on next call).
cmdop connect key clearcmdop connect set-key <key> is kept as a deprecated alias to
cmdop connect key set <key> and prints a notice steering you to the new
form. See internal/connect/CLAUDE.md for the full surface.
Workspace migration on the server
When a logged-in user changes the active workspace in the web cabinet, the
server side notices the new workspace_id in the next OAuth-bearing call
and migrates the machine record from the old workspace to the new one.
This is what lets you sign up under a personal workspace, then move a
machine into a team workspace later without re-running cmdop agent register.
The CLI never has to do anything special — it picks up the new workspace
on the next sync (or whenever the dashboard updates the OAuth token).
When to use multiple workspaces
A few patterns we see often:
- Production / staging split. Two workspaces with different relay accounts so a misconfigured CI job cannot accidentally reach production.
- Multi-tenant consultancy. One workspace per customer, switched per
engagement. OAuth +
synckeeps the list current. - Personal vs company. A personal workspace for your own laptop, plus a team workspace shared with co-workers.
- On-prem relay. Set
Workspace.Serverfor an internal relay (everything else stays on the default cloud relay).
Related
The precedence chain that turns CLI flags, env vars, and stored workspaces into a single resolved API key.
How machines are scoped to a workspace and how the resolver picks one.
Workspace credentials versus per-machine passwords — they are different layers.
The full CLI surface for cmdop connect workspace and friends.