Client or integration
Codex CLI
Area
Service lifecycle
Summary
What happened: On a machine enrolled as a hub client (runtimeRole: "client", connected via ocx hub invite + ocx connect), 'ocx service uninstall' unconditionally deletes '~/.opencodex/service-api-token'. In hub/client mode that file is not a service artifact: it holds the per-client data-plane key issued by the pairing exchange (the remote-hub docs say the client writes it to the existing owner-only service-api-token file, never config.json). After the deletion, every client lifecycle command fails closed and there is no CLI route back to a working client; the only fix is hand-editing ~/.opencodex/config.json.
What I expected instead: uninstall should not destroy a hub client's admission credential. Either skip the unlink when a client connection exists (or when runtimeRole is "client"), or refuse with guidance ("run ocx disconnect first"), or retain the file and state that it is the hub key. Ideally 'ocx disconnect' and 'ocx connect rotate' should also tolerate an absent token file, so that the documented recovery path exists at all.
Impact: the machine is stuck in a half-state: 'ocx connect status' still reports "Connection: connected" next to "Token file: missing", while sync and disconnect both refuse. Because the same uninstall path also restores native Codex, Codex silently falls back to its own provider routing instead of the hub, so the operator does not even see an error in the client.
Reproduction
- Enroll a machine as a hub client: 'ocx hub invite' on the hub, then 'echo "
" | ocx connect https://:8443 --management-url https:// --pairing-code-stdin --clients codex'. The client is connected and '~/.opencodex/service-api-token' exists.
- On that same client, remove a leftover standalone service (for example a launchd job from an earlier standalone setup): 'ocx service uninstall'
- Observe: '~/.opencodex/service-api-token' is gone. src/service/cli.ts:427 unlinks it unconditionally right after removeServiceInstallState().
- Try to recover with the commands the CLI itself suggests:
- 'ocx sync' -> "Connected sync failed without local fallback: client_token_changed"
- 'ocx disconnect' -> "Error: client_token_changed"
- 'ocx config unset client' -> "Error: schema_invalid: runtimeRole client requires a complete client connection"
- 'ocx config set runtimeRole standalone' -> "Error: schema_invalid: client connection requires runtimeRole client"
The CLI refuses both directions of the role flip, so no documented command can leave the broken state.
Recovery that actually worked: hand-edit '~/.opencodex/config.json' (remove the "client" object, set "runtimeRole": "standalone"), then invite and connect again. The abandoned hub-side key is still present and has to be revoked manually ('ocx access key remove '), because 'ocx connect revoke' needs a live connection.
Relation to existing work: #5144 (fix(client): clear disconnected hub token on standalone recycle) touches the same token lifecycle, but covers the disconnect/recycle direction, not uninstall.
Version
2.64.0 (npm latest; the same unlink is present on main, src/service/cli.ts:427)
Operating system
Client: macOS 26.6.2 (arm64). Hub: Ubuntu 26.04.1 LTS (x86_64).
Provider and model
No response
Logs or error output
$ ocx sync
Connected sync failed without local fallback: client_token_changed
$ ocx disconnect
Error: client_token_changed
$ ocx config unset client
Error: schema_invalid: runtimeRole client requires a complete client connection
$ ocx config set runtimeRole standalone
Error: schema_invalid: client connection requires runtimeRole client
$ ocx connect status
Connection: connected
Local Codex CLI: ready
Hub: https://<hub-host>:8443
API key id: <redacted>
Clients: codex
Token file: missing
$ ls ~/.opencodex/service-api-token
ls: No such file or directory
Screenshots and supporting files
No response
Redacted configuration
{
"runtimeRole": "client",
"port": 10100,
"client": {
"serverUrl": "https://<hub-host>:8443",
"managementUrl": "https://<hub-host>:9445",
"managementTransport": "direct",
"selectedClients": ["codex"],
"tokenEnv": "OPENCODEX_API_AUTH_TOKEN",
"apiKeyId": "<redacted>",
"tokenFingerprint": "<redacted>"
}
}
Checks
Client or integration
Codex CLI
Area
Service lifecycle
Summary
What happened: On a machine enrolled as a hub client (runtimeRole: "client", connected via ocx hub invite + ocx connect), 'ocx service uninstall' unconditionally deletes '~/.opencodex/service-api-token'. In hub/client mode that file is not a service artifact: it holds the per-client data-plane key issued by the pairing exchange (the remote-hub docs say the client writes it to the existing owner-only service-api-token file, never config.json). After the deletion, every client lifecycle command fails closed and there is no CLI route back to a working client; the only fix is hand-editing ~/.opencodex/config.json.
What I expected instead: uninstall should not destroy a hub client's admission credential. Either skip the unlink when a client connection exists (or when runtimeRole is "client"), or refuse with guidance ("run ocx disconnect first"), or retain the file and state that it is the hub key. Ideally 'ocx disconnect' and 'ocx connect rotate' should also tolerate an absent token file, so that the documented recovery path exists at all.
Impact: the machine is stuck in a half-state: 'ocx connect status' still reports "Connection: connected" next to "Token file: missing", while sync and disconnect both refuse. Because the same uninstall path also restores native Codex, Codex silently falls back to its own provider routing instead of the hub, so the operator does not even see an error in the client.
Reproduction
" | ocx connect https://:8443 --management-url https:// --pairing-code-stdin --clients codex'. The client is connected and '~/.opencodex/service-api-token' exists.The CLI refuses both directions of the role flip, so no documented command can leave the broken state.
Recovery that actually worked: hand-edit '~/.opencodex/config.json' (remove the "client" object, set "runtimeRole": "standalone"), then invite and connect again. The abandoned hub-side key is still present and has to be revoked manually ('ocx access key remove '), because 'ocx connect revoke' needs a live connection.
Relation to existing work: #5144 (fix(client): clear disconnected hub token on standalone recycle) touches the same token lifecycle, but covers the disconnect/recycle direction, not uninstall.
Version
2.64.0 (npm latest; the same unlink is present on main, src/service/cli.ts:427)
Operating system
Client: macOS 26.6.2 (arm64). Hub: Ubuntu 26.04.1 LTS (x86_64).
Provider and model
No response
Logs or error output
Screenshots and supporting files
No response
Redacted configuration
{ "runtimeRole": "client", "port": 10100, "client": { "serverUrl": "https://<hub-host>:8443", "managementUrl": "https://<hub-host>:9445", "managementTransport": "direct", "selectedClients": ["codex"], "tokenEnv": "OPENCODEX_API_AUTH_TOKEN", "apiKeyId": "<redacted>", "tokenFingerprint": "<redacted>" } }Checks