Agent repository credential flow
Overview
Codex uses prepared sessions; OpenShell receives placeholders. Workers authorize signed requests against live grants. Enrollment; qualification.
Entry Points
apps/controller/src/index.ts:createFastifyAppregisters repository-option and Agent lifecycle routes.apps/controller/src/console/agents/repositories.mts:createRepositoryFieldsrenders discovery and inherited or custom access.apps/controller/src/worker.ts:ControllerWorker.prepareRevisionprepares repository sessions before invoking Compute.
Flow
graph TD
Console["<b>Console form</b><br/>Load Namespace choices"] --> Options["<b>Repository policy</b><br/>Refs, names, profiles"]
Options --> API["<b>Agent API</b><br/>Recheck and save refs"]
Options -->|Unverified authorization or discovery error| CreateBlocked["<b>Create blocked</b><br/>Retry before writes"]
API -->|Known zero-binding rejection| OrdinaryRetry["<b>Ordinary retry</b><br/>Reuse Configuration"]
OrdinaryRetry --> API
API -->|Known repository-scoped rejection| FormLocked["<b>Recovery locked</b><br/>Keep Configuration ID; block retry"]
FormLocked -->|Reload choices| Options
Options -->|Successful reload| Reselect["<b>Reselect scope</b><br/>At least one repo and profile"]
Reselect -->|Explicit selection| API
Options -->|Reload denied, conflict or unavailable| FormLocked
Options -->|Expired or obsolete view| ViewStop["<b>No current-view retry</b><br/>Sign in or ignore stale completion"]
API --> Version["<b>Deploy</b><br/>Freeze grants and deadline"]
Version --> Worker["<b>Claimed worker</b><br/>Recheck actor and policy"]
Worker -->|Denied or expired| Close["<b>Cleanup</b><br/>Close exact attempts"]
Worker -->|Native sender| Capability["<b>Check capability</b><br/>Fresh admissions only"]
Worker -->|Protected Driver| Protected["<b>OCC finite session</b><br/>Prepare and install through Driver"]
Protected --> Compute
Worker -->|Active standing Git evidence missing| Observe["<b>Observe existing Harness</b><br/>No preparation or issuance"]
Observe -->|Durable check within five seconds| Worker
Worker -->|Repository execution cannot resume| Withdraw["<b>Withdraw Git</b><br/>New revision required"]
Withdraw --> Close
Capability -->|Available| Attempt["<b>Persist opening</b><br/>Before control request"]
Capability -->|Unavailable| Blocked["<b>Block new admission</b><br/>Worker stays unready"]
Attempt --> Service["<b>Private control</b><br/>Check bound registry grant"]
Service --> Receipt["<b>Journal</b><br/>Commit exact admission"]
Receipt -->|Acknowledged| New["<b>New material</b><br/>Record ID before delivery"]
Service -->|Open session| Retained["<b>Retained material</b><br/>No bearer recovery"]
Service -->|Lost response| Recover["<b>Recover only</b><br/>Find or fence, then close"]
Recover -->|Never delivered or disposed| Attempt
Recover -->|Known closing session| Wait["<b>Wait for disposal</b><br/>Retry, no remint"]
Wait --> Worker
Recover -->|Known session missing| Refuse["<b>Fail version</b><br/>Retain cleanup obligation"]
New --> Compute["<b>Compute</b><br/>Validate complete set"]
Retained --> Compute
Compute -->|Missing retained files| Repair["<b>Repair exact subset</b><br/>Close and verify disposal"]
Repair -->|Disposed| Compute
Repair -->|Closing| Wait
Repair -->|Missing| Refuse
Refuse --> Close
Compute --> Pod["<b>Consumer</b><br/>Matching Agent revision ID and material generation"]
Pod --> Command["<b>Git or gh command</b><br/>Pin target and session"]
Command -->|Other requests| Gateway["<b>HTTPS gateway</b><br/>Exact repository/profile"]
Command -->|Managed push with policy| PushRefs["<b>Pre-push</b><br/>Check destination refs"]
PushRefs -->|Allowed| Gateway
PushRefs -->|Denied| PushDenied["<b>Reject whole push</b><br/>No ref update"]
Pod -->|Durable maintenance| Worker
Pod -->|Stop or retire| Close
Close -->|Unavailable or pending| Queue["<b>Durable cleanup</b><br/>Coalesce by version and purpose"]
Queue --> Close
Close -->|Confirmed disposal| Terminal["<b>Receipt</b><br/>Commit exact result"]
Terminal --> Done["<b>Cleanup settled</b><br/>Retain immutable evidence"]
Pod -->|Delete Agent| Delete["<b>Delete</b><br/>Queue cleanup and retire Compute"]
Delete -->|Independent cleanup| Queue
Delete -->|Runtime retired| Finalize["<b>Finalizer</b><br/>Detach and remove live rows"]
classDef state fill:#EDF2F7,stroke:#879AB0,color:#25364A,stroke-width:1px
classDef operation fill:#EBF3F0,stroke:#7F9D93,color:#2B4038,stroke-width:1px
classDef condition fill:#F7F1E5,stroke:#B3A078,color:#514532,stroke-width:1px
class API,Version,Attempt,Queue,FormLocked,Receipt,Terminal state
class Console,Options,Worker,Service,New,Retained,Compute,Pod,Command,Gateway,Close,Done,Delete,Finalize operation
class Recover,Repair,Refuse,Wait,ViewStop,CreateBlocked,OrdinaryRetry,Reselect,PushRefs,PushDenied,Capability,Blocked conditionExecution Trace
1. Project choices and resolve Namespace policy during Agent admission
listRepositoryOptions checks Agent create in the Namespace or update on the
exact Agent, plus Compute availability. Bundled listings use the bound GitHub
catalog. Programmatic listings use the registered Repo Driver. Both reject stale
owner results. Deploy checks the exact Harness. Empty catalogs return [].
Absent repository adds meta.notConfigured. Closed Namespaces conflict.
Classified discovery failures return 503 REPOSITORY_OPTIONS_UNAVAILABLE.
OCC allows 1,000 ordinary repositories plus the separate Installation option
(1,001 choices). OCC rejects overflow with 503 DEPENDENCY_UNAVAILABLE.
createRepositoryFields searches choices and caps attachments at 16.
repositorySettings enforces bound Installation policy for explicit and
inherited saves. Unbound saves use the registered Repo Driver. The database
checks bindings. Only fresh, empty drafts tolerate classified outages.
Catalogs omit sender metadata, credentials and provider descriptions.
Save Configuration first. Known Agent rejection permits reuse. Repository-scoped retries require a successful reload and explicit selection of at least one repository and profile. Read stored Agent and Configuration after unknown outcomes.
Requests carry refs and optional profiles, never tokens or grant IDs.
Nonempty selections without bound built-in policy or a selected Repo Driver
return 409 RESOURCE_CONFLICT. OCC preserves order. GitHub defaults legacy profiles
to git-write without provider or control-socket calls.
apps/controller/src/drivers/repo/github/access/registry.ts:resolveGitHubRepositoryBinding
requires an exact Namespace/ref/profile. Each binding gets one grant with a
fingerprint covering provider, App, installation, repository, duration, full
Namespace profile policy, exact permissions and optional normalized push-ref
allowlist. Omitted selections persist. [] clears them.
2. Freeze a deployable version
apps/controller/src/composition/installation-config.ts:loadInstallationConfiguration
matches GitHub Backend and registry IDs. It snapshots policy before loading
packages or registries, preserving protected Driver identity.
packages/occ/src/index.ts:OpenClawController.forInstallation binds that owner.
File-based repository selects built-in policy. Optional repository.policyMode: built-in
preserves identity. Startup rejects other modes, drivers.repo and separate
public Repo Drivers. Programmatic registration remains available.
apps/controller/src/drivers/egress-proxy/openshell-configuration.ts owns
OpenShell parsing and chart constraints.
OpenClawController.admitRepositoryCredentials calls resolveBindings for
bound policy and RepositoryPolicyDriver.resolve otherwise. OCC checks
Compute topology and freezes Driver identity, grants and deadline across
renewal and recovery. validateRepositoryBrokerNetworkProxy rejects
broker-blocking Codex settings.
After approval lookup, changed bound ownership returns 503 before invocation. Unbound deploys recheck their Driver. Denial or changes to binding or scope block revision creation.
Protected repository.preparedGitHub uses the installed Sandbox/Credential
Proxy Driver. Finite sessions need no native sender, control socket, broker CA
or session files.
repository.nativeSender selects GitHubNativeRepositorySender to own native
sessions and files without an App key. The worker's prepared provider issues
and holds credentials. Cleanup uses recorded identity without policy resolution
or reauthorization.
3. Record ownership before opening a session
apps/controller/src/worker/repository-credentials.ts:RepositoryCredentialLifecycle.prepare
rechecks actor, Namespace, Agent, revision, Driver, grant and deadline.
GitHubNativeRepositorySender.checkAdmissionReady checks broker capability for
fresh admission with a two-second timeout. Fresh OPEN sends preparedOnly: true.
Old parsers reject it after socket replacement. Control requires a prepared
backend. Before Driver invocation, State
uses a live claim and Namespace/Agent locks to reject stopped or deleting owners
and save immutable cleanup context. State keeps identifiers and phases, never
bearers or files.
apps/controller/src/backends/repository-credentials/control-client.ts:UnixRepositoryCredentialControlClient
sends the bound request over the private socket.
apps/controller/src/drivers/repo/github/access/warm-registry-factory.ts:createPreparedGitHubRegistryFactory
resolves and compares the grant. The client accepts DISPOSED with historical
revoked or expired counts and zero outstanding obligations.
apps/controller/src/drivers/repo/github/access/client/config.ts:encodeRepositoryCredentialSessionFiles
encodes files. The worker records the session ID before delivery to Compute.
Confirmed open sessions yield retained bindings without files. recoverOnly
finds or fences unfinished admissions and closes recovered sessions. Fresh
material requires confirmed disposal or a missing opening with no recorded
session. Invalidation blocks replacement. Closing raises
REPOSITORY_CLEANUP_PENDING within Work bounds and the revision deadline.
Closing cleanup uses the configured interval. Other obligations retain their
code and retry at age / 40, bounded by that interval and ten minutes.
apps/controller/src/drivers/repo/access/control.ts:createControlAdmission
reserves before release.
apps/controller/src/backends/repository-credentials/receipt-store.ts:RepositoryReceiptStore
retains fences and original-broker disposal evidence after pruning.
Transport failure cannot establish absence. preparedOnly requires protocol 3.
Unmarked v1 requests can only recover and close original IDs.
RepositoryCredentialLifecycle.verifyRelease and
apps/controller/src/drivers/repo/github/access/prepared-repository-git.ts:sessionQuery
require protocol 3 for native release and material selection.
Protected sessions use protocol 2. Selection checks policy-reduction version 2
against execution admission v7, the exact installed plan, recipient, standing
approval and session lifetime.
apps/controller/src/drivers/repo/access/protected-repository-git-installation.ts:selectedRepositoryGrantSql
shares per-member selection, Driver and expiry checks between production and
withdrawal. Frozen identities reject session replacement. Transaction locks remain.
4. Deliver and retain one complete runtime generation
apps/controller/src/drivers/compute/kubernetes/repository-material.ts:repositoryMaterialSpec
validates new | retained bindings against the version and hashes sorted
reference/session pairs.
apps/controller/src/drivers/compute/kubernetes/repository-material-store.ts:RepositoryMaterialStore.prepare
checks ownership, contents and deadlines before creating immutable Agent/version-owned
Secrets. It rechecks deadlines after requests and before creation or return.
Expiry leaves Secrets for owned cleanup. RepositoryCredentialLifecycle.repair
closes missing retained bindings and retries Compute once after confirmed disposal.
Missing inventory fails.
apps/controller/src/drivers/compute/kubernetes/repository-material.ts:repositoryMaterialDeployment
mounts Secrets only in the first initializer. Sorted projections prevent
key-order rollouts. Session replacement still rolls.
apps/controller/src/drivers/compute/kubernetes/repository-material-init.ts:REPOSITORY_MATERIAL_INIT_ENTRYPOINT
validates the full projection before writing in-memory mode-0700 directories
and mode-0600 files. REPOSITORY_NATIVE_GIT_INIT_ENTRYPOINT mounts the private
subPath at /run/oce/repository-credentials outside the fsGroup-writable root and calls
apps/controller/src/drivers/repo/github/access/client/native-git.ts:prepareNativeGitConfiguration
to check private files and write gitconfig. Retry removes only validated
private gitconfig. Both initializers gate startup. The consumer mount is
read-only. Metadata and gateway bearers stay separate.
apps/controller/src/drivers/compute/kubernetes/index.ts:KubernetesComputeDriver.activateRevision
replaces consumers whenever material changes, including within a version. Readiness requires
role, version, generation and current deadlines. Dedicated replacement preserves enrollment
and version-private storage.
KubernetesComputeDriver.prepareRevision rechecks material after plugin,
gateway and node observations; generation changes, lost readiness or expiry return incomplete.
Activation rechecks deadlines after observation.
Dedicated gateways lack repository material and repository-gateway egress.
Both consumers trust CAs through SSL_CERT_FILE, GIT_SSL_CAINFO and
NODE_EXTRA_CA_CERTS; overrides fail.
Compute grants consumer egress; Helm admits consumers through
credential-sidecar ingress selectors.
For prepared native Git,
apps/controller/src/drivers/compute/kubernetes/repository-compute-ingress.ts:reconcilePreparedRepositoryIngress
requires the peer’s hostname, TLS port and worker selector to match Helm’s
broker Service. System Git includes /run/oce/repository-credentials/gitconfig
and preserves global settings.
Embedded repositoryNativeConfiguration puts the gh router first in
tools.exec.pathPrepend. AGENT_RUNTIME_ENTRYPOINT sets Codex's
allow_login_shell=false and shell_environment_policy.set.PATH and retains
the Pod's NODE_EXTRA_CA_CERTS for sandboxed commands. Model authentication
persists. Consumer material excludes App keys, JWTs, installation tokens and
the control socket. The networking contract
defines Codex plugin and repository-bound filesystem and proxy access, explicit
denies and dedicated/embedded eligibility. Unbound policy, broker authorization
and TLS verification still apply. Codex checks the named profile at thread start,
resume, fork and turns.
5. Authenticate native Git and route GitHub CLI commands
Configuration routes canonical HTTPS hosts to the gateway. Before returning a
bearer, the helper checks host, path, generation and deadline.
OCE_REPOSITORY_REF selects bindings.
Routing limits describe command preflight and egress.
pushRefAllowlist selects image-owned hooks. scripts/build-repository-credentials.ts:stage
builds launchers that resolve dispatcher URLs through symlinks and relocations.
apps/controller/src/drivers/repo/github/access/client/hook-dispatch.ts:checkPush
rejects disallowed refs before updates and forwards arguments and input to
common-directory hooks. See hook limits.
apps/controller/src/drivers/repo/github/access/client/router.ts:routeRepositoryClient
routes gh by target or effective remote, pins generation, ref and session,
selects private configuration and preserves HOME and shared selection.
OpenShell's
apps/controller/src/drivers/repo/github/access/routes/request-selection.ts:requestHead
accepts CLI metadata.
git-write draftScope excludes the timezone header.
installationApiOperation admits gh auth status API-root reads under current
GitHub API grants. The grant owner authorizes signed requests.
apps/controller/src/drivers/egress-proxy/openshell-online-git-authority.ts:validBinding
uses normalizePushRefAllowlist for Git-only policies, including terminal /*.
Draft heads require concrete refs. Receive-pack rejects disallowed refs before approval.
Access levels define the grants.
Service exchange checks bearer, fixed repository/profile, capacity and deadlines; token refresh uses that grant until expiry.
6. Maintain, recover and retire ownership
The worker binds
ProtectedRepositoryGitAuthority with matching Drivers. OpenShellGitRuntime.start
reserves one instance before installing worker dependencies. Failed setup permits retry.
Close is terminal. Serial reconcile uses a 25-second timeout and one-second interval.
Close aborts and joins. Readback cannot restore authority.
The gRPC client verifies profile identity before treating omitted endpoint/credential lists as empty. Withdrawal accepts files-only profiles, refusing missing, mismatched or Git-overlapping profiles.
Maintenance uses 30 seconds, the sender interval or shorter Compute interval. Restart invalidates missing sessions without reminting. Prepared custody erases standing tokens after verified expiry plus skew; uncertain revocations remain unresolved.
Standing admission requires permission and current scope.
Reconciliation retains the bound Installation policy or unbound Repo Driver; conflicts prevent startup.
Under Namespace/Agent locks, eligibility rejects withdrawal or scope changes with REPOSITORY_STANDING_POLICY_CHANGED before release.
GitHub fingerprints exact permissions and repository identity. Issuance/delivery recheck opt-ins and grant; standing delivery requires one binding.
After hook acknowledgement, the observer
rechecks renewal policy and recipient. It refuses expiry, unacknowledged launches and observed-only previews.
Cold verification and
predecessor observation
confirm expired launch identity through fresh Sandbox and main-process readback.
Freshness limits
govern subtraction and succession. Installation and post-detach completion require ready credentials.
ControllerWorker.assertRepositoryGitMaterial awaits committed tokens after activation.
Quarantine/timeout fails deployment. For existing Agents, continueRevisionWithoutGit
atomically records REPOSITORY_GIT_TOKEN_QUARANTINED or REPOSITORY_GIT_TOKEN_PENDING
and maintenance, or retires the version (first-token handling).
Missing standing evidence closes Git. Within five seconds, the worker rechecks model authority, withdrawal, stop and replacement without preparation or activation (test).
OpenShellGitRuntime.repositoryWorkerRestartDisposition reports supported
held/preview execution original-worker-required; unsupported legacy routes unknown.
Preflight cannot authorize execution.
OpenShellGitRuntime.verifyExecution reports RepositoryExecutionRequiresRedeployError
when persisted v5 owner, binding and stable fence prove non-adoptable replacement. ControllerWorker.completeStandingRepositoryWithdrawal rechecks
ownership, closes sessions and queues withdrawal atomically.
Work records REPOSITORY_EXECUTION_REDEPLOY_REQUIRED; getDeploymentStatus projects
repositoryAccess, preserving history (regression).
apps/controller/src/worker.ts:ControllerWorker.processRepositoryCleanup consumes
owner-bound Work without admission or delivery. Terminal Work closes attempts and calls stopRevision, preserving replacements. Compute mismatch or stop failure retries. Completion requires disposal and retirement; missing sessions prove neither. Attempts survive deletion.
ControllerWorker.withdrawExpiredRepositoryAccess retires the original version for IAM
refusal, confirmed actor or Agent-principal absence, required model-source withdrawal,
or unsupported Git withdrawal. Model withdrawal precedes Gateway readiness.
Failed reads stay pending unless independent facts prove authority loss. Optional withdrawals cannot justify retirement. recoverPendingCredentialWithdrawals recovers exhausted original/companion work, preserving manual-replay holds.
See tests.
updateAgent commits finite removal under Namespace/Agent locks through withdrawFinite: irreversible session closure and cleanup with original deployment authority. The guard rejects sessions. Re-add, Stop supersession and stale-lease transfer cannot restore Git. Pending or denied Stop permits Git detachment, not retirement or running maintenance. Cleanup waits for running intent; authority loss, replacement and terminal retirement win.
PostgresWorkQueue.repositoryExpiryState prioritizes retirement. completeActivatedRevision preserves active removal supervision; REPOSITORY_ACCESS_WITHDRAWN is not activation proof. PostgresOpenShellGitRouteStore.adopt blocks removed Git. Its expire and V2 reduceCredentialPolicy require committed finite removal while stopped. Stopped maintenance defers its claim. Git cleanup may observe pending source effects without replay or completing source withdrawal, then awaits detachment. Other withdrawals remain running-only.
ControllerWorker.processAgentDeletion retires Compute and queues version-keyed cleanup
without waiting for sessions. PostgresWorkQueue.completeAgentDeletion
calls occ.finalize_agent_deletion to atomically detach attempts, delete live rows and audit.
Unresolved provisioning defers completion without consuming retries.
Compute waits for owned Pods to stop before removing material, preserving Secrets referenced by Pods/current Deployments. Deletion requires ownership and UID preconditions; Routes/Deployments also require resourceVersion. Stop preserves newer resources. Shared cleanup retries partial failure; service restart cannot prove remote revocation.
Debugging and Verification
occ agent get AGENT_ID --output json reports activeRevisionId. Inspect
REPOSITORY_BINDING_CHANGED, REPOSITORY_CREDENTIAL_DEADLINE_EXCEEDED,
REPOSITORY_SESSION_RECOVERY_UNSAFE, REPOSITORY_GIT_TOKEN_QUARANTINED, REPOSITORY_GIT_TOKEN_PENDING, REPOSITORY_CLEANUP_PENDING or
REPOSITORY_CLEANUP_COMPLETE. Check registry identity and deadline before retry.
apps/controller/src/worker.ts:processRepositoryCleanup logs
worker.repository-cleanup-warning with cause once per worker.
repository-not-admitted identifies unselected targets; name-one-repository-target
and name-one-repository-ref require explicit selection. Inspect metadata
and Pod generation without printing credentials.
Tests distinguish lifecycle, installed-runtime and live-provider proof. Docker/Helm checks exclude CNI; console fixtures exclude model, Slack and GitHub execution. Verify live PR updates, denial before provider contact and continued chat separately.
