Use a static credential source on the local OpenShell profile
This is the static Credential Gateway setup for new and existing sources. It does not qualify protected Credential Proxy access or transfer source ownership. For protected setup and upgrade decisions, start with Credential Proxy setup.
Use this procedure to register an OpenAI-compatible API key with the OpenShell Credential
Gateway and bind it to a dedicated Codex Agent on the
local OpenShell profile.
OpenShell keeps its own copy of the key, including with a Git Proxy selected, and substitutes it only on requests to
its authorized endpoint; the Harness receives only a placeholder. By default the
endpoint is https://api.openai.com/v1. The
credential source reference defines the
API behavior.
The profile installs private Gateway routing, so the deployment provisions an OpenShell Sandbox for the dedicated Codex Harness. After deployment, verify a model response.
For native Git with worker-managed GitHub App credentials, use the OpenShell Git push guide.
Before you start
Start the Kubernetes-only OpenShell profile and build the CLI:
go build -trimpath -o bin/occ ./cmd/occexport OCC_DEVELOPMENT_COMPUTE_DRIVER=kubernetesexport OCC_DEVELOPMENT_CONTROL_PLANE=kubernetesexport OCC_DEVELOPMENT_SANDBOX_DRIVER=openshell./scripts/dev-upExport the API URL and service-key file that dev-up printed, then select the
bootstrap Namespace:
export OCC_URL='<API URL printed by scripts/dev-up>'export OCC_SERVICE_KEY_FILE='<Service key file printed by scripts/dev-up>'export OCC_NAMESPACE="$(./bin/occ namespace list -o json | jq -r '.[] | select(.name == "default") | .id')"You need an existing provider API key in a private file, jq, and a model name
that key can use. Register the complete native model ID under
models.providers.codex, including any namespace. OCE's generated Gateway
configuration supplies the native provider explicitly; do not add it to the
model ID yourself.
Register the key
If arriving from Credential Proxy setup, use only the openai example below,
obtain whole-execution consent before creation, then
return to live-source setup.
Registration creates a Gateway copy. Live-source setup requires explicit adoption;
the copied-key rotation and cleanup below do not describe the adopted path.
Store the key as a Namespace Secret. Build the request file with owner-only permissions, delete it after use, and never put the key on the command line:
(umask 077; tr -d '\n' < /path/to/openai-key | jq -Rs '{name: "openai-model-key", value: .}' > model-secret.json)SECRET_REF="$(./bin/occ secret create --file model-secret.json -o json | jq -c .ref)"rm model-secret.jsonFor a Codex personal access token, including a service-account token, save that
token as the Secret above and register type: "codex-pat" with
secrets: {access_token: $ref} in the command below. Select dedicated Codex;
OpenShell keeps the real token outside the Harness. The Driver reference
defines its permitted endpoints and authentication limits.
Register an API-key Secret as an openai credential source:
Choose the revision model provider
through an Installation default or a deployment override. For a custom endpoint,
set MODEL_AUTHORIZED_BASE_URL to that URL when registering the source.
Registration authorizes the destination; it does not select the Agent's route.
Keep models.providers.codex.baseUrl at the fail-closed http://127.0.0.1:9
shown below for the Gateway's transport bridge.
The current source profile uses Bearer authorization; an endpoint requiring
x-api-key is not supported by this profile.
jq -n --argjson ref "$SECRET_REF" --arg authorized_base_url "${MODEL_AUTHORIZED_BASE_URL:-}" \ '{name: "openai", type: "openai", config: (if $authorized_base_url == "" then {} else {authorized_base_url: $authorized_base_url} end), secrets: {api_key: $ref}}' > credential-source.jsonSOURCE_ID="$(./bin/occ credential-source create --file credential-source.json -o json | jq -r .id)"./bin/occ credential-source get "$SOURCE_ID"Expected result: STATE and GATEWAY STATUS both show ready. The response
never contains the key. Changing the Secret afterward does not change the Gateway
copy. Explicit live adoption follows the separate procedure linked above.
Create the Agent and grant the source
Write a dedicated Codex Configuration. Replace each <model> with the native
model ID, that your provider account can use:
cat > configuration.json <<'JSON'{ "kind": "agent", "values": { "gateway": { "mode": "local", "bind": "lan", "controlUi": { "enabled": false }, "auth": { "password": { "source": "env", "provider": "default", "id": "OPENCLAW_GATEWAY_PASSWORD" } }, "http": { "endpoints": { "chatCompletions": { "enabled": true } } } }, "agents": { "defaults": { "model": "codex/<model>", "models": { "codex/<model>": { "agentRuntime": { "id": "codex" } } } } }, "models": { "providers": { "codex": { "baseUrl": "http://127.0.0.1:9", "api": "openai-responses", "models": [{ "id": "<model>", "name": "<model>" }] } } }, "plugins": { "allow": ["codex"], "entries": { "codex": { "enabled": true, "config": { "appServer": { "mode": "guardian", "approvalPolicy": "on-request", "sandbox": "read-only", "transport": "websocket", "url": "${APP_SERVER_URL}", "authToken": "${APP_SERVER_TOKEN}" } } } } } }}JSONCONFIGURATION_ID="$(./bin/occ configuration create --file configuration.json -o json | jq -r .id)"The OpenShell Sandbox Driver freezes "sandbox": "danger-full-access" and
"approvalsReviewer": "user" for dedicated Codex versions and removes
appServer.networkProxy. approvalPolicy stays configured. The bridge selects the native :danger-full-access profile,
which the Codex account must allow. OpenShell enforces the outer sandbox;
restricted native profiles require nested bubblewrap namespaces that it blocks.
See OpenShell Sandbox configuration.
Create the Agent with the source as its Harness authentication:
jq -n --arg configuration "$CONFIGURATION_ID" --arg source "$SOURCE_ID" \ '{name: "openshell-codex", configurationId: $configuration, executionMode: "dedicated", harnessAuth: {method: "credential_source", sourceId: $source}, credentialSources: [{sourceId: $source}]}' > agent.json./bin/occ agent create --file agent.json -o json > agent-response.jsonAGENT_ID="$(jq -r .id agent-response.json)"Deployment requires the Agent's service principal to have operate on the
exact source. It needs no permission on the Secret:
jq -n '{name: "Use a credential source", permissions: [{action: "operate", resourceKind: "credential_source"}]}' > role.jsonROLE_ID="$(./bin/occ iam role create --file role.json -o json | jq -r .id)"jq -n --arg principal "$(jq -r .servicePrincipalId agent-response.json)" \ --arg role "$ROLE_ID" --arg source "$SOURCE_ID" \ '{subjectKind: "identity", subjectId: $principal, roleId: $role, resourceKind: "credential_source", resourceId: $source}' > binding.json./bin/occ iam access-binding create --file binding.jsonDeploy and check the result
./bin/occ agent runtime-credentials provision "$AGENT_ID"DEPLOYMENT_ID="$(./bin/occ agent deploy "$AGENT_ID" -o json | jq -r .id)"./bin/occ agent deployment-status "$AGENT_ID" "$DEPLOYMENT_ID"Expected result: OCC accepts the deployment and freezes
{"method": "credential_source", "sourceId": "cs_…"} in the version. Compute
starts the Agent Gateway, then OpenShell creates the Sandbox, and the version
becomes active after Harness readiness and workspace-node connection. For
codex-pat, readiness also requires PAT authentication in the serving process;
a successful earlier login check is insufficient. Startup can take several minutes.
Without the access binding, the deploy request fails with 403. After activation,
verify a real model response; readiness does not prove inference.
If a managed codex-pat deployment stays unready, verify the Agent binds the
intended source, retains its required access binding, and can reach the permitted
provider endpoints through OpenShell. Use
Harness troubleshooting
and the deployment status above to inspect the failure. A serving process that
started without PAT authentication can remain unauthenticated after the provider
recovers; status polling observes authentication but does not reload it. Absent
authentication alone does not establish token revocation.
Let the existing deployment reach a terminal status before retrying; readiness
waits have a convergence deadline,
and pending cleanup must finish. After correcting the cause of a failed
deployment, run ./bin/occ agent deploy "$AGENT_ID" again to create a fresh
version, then check its deployment status and verify a model response.
Rotate the key
Changing the Secret does not change the gateway's copy. Update the Secret, or create a replacement, and then push it to the gateway:
./bin/occ credential-source update "$SOURCE_ID"For a replacement Secret, add --file with
{"secrets": {"api_key": <replacement ref>}} for openai, or
{"secrets": {"access_token": <replacement ref>}} for codex-pat. Running Agents keep the old value
until they are redeployed. The
update reference lists the
required permissions and failure cases.
Clean up
A source cannot be deleted while an Agent references it. Record the source's
current Secret, delete the Agent, and wait until agent get returns 404:
SECRET_ID="$(./bin/occ credential-source get "$SOURCE_ID" -o json | jq -er ' if .type == "openai" then .secrets.api_key.id elif .type == "codex-pat" then .secrets.access_token.id else error("Expected an openai or codex-pat source") end')"./bin/occ agent delete "$AGENT_ID"./bin/occ agent get "$AGENT_ID"Then delete the source, its Secret, the Configuration, and the Role:
./bin/occ credential-source delete "$SOURCE_ID"./bin/occ secret delete "$SECRET_ID"./bin/occ configuration delete "$CONFIGURATION_ID"./bin/occ iam role delete "$ROLE_ID"rm -f credential-source.json configuration.json agent.json agent-response.json role.json binding.jsonDeleting the source also removes the gateway's copy. Within about a minute of
registration, credential-source delete returns 503 and keeps the source as
deleting; run it again after that window. To discard the whole
environment, stop and clean up
the profile instead.
Troubleshoot
credential-source createreturns503: the API cannot reach OpenShell Gateway, or no Credential Gateway is selected. Check thatdev-upfinished and that theopenshell-gatewayPod inoce-systemis running.- A source stays
registeringordeleting: a registration was interrupted or its cleanup failed. Run./bin/occ credential-source deleteto remove any gateway copy; retry it until it succeeds. GATEWAY STATUSis notready: read thereasonfrom./bin/occ credential-source get "$SOURCE_ID" -o json.- Deploy returns
409: the Agent usesapi_keyor another Secret-backed method. With a Credential Gateway selected, onlycredential_sourceis accepted. credential-source deletereturns409: an Agent draft, active version, or pending deployment still references the source. WithCREDENTIAL_WITHDRAWAL_IN_PROGRESS, only withdrawal work still retrying for an earlier version that held the source blocks it: wait up to about an hour, or delete the Agent.iam role deletereturns409: an access binding still uses the Role. Deleting the source removes its bindings; otherwise find the binding with./bin/occ iam access-binding listand delete it first.
