Agent repository credential flow
Overview
The Console lists approved repositories; admission saves selections and deployment freezes grants. The worker delivers sessions to Kubernetes embedded OpenClaw or dedicated Codex with compatible Harness authentication and no Sandbox Driver. See service forwarding and retirement and runtime qualification.
Entry Points
apps/controller/src/index.ts:createFastifyAppregisters repository-option and Agent lifecycle routes. Discovery requires Agent-create or exact-Agent update.apps/controller/src/console/agents/repositories.mjs:createRepositoryFieldsrenders discovery and inherited or custom access.apps/controller/src/worker.ts:ControllerWorker.prepareRevisionprepares repository sessions before invoking Compute.
The Installation selects a repository Driver and Backend. API, worker and service share an immutable registry; the Namespace is ready. Unbound Agents bypass this.
Flow
graph TD
Console["<b>Console create form</b><br/>Load safe Namespace choices"] --> Options["<b>Repo Driver projection</b><br/>Refs, names, allowed profiles"]
Options -->|Visible refs| Metadata["<b>Credential service</b><br/>Optional descriptions"]
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 any write"]
API -->|Known zero-binding rejection| OrdinaryRetry["<b>Ordinary retry</b><br/>Reuse Configuration directly"]
OrdinaryRetry --> API
API -->|Known repository-scoped rejection| FormLocked["<b>Recovery locked</b><br/>Keep Configuration ID and block retry"]
FormLocked -->|Reload current choices| Options
Options -->|Successful reload| Reselect["<b>Reselect current 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 --> Revision["<b>Deploy revision</b><br/>Freeze grants and deadline"]
Revision --> Worker["<b>Claimed worker</b><br/>Recheck actor and policy"]
Worker -->|Denied or expired| Close["<b>Cleanup ownership</b><br/>Close exact attempts"]
Worker --> Capability["<b>Check capability</b><br/>Fresh admissions only"]
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>Receipt journal</b><br/>Commit exact admission"]
Receipt -->|Acknowledged| New["<b>New material</b><br/>Record ID before delivery"]
Service -->|Existing 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/>Bounded retry, no remint"]
Wait --> Worker
Recover -->|Known session missing| Refuse["<b>Fail revision</b><br/>Retain cleanup obligation"]
New --> Compute["<b>Compute delivery</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>Execution consumer</b><br/>Exact revision 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>Native 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 revision and purpose"]
Queue --> Close
Close -->|Confirmed disposal| Terminal["<b>Terminal receipt</b><br/>Commit exact result"]
Terminal --> Done["<b>Cleanup settled</b><br/>Retain immutable evidence"]
Pod -->|Delete Agent| Delete["<b>Agent deletion</b><br/>Queue cleanup and retire Compute"]
Delete -->|Independent cleanup| Queue
Delete -->|Runtime retired| Finalize["<b>State 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,Revision,Attempt,Queue,FormLocked,Receipt,Terminal state
class Console,Options,Metadata,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
OpenClawController.listRepositoryOptions authorizes Namespace Agent creation
or exact-Agent update, checks Compute-owned availability, then projects opaque references,
names and profiles through GitHubRepoDriver.listOptions. Exact Harness validation
remains at deployment. No approvals yields []; closed Namespaces conflict.
Classified optional discovery failures map to 503 REPOSITORY_OPTIONS_UNAVAILABLE.
createRepositoryFields searches up to 1,000 choices with 16 attachments;
repositorySettings resolves inheritance intent through RepoDriver and the
database checks concrete bindings. Only fresh, empty drafts permit the classified
outage. For up to 20 visible refs,
apps/controller/src/drivers/repo/github/credentials/descriptions.ts:createGitHubRepositoryDescriptions
fetches descriptions with repository-scoped, metadata-only tokens, sharing the
provider queue and cleanup lifecycle. Missing metadata never blocks selection.
The form saves Configuration first and preserves it after known Agent rejections. Retries reuse it; repository retries require successful reload and nonempty reselection. Failed reloads block creation; obsolete completions cannot mutate the view. Unknown outcomes require stored Agent and Configuration reads.
packages/occ/src/index.ts:OpenClawController.repositoryBindingSelections
uses resolveRepositoryBindings after existing authorization. Inputs contain distinct opaque references and optional profiles, never provider tokens
or caller-selected grant identities; selections keep request order. The concrete
apps/controller/src/drivers/repo/github/driver.ts:GitHubRepoDriver.resolve
uses local registry policy, defaults omitted legacy profiles to git-write, and
makes no control-socket or GitHub calls.
apps/controller/src/drivers/repo/github/credentials/registry.ts:resolveGitHubRepositoryBinding
requires the exact Namespace/reference/profile combination. Its fingerprint
binds provider/App/installation/repository identity, duration policy and complete
Namespace profile policy, exact permissions and optional normalized
push-ref allowlist. Each binding has one grant; installations can supply several
repositories. OCC stores normalized Agent selections; an omitted update array
preserves them and an empty array clears them.
2. Freeze a deployable revision
packages/occ/src/index.ts:OpenClawController.admitRepositoryCredentials
re-resolves the draft, validates Compute topology and freezes Driver identity,
grants and an absolute deadline unaffected by renewal or recovery. Duration
86400 allows 24 hours. apps/controller/src/index.ts:clientRevision returns
only Driver identity, references, profiles and deadline.
apps/controller/src/composition/repository-credentials/platform.ts:composeRepoDriver
constructs GitHubRepoDriver for capability repo from a Backend-owned Unix
client, validated registry and public CA. Installation and Backend membership
must select the same Driver ID. The API and worker never load the token engine or
App key. The production startup flow owns composition and
sidecar launch; the service validates protected inputs before listening.
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. Before a
fresh attempt, GitHubRepoDriver.checkAdmissionReady checks the broker capability
with a bounded request. The worker also checks it before refreshing readiness.
An unavailable capability blocks fresh admission, but not recovery or cleanup.
Under the live claim and Namespace/Agent locks, State records the attempt and
immutable cleanup context before the Driver call. It rejects stopped or deleting
owners and stores identifiers and phases, never bearers or client files.
apps/controller/src/backends/repository-credentials/control-client.ts:UnixRepositoryCredentialControlClient
sends the bound request over the private socket. The service independently
resolves and compares the grant through
apps/controller/src/drivers/repo/github/credentials/registry-factory.ts:createGitHubRegistryDriverFactory.
The client validates each status before projection. DISPOSED permits historical
revoked or expired counts, but no active, pending or uncertain obligations.
Only a created response contains the bearer. The Driver encodes transient files with
apps/controller/src/drivers/repo/github/credentials/client/config.ts:encodeRepositoryCredentialSessionFiles,
returning status with that closed file map. The worker
records the session ID before passing files to
ComputeRevisionContext.repositoryCredentials.
A confirmed open session yields a retained binding without files. recoverOnly
finds or fences unfinished admissions and closes recovered sessions.
Fresh material requires confirmed disposal or a missing opening without a recorded
session ID; invalidated known sessions block automatic same-revision replacement.
Closing sessions raise REPOSITORY_CLEANUP_PENDING until disposal, subject to Work
bounds and the revision deadline. Validated DISPOSED observations survive service pruning.
apps/controller/src/drivers/repo/credentials/control.ts:createControlAdmission
reserves before releasing material. The worker's
apps/controller/src/backends/repository-credentials/receipt-store.ts:RepositoryReceiptStore
commits exact admission fences and original-broker terminal observations. Failure
before terminal commit remains unknown; transport failure cannot establish absence.
4. Deliver and retain one complete runtime generation
apps/controller/src/drivers/compute/kubernetes/repository-material.ts:repositoryMaterialSpec
validates new | retained bindings against the revision and hashes sorted
reference/session pairs.
apps/controller/src/drivers/compute/kubernetes/repository-material-store.ts:RepositoryMaterialStore.prepare
validates ownership and contents before creating immutable
Agent/revision/session-owned Secrets. It reports missing retained bindings. The worker's RepositoryCredentialLifecycle.repair closes that subset
and requires disposal before replacement, then retries Compute once. Missing
inventory fails the revision. Pending closure blocks replacement until bounded
retry or active-revision continuation confirms disposal.
apps/controller/src/drivers/compute/kubernetes/repository-material.ts:repositoryMaterialDeployment
mounts Secrets only in the first init container. Sorted projection items prevent
key-order changes from triggering rollouts; session replacement still does.
apps/controller/src/drivers/compute/kubernetes/repository-material-init.ts:REPOSITORY_MATERIAL_INIT_ENTRYPOINT
validates a complete projection, then writes mode-0700 directories and mode-0600
files into memory-backed storage. REPOSITORY_NATIVE_GIT_INIT_ENTRYPOINT mounts
that private subPath at /run/oce/repository-credentials, avoiding the
fsGroup-writable volume root. It calls
apps/controller/src/drivers/repo/github/credentials/client/native-git.ts:prepareNativeGitConfiguration
with private-file checks. Retry removes only a validated private gitconfig.
Both init steps gate startup; the consumer mounts material read-only. Public
metadata and gateway bearers remain separate.
apps/controller/src/drivers/compute/kubernetes/index.ts:KubernetesComputeDriver.activateRevision
replaces the consumer when material changes, including within one revision.
Readiness requires role, revision and generation. Dedicated replacement preserves enrollment and revision-private storage. KubernetesComputeDriver.prepareRevision rechecks material after plugin,
gateway and node observations, including for successors; changed generation or
lost readiness returns incomplete.
The gateway receives neither repository material nor repository-gateway egress.
Compute grants consumer egress; Helm admits consumers through
credential-sidecar ingress selectors. Native preparation
writes aggregate gitconfig without reading bearers. System Git includes
/run/oce/repository-credentials/gitconfig, preserving HOME/global configuration.
Embedded repositoryNativeConfiguration keeps the gh router first in
tools.exec.pathPrepend. AGENT_RUNTIME_ENTRYPOINT sets Codex's
allow_login_shell=false and shell_environment_policy.set.PATH. The Harness
model environment remains intact. App keys, JWTs, installation tokens and the
control socket never enter this material set. Selected Codex plugins can read /app/node_modules/openclaw,
/home/node/.openclaw/plugin-skills and /home/node/openclaw-runtime-assets/plugin-skills
for the stock app-server and published skills inside sandboxed Codex tools. Repository-bound Codex consumers additionally receive stock Codex
allow_local_binding = true, mode = "full", and the exact broker hostname
allowance; explicit denies prevail. That repository profile also grants
read-only access to /opt/oce/repository-credentials and
/run/oce/repository-credentials so the native binary, Git helper, and generated
session material remain reachable inside sandboxed Codex tools. The
networking contract
defines dedicated/embedded eligibility. Unbound policy and broker authorization remain unchanged.
Compute supplies CA trust; TLS verification stays enabled.
5. Authenticate native Git and route GitHub CLI commands
Stock Git owns commands, remotes, push URLs, worktrees and settings. Configuration
rewrites canonical HTTPS hosts to their gateway origin. The scoped helper checks
host/path, generation and deadline before supplying the bearer. OCE_REPOSITORY_REF
disambiguates bindings, not destinations. Local identity, hooks, aliases, overrides
and other helpers remain available; there is no whole-command preflight or egress
confinement.
See the routing limits.
pushRefAllowlist selects image-owned hooks.
apps/controller/src/drivers/repo/github/credentials/client/hook-dispatch.ts:checkPush
matches the destination, normalizing trailing slashes and validating
usernames after binding selection; duplicate grants remain ambiguous. It checks
every destination ref and rejects the whole push before updates, though discovery
may contact the service. It delegates original arguments and input to
common-directory hooks. commonDirectory uses Git-supplied directories before
initial HEAD; linked worktrees resolve their shared directory through Git.
Custom hook paths and
API writes remain outside this best-effort guardrail.
apps/controller/src/drivers/repo/github/credentials/client/router.ts:routeRepositoryClient
routes supported gh commands using explicit targets or effective Git remotes.
It pins generation, reference and session, selects private gh configuration
and preserves HOME without mutating shared selection.
apps/controller/src/drivers/repo/github/credentials/profiles.ts owns the exact
Reader, Contributor and Collaborator permission maps. The GitHub route classifier
admits profile-selected REST operations and token-bounded GraphQL for
all three. Every GraphQL POST remains a possible write; Reader's token, not a
query parser, enforces its read-only grant. See
access levels.
The service exchange flow enforces bearer, immutable repository/profile, capacity and deadlines, acquiring fresh installation tokens under the same grant until the revision deadline.
6. Maintain, recover and retire ownership
apps/controller/src/worker.ts:ControllerWorker.completeActivatedRevision
commits completion and maintenance together, preserving the original actor.
Repository revisions use the Driver's 30-second interval or a shorter Compute
interval. Restart resumes queued work without inventing actors. A committed terminal
receipt survives broker restart; a missing known session remains invalidated.
REPOSITORY_SESSION_RECOVERY_UNSAFE permanently fails observation and queues
runtime retirement; later workers cannot remint for that revision. An authorized user can
deploy a new revision without settling old cleanup.
apps/controller/src/worker.ts:ControllerWorker.finalizeActiveRevision
atomically fails bounded observations and enqueues successors while the active revision remains authorized. Stop, policy drift, expiry and revoked authority
cannot use this continuation to reopen sessions.
packages/occ/src/state/postgres-work-queue.ts:PostgresWorkQueue.enqueueRepositoryCleanup
and terminal queue transitions persist exact revision-owned obligations. Registration checks claim and owner; recovery transfers eligible failures.
Work coalesces by revision and purpose, retaining its creating actor and source failure evidence. Queued or claimed Work keeps its
schedule and claim; later obligations requeue succeeded Work. Previously queued
cleanup remains eligible.
RepositoryCredentialLifecycle.closeRevision records session-only cleanup with
the attempts it marks closing. Cleanup retries at the Driver interval without consuming Work retries.
ControllerWorker.processRepositoryCleanup consumes validated owner-bound Work
without policy resolution, admission or material delivery, even after the actor
loses ordinary permissions. Terminal-runtime Work fences attempts, closes sessions
and calls Compute's stopRevision for that revision. Compute mismatch or stop
failure remains retryable beyond foreground limits. Completion requires settled
sessions and runtime retirement. Session-only repair/rotation never stops healthy
workloads. CLOSED denies local use but awaits disposal; missing inventory or
invalidation does not prove provider settlement. Retained attempts support
cleanup after revision deletion.
ControllerWorker.processAgentDeletion queues cleanup and retires Compute without
waiting for sessions. After owner detachment, cleanup Work uses its revision key
for dispatch and audits. Under the current claim,
PostgresWorkQueue.completeAgentDeletion calls occ.finalize_agent_deletion to
detach attempts, delete live rows and audit deletion atomically. Attempts and cleanup Work survive without fabricated disposal. Sessions block
neither admission nor completion. Unresolved provisioning effects
still defer completion without consuming retries.
Compute retirement waits for owned Pods to stop before removing their material. It preserves Secrets referenced by actual Pods and current Deployments, and limits deletion to exact ownership with UID preconditions. Stop removes only the stopped revision's route and preserves newer shared resources. Route and Deployment deletion require the observed resourceVersion. Shared cleanup remains retryable after partial failure. Service restart cannot prove remote token revocation.
Debugging and Verification
Compare occ agent get AGENT_ID --output json with the admitted activeRevisionId.
Inspect worker events for
REPOSITORY_BINDING_CHANGED, REPOSITORY_CREDENTIAL_DEADLINE_EXCEEDED,
REPOSITORY_SESSION_RECOVERY_UNSAFE, REPOSITORY_CLEANUP_PENDING or
REPOSITORY_CLEANUP_COMPLETE. Check registry
identity and deadline before retrying.
repository-not-admitted means an unselected target; name-one-repository-target
or name-one-repository-ref requires explicit selection. Inspect material metadata
and Pod generation without printing bearers or Secrets.
The test guide distinguishes lifecycle, installed-runtime and live-provider proof. The image volume case checks Docker mounts; Helm checks rendered ingress. Neither proves CNI enforcement. Console recordings cover fixture/API behavior, not model, Slack or GitHub execution. Ready Pods and local commands do not prove live writes.
