OpenClaw EnterpriseDOCSGitHub

Service Account Driver Credential Delivery Flow

Overview

OCC starts with Installation Backend composition, then creates a Namespace-owned account, separately issues its provider-backed credential, and deploys an associated dedicated Codex Agent. The API owns the Backend client and upstream account calls; worker reconciliation repeats metadata checks before Compute projects the account token to Codex. This flow stops after Codex starts with the projected access token.

Entry Points

Flow

graph TD
  subgraph Composition["Installation composition"]
    A["Validate Backend and selected Driver"] --> B["API builds ChatGPT client"]
    B --> C["Inject Backend into ServiceAccount Driver"]
    A --> D["Worker keeps nonsecret Backend metadata"]
  end
  subgraph API["OCC API"]
    C --> E["Authorize and create OCC account"]
    E --> F["Create upstream account and private binding"]
    F --> G["Authorize separate credential issuance"]
    G --> H["Issue token and store account Secret"]
    H --> I["Persist credential ID and Secret reference"]
    I --> J["Save nullable Agent backendId"]
    J --> K["Validate binding and freeze Agent revision"]
  end
  subgraph Worker["Worker and Compute"]
    D --> L["Reauthorize deployment actor"]
    K --> L
    L --> M["Recheck Backend and binding metadata"]
    M -->|valid dedicated Codex| N["Project account token into Codex"]
    M -->|mismatch| O["Fail candidate"]
    N --> P["Codex authenticates token and starts app server"]
  end

Execution Trace

1. Compose the Backend and its ServiceAccount Driver

apps/controller/src/api-startup.ts:startApi

loadInstallationConfiguration validates the singular backend array and requires each ChatGPT Backend to declare the selected service_account member Driver. api-startup.ts:startApi then reads the mounted apiKeyPath, constructs Backend<ChatGPTClient>, and injects it into the bundled ChatGPTServiceAccountDriver factory. That client and admin key stay on the API side. The worker receives only nonsecret Backend definitions so it can reject stale or mismatched deployment snapshots before Compute effects.

2. Create the account and private Backend binding

packages/occ/src/index.ts:OpenClawController.createServiceAccount

OpenClawController.createServiceAccount authorizes the exact Namespace and allocates its sa_* identity. ChatGPTServiceAccountDriver.create creates the upstream account, registers rollback, and persists its private Backend binding in the same PostgreSQL transaction, including Backend, Driver, Namespace, account, and workspace identity.

apps/controller/src/backends/chatgpt.ts:ChatGPTClient.request owns each upstream HTTP response. It consumes successful replies and the structured 404 replies needed to confirm already-absent resources. Before rejecting another HTTP status or a response declared larger than its 4 MiB allowance, it cancels the unused body. That releases transport capacity before the next operation; a cancellation failure does not replace the sanitized Backend error. Successful and structured 404 response bodies are read incrementally. A chunked body is cancelled as soon as received bytes cross the same 4 MiB allowance, before parsing or retaining the overflowing chunk.

Account creation and credential issuance are separate operations. Creating the account does not issue a token, and later issuance or deletion requires that exact binding to match the current configured Backend and member Driver.

Before deletion, OCC locks the Namespace and account, then checks Agent drafts, selected versions, queued or claimed Agent revision work, and queued or running Agent provisioning plans through serviceAccounts.hasReferences. A conflict returns before any Driver call can revoke the credential or remove its Secret. Namespace locking serializes this check with draft changes and deployment admission; the PostgreSQL reference query observes active pointers and pending work together during worker cutover. With no ServiceAccount Driver selected (no ChatGPT Backend), an account holding an issued access token answers 409 SERVICE_ACCOUNT_DRIVER_NOT_CONFIGURED naming the fix and stays, since nothing can revoke its token. With ?force=true OCC instead reads the binding's Backend, workspace, account, and credential IDs, removes the token Secret through KubernetesComputeDriver.deleteServiceAccountCredential and the account, and audits revocation: "skipped"; with a Driver selected, force is ignored and revocation runs.

3. Issue the credential and create one account Secret

apps/controller/src/drivers/service-account/chatgpt.ts:ChatGPTServiceAccountDriver.createCredential

OpenClawController.createServiceAccountCredential authorizes account update before ChatGPTServiceAccountDriver.createCredential issues a Codex-scoped token and registers one rollback callback. The callback first confirms revocation of that exact provider credential, then removes only the account-owned Secret whose token and credential ID both match. Unconfirmed revocation leaves the Secret available for recovery. The callback runs after PostgreSQL releases the account lock, so deletion uses uid and resourceVersion preconditions to protect a later issuance.

KubernetesComputeDriver.storeServiceAccountCredential stores the token in one account-owned control-plane Secret. If the create reply is lost, an exact readback of ownership, token, credential ID, uid, and resourceVersion confirms the stored state without repeating or deleting the Secret. An absent or mismatched object leaves the create failure in force; an unreadable or unidentified object remains an unknown outcome. The Driver owns compensation after either storage failure or a later known transaction failure.

The private Backend binding retains workspace metadata. The private credential ID, internal credential reference, and audit changes commit together. The Secret also records the provider credential ID. A crash or an unknown COMMIT that failed runs no compensation, so before issuing, and when deleting an account with no recorded credential, the Driver reads the Secret through KubernetesComputeDriver.findServiceAccountCredential, revokes the credential it records, and deletes it only while it still records that credential. With no ServiceAccount Driver selected (no ChatGPT Backend), issuance answers 409 SERVICE_ACCOUNT_DRIVER_NOT_CONFIGURED naming the fix after the grant and account lookup; a selected Driver that fails stays 503.

4. Save Agent Backend intent and admit the Agent revision

packages/occ/src/index.ts:OpenClawController.createAgent, updateAgent, deployAgent

Agent backendId is nullable. Create omission saves null; PATCH omission preserves the current value; explicit null clears it; and a nonnull ID must name a configured Backend. Saving or changing the draft Agent reference makes no upstream call. The Agent selects the issued account through harnessAuth: { method: "codex_pat", source: { kind: "service_account", namespaceId, id: serviceAccountId } }.

deployAgent authorizes the Agent, Configuration, and associated account, then validates access_token ownership with validateServiceAccountBackendBinding. Managed access-token deployment requires the exact nonnull Backend, selected member Driver, workspace, account, recorded credential issuance, and dedicated Codex execution. An account without an issued credential cannot deploy; without a ChatGPT Backend that refusal is 409 SERVICE_ACCOUNT_DRIVER_NOT_CONFIGURED. Admission freezes the account identity, exact credential reference, private Backend binding, and Agent backendId in the Agent revision. Supplied API keys use the same harness binding path through an OCC Secret; native account references are not model-auth selectors.

5. Recheck metadata and project the account Secret

apps/controller/src/drivers/compute/kubernetes/index.ts:KubernetesComputeDriver.prepareRevision

ControllerWorker.resolveRevisionBackend runs after IAM reauthorization and before Compute reconciliation. It rejects a missing configured Backend as BACKEND_UNAVAILABLE and a metadata mismatch as SERVICE_ACCOUNT_BACKEND_MISMATCH. Only Backend, Driver, workspace, account, and issuance metadata leave the repository; upstream account IDs, admin keys, and credential values stay private.

KubernetesComputeDriver.prepareRevision uses its internal prepareHarnessAuth rendering step and CODEX_LOGIN_MODE=codex_pat, then delivers the account token into a data-plane Secret for that Agent revision. Only dedicated Codex receives it. Embedded execution is rejected for managed access tokens. The Gateway receives no model credential. The trusted worker reads the CP source and manages the DP projection; workload ServiceAccounts receive no Secret API permission. Workspace ownership remains part of control-plane validation, without a runtime workspace override.

6. Authenticate Codex with the account token

apps/controller/src/drivers/compute/kubernetes/runtime-entrypoints.ts:AGENT_RUNTIME_ENTRYPOINT

AGENT_RUNTIME_ENTRYPOINT uses the same native login for Driver-issued account tokens and directly supplied codex_pat credentials:

sh
codex -c cli_auth_credentials_store=file login --with-access-token

Codex derives account identity from the token. The entrypoint clears the token environment and starts its authenticated app server. Refresh, rotation, and automated reconciliation remain deferred.

Debugging and Verification

Search documentation