OpenClaw EnterpriseDOCSGitHub

Protected repository access with OpenShell

Follow OpenShell enrollment. OCE matches the held launcher request to an authenticated middleware denial before issuing credentials. Installation and signed release precede Agent startup. Select repositories sharing explicit permissions. OCE verifies GitHub's returned scope against canonical IDs; GitHub governs refs and REST/GraphQL. Grant changes block use.

git-write retains exact ref policy. Qualification requires configured capabilities and runtime evidence. git-read and git-full keep their existing semantics. Installation preview requires separate opt-in. Exact-dispatch and v7 reduction guarantees remain unqualified.

Keep Credential Gateway with the bundled OpenShell Backend. The Proxy serves repository access, while model access uses a Gateway Harness source. For upgrades, see compatibility upgrade.

Prerequisites

Configuration for a qualified release

Select Kubernetes Compute, dedicated Codex, Sandbox and Credential Proxy from one Driver registration. Use Installation configuration and artifact rules. Set repository eligibility in the GitHub Backend registry at /etc/openclaw/repository-registry/registry.json. Keep the session duration and repository.preparedGitHub for prepared credentials. Connect the selected Driver with repository.openShell and Helm openshellGit. Omit controlSocket, publicCaPath, and repository.nativeSender. Remove Compute's network.repositoryCredentials: startup refuses it without repository.nativeSender.

Set Helm openshellGit.enabled: true, openshellGit.mode: protected, repositoryCredentials.enabled: false, and repositoryCredentials.authority: prepared-git. Keep repository.openShell.authority on host 0.0.0.0 (or ::) and on the openshellGit.port value (default 8445), with the chart's files /etc/openclaw/openshell-git/tls.crt, tls.key and gateway-public.pem. Startup and the upgrade check refuse another host, port or path with CHART_LISTENER_MISMATCH. The repositoryCredentials inputs select the registry ConfigMap, worker-only App-key and existing custody Secrets, and approved issuer CIDRs. Preserve the custody key across restarts; never put either key in Agent configuration or Helm values.

Prepare the request-phase service

Prepare a separate serving certificate whose DNS SAN covers openclaw-enterprise-openshell-git.openclaw-system.svc, its private key, and the public PEM key from the selected OpenShell Gateway JWT signing bundle. Create these resources in the OCE namespace with your approved Kubernetes context:

bash
kubectl --context "$CONTEXT" -n openclaw-system create secret tls openshell-git-tls \  --cert=/secure/occ/openshell-git/tls.crt \  --key=/secure/occ/openshell-git/tls.keykubectl --context "$CONTEXT" -n openclaw-system create configmap openshell-gateway-public \  --from-file=gateway-public.pem=/secure/occ/openshell-git/gateway-public.pem

Add this to the existing production values. Replace the example namespace and Pod labels with observed labels for your Gateway and trusted supervisors; both call the middleware. Do not select Agent workload Pods as trusted callers.

yaml
openshellGit:  enabled: true  mode: protected  port: 8445  tlsSecretName: openshell-git-tls  issuerPublicKeyConfigMapName: openshell-gateway-public  clients:    - namespaceSelector:        matchLabels:          kubernetes.io/metadata.name: openshell-system      podSelector:        matchLabels:          app: openshell-gateway    - namespaceSelector:        matchLabels:          kubernetes.io/metadata.name: openshell-system      podSelector:        matchLabels:          app: openshell-supervisor

The chart exposes a ClusterIP Service on port 8445 and allows that port only from these peers under the chart's default-deny policy. It mounts the serving key, certificate and public issuer key at the fragment's /etc/openclaw/openshell-git paths in the worker. The API, initialization Job and repository sidecar do not receive this projection. Keep the worker port equal in Helm and the Installation. If the OpenShell namespace enforces egress, also permit its Gateway and supervisors to reach this Service's selected worker Pods on TCP 8445 and resolve cluster DNS.

Set authority.issuer to openshell-gateway:<gateway_id> from the actual Gateway JWT configuration, keyId to the contents of its kid_path, and audience to the registration audience below. The fragment's values are examples, not discovery. The Gateway's signing private key stays with the Gateway. Render and inspect the combined production values, then use your normal Helm upgrade procedure.

Register the request-phase service

Use matching stock OpenShell 0.1.6 Gateway and supervisor images for required request bindings. Register this middleware so OpenShell can enforce OCE's decisions; grant repository access separately. Use the certificate's exact Service DNS name and mount the serving CA's public certificate at tls_ca_cert_path on the Gateway. Its [openshell.gateway.gateway_jwt] signing key, public key, key ID and gateway ID must match the OCE verifier settings.

toml
[[openshell.supervisor.middleware]]binding_policy = "allowlist"bindings = ["http_request_pre_credentials"]name = "oce-prepared-git"grpc_endpoint = "https://openclaw-enterprise-openshell-git.openclaw-system.svc:8445"tls_ca_cert_path = "/etc/openshell/certs/oce-git-ca.pem"audience = "oce-prepared-git"max_payload_bytes = 4194304timeout = "5s"

The selected repository path and github-installation preview require exactly 5s here. OCE still caps its authorization decision at three seconds.

Start the configured OCE worker before restarting the Gateway: Gateway startup calls authenticated Describe and refuses an unavailable or incompatible service. OCE advertises the request-phase binding; policy admission calls ValidateConfig. The allowlist requires that binding: a service advertising only response hooks is refused by both Gateway and supervisor. Do not remove the required binding. The registration name must match repository.openShell.registrationName. OCE attaches the selected binding to the sandbox policy with on_error: fail_closed for github.com and api.github.com. Do not hand-author its binding identifier. Those hosts, and every other name under github.com (such as ssh.github.com, codeload.github.com, gist.github.com, docs.github.com and *.pkg.github.com), come only from this route: if another Sandbox Driver network policy (policy.networkPolicies, such as a source-control policy) also allows one, the Installation refuses to start and names that policy. ghcr.io and githubusercontent.com hosts are not covered. The Driver still needs at least one policy, such as the model endpoint's. An Agent network grant for those hosts on any port is refused the same way: saving it for an Agent with a repository binding, adding the binding beside it, deploy and live application each fail with a 409 that names the grant. IP-address hosts (IPv4, IPv6 or a range, private and loopback included) may lead to GitHub, so both checks refuse them too; use DNS names, such as a Service name. The checks compare names only: a DNS name that resolves to GitHub (a wildcard-IP name or your own record) is not detected; do not add one. Retain HTTPS, hostname verification and authenticated Gateway JWTs; do not enable allow_insecure_transport. This attachment alone does not establish qualified enforcement.

Grant access to a GitHub App installation

Use the GitHub App installation guide for membership and permissions, and the registry reference for policy. Prefer repository scope. To expose an installation scope, set a distinct installationAccess.repositoryRef, allow the Namespace with accessMode: installation, and select that reference with profile: github-installation in the Agent's repositoryBindings, as its only binding (Agent save and deploy refuse it next to another). No default or wildcard selects this scope; the sender must confirm github_installation support. The bundled OpenShell Credential Proxy requires observedHookPreviewWorkspace; otherwise deploy answers 409 RESOURCE_CONFLICT. Missing or incompatible registration returns 503 DEPENDENCY_UNAVAILABLE. Use 0.1.6 for incident key distrust; 0.1.5 remains affected.

Repository grants require authenticated allowlist/exact with only pre_credentials. Opted-in installation preview also accepts both binding fields absent; partial, malformed or explicit optional/wrong values fail. Both paths retain policy, recipient, isolation and authenticated launcher-denial checks. For git-write, startup probes stay within the selected repository endpoint without broadening grants, accessing credentials or releasing the Harness. github-repository and github-installation providers retain their discovery-path probe.

Absence grants only observed assurance: 0.1.5 and 0.1.6 default dynamic registrations emit identical absence, without a distinct advertised support signal. Before qualification, intentional downgrade is indistinguishable. After qualification, registration changes break the recipient fingerprint and close the route. Repository grants never use this fallback.

OCE does not add a ref allowlist to installation scope. GitHub's membership, permissions and branch rules set its limit. See operator verification and withdrawal for profile-specific allowed/denied requests and the required model-source choice.

Use the admitted repository

The Sandbox sets process-scoped safe.directory for its approved mount, allowing root-owned workspaces without trusting nested repositories or changing persistent Git configuration. push.negotiate=true reduces redundant transfers; push request limits apply.

With github-repository, use selected HTTPS Git URLs; API operations use token permissions. Both API scopes refuse runner-token and deploy-key creation. Selected-repository requests to api.github.com accept Time-Zone: Etc/UTC; GraphQL POSTs also accept Graphql-Features: merge_queue and bounded X-GH-CACHE-TTL hints (such as 24h0m0s). Hints support gh pr list --search; OCE ignores their values and checks current authorization. Duplicate headers are refused.

With git-write, use exactly https://github.com/$OCE_OPENSHELL_GIT_REPOSITORY.git for clone, fetch and push. Credential paths are case-sensitive: mixed-case URLs or those missing .git receive no credential. Git reports could not read Username or The requested URL returned error: 403.

Finite access ends at its deadline or when an admitted binding is removed. Removal withdraws that revision's whole Git set; re-adding cannot restore it. Remove standing approval with its current generation before clearing bindings. An authorized request can finish after withdrawal. Removal during incomplete activation can fail deployment while retaining the independently verified runtime; workspace setup stays pending. See withdrawal and retained supervision.

With git-write, the allowed draft POST to api.github.com accepts one Time-Zone: Etc/UTC header. draftPullLimit: one-per-grant allows one draft across Agents and versions sharing the grant. To allow more drafts from the same ref, set draftPullLimit: none in the Namespace policy and deploy a new version. OCE still records each draft use and checks draft status, head and base.

A one-draft grant spends its allowance at authorization, before OCE knows whether GitHub created a draft. On an uncertain push or draft outcome, inspect the original remote ref or PR and retained operation; do not replay it. See Credential Proxy uncertainty.

Diagnose a refused request

GitHub App permissions flags alone prove neither write access nor denial. Verify the authorized operation and remote result; Git push success does not prove pull-request API access.

Git hides JSON error bodies. A refused clone, fetch, ls-remote or initial push reports HTTP 403 (exit 128). Refused push uploads, usually for another branch or tag (request_not_allowed), report error: RPC failed; HTTP 403 and fatal: the remote end hung up unexpectedly. Even with Everything up-to-date (exit 1), GitHub received nothing.

After a known pre-send refusal, trace a fresh read. Git redacts authorization:

bash
GIT_TRACE_CURL=1 git ls-remote origin 2>&1 | sed -n 's/.*<= Recv data: //p' \  | tr -d '\n' | grep -o '"reason_code":"[a-z_]*"'

The draft helper reports Draft not created: the repository route refused it (access_withdrawn); GitHub did not receive it.

Reason codes

Code Meaning What to do
request_not_allowed The selected profile does not admit the request; repository-scoped limits include ref, repository and draft constraints Do not retry. Change the request or ask an operator
draft_already_opened The grant's one-draft allowance was consumed; creation may be uncertain Inspect GitHub without replay; see the draft limit
access_withdrawn This version's repository access ended: the binding was removed, the version was replaced or stopped, or its session expired Do not retry. An operator binds and deploys
credential_unavailable The route lost its credential, for example because GitHub rejected the App key or a renewal failed An operator checks the worker log; a rejected key needs a new deploy
route_blocked The worker closed this version's route for good, for example after the Sandbox was replaced An operator deploys a new version
route_not_ready The route is starting, verifying or briefly unavailable Retry in a minute
online_authorization_denied The worker did not answer in time, matched no route, or had too many decisions in flight for the Sandbox or in total Retry once, then ask an operator

Retry advice applies only to known pre-send refusals, never uncertain sends.

Deploying versions lack credentials; requests answer route_not_ready until activation.

Worker log

After held enrollment failure and cleanup, check worker logs. worker.openshell-git-probe-failed records the HTTP 400 reason:

The event omits stdout, stderr, response bodies, URLs and proofs. Collector attributes: occ.git_probe.reason and optional occ.git_probe.exit_code (limits). A parsed unexpected denial returns HTTP 409 preview_not_observed.

For refused Git requests, worker.openshell-git-denied records reasonCode from the table, the detailed reason (such as ref_not_allowed, draft_rejected, session_closed, material_unavailable, installation_changed or route_blocked), Sandbox ID and supervisor request ID. For lost credentials, worker.openshell-git-route-warning carries the same reasonCode to distinguish a removed binding (access_withdrawn) from a rejected App key (credential_unavailable).

Route state, restarts and recovery

REPOSITORY_GIT_ROUTE_PENDING means unverified Sandbox, supervisor or provider generation, or unavailable material. REPOSITORY_GIT_ROUTE_BLOCKED closes Git until redeployment. Continued chat requires independent model access and verified Git-only removal. Identity changes, overlapping GitHub access or failed observation can require retirement. While Git remains attached, the worker reuses one conditional detach request. A moved Sandbox version or unknown Gateway outcome retires the runtime. An empty profile workspace selects the platform profile. Files-only profiles may omit endpoint and credential lists. Exact identity and Git overlap checks still apply.

Standing succession confirms the original predecessor and generation with a fresh WITHHELD / CREDENTIAL_EXPIRED report, then rechecks Sandbox readiness and main process identity. Observation age plus monotonic read time must stay under 15 seconds at nanosecond precision. Both timestamps must fall within 30 seconds of local time. An elapsed budget leaves the route pending. A contradictory witness cannot authorize issuance, preparation or a new route fence. New installations and post-detach model completion require ready credentials and independent model authorization.

In OpenShell 0.1.6, Sandbox-wide policy reload during Git detach can interrupt model and workspace connections despite runtime, conversation and authorization retention. Inspect uncertain turn outcomes before retrying.

Transient observation failures close access until the same installation verifies (1–10 s retry backoff). Withdrawal preserves peers’ independent grants. Replacement workers cannot adopt held launches (github-repository, protected git-write and installation preview). Redeploy after worker_replaced or terminal bootstrap_failed. After verified v5 held-launch replacement, deployment detail retains history and reports repositoryAccess.state: redeploy-required and REPOSITORY_EXECUTION_REDEPLOY_REQUIRED. OCE verifies withdrawal and model authority. Historical v4 and installation-wide v5 cannot prepare, issue or renew credentials. Installation-wide issuance requires observed-hook preview. Replacement workers finish detach using recorded custody. Never replay uncertain writes. See Credential Proxy lifecycle. For prepared-material expiry, quarantine and retirement: Token Service lifecycle and its source flow.

OPENSHELL_GIT_RECONCILIATION_REQUIRED reasons: execution_replaced, supervisor_replaced, write_unknown, worker_restarted or gateway_unauthorized. worker.openshell-git-route-warning distinguishes unobserved reads, pending material, closed routes (worker_replaced, or withdrawn and repository_expired when another worker closed the route) and retained ones. OPENSHELL_GIT_WITHDRAWAL_UNRECORDED means unpersisted withdrawal. Confirm durable withdrawal and Git detach or runtime retirement. OCE drops closed routes after confirmed absence (two reads if never observed). Failed reads retain them. Gateway authorization refusal logs retained with gateway_unauthorized and retries with backoff until absence; preparation, adoption and verification remain refused, and denials log no_binding. worker.openshell-git-route reports same-worker restoration, with durationMs. Correlate revision and request IDs with worker.openshell-git-denied.

Qualification history

OpenShell 0.1.4 (5a7728c3) reported supervisor process and network identities without Gateway attestation; the Gateway authenticated only the Sandbox principal. Reconnect continuity therefore relied on the supervisor's claims. This observation does not qualify a newer candidate's identity.

That version also reset all Sandbox connections on provider material changes, including the workspace WebSocket and model streams. Activation passed through REPOSITORY_GIT_MATERIAL_PENDING, WORKSPACE_NODE_RECONNECT_PENDING, and WORKSPACE_NODE_PENDING. Later renewals repeated the reset (observed chat outages of about 1–7 s). Qualify connection continuity on the selected release; inspect uncertain request outcomes before retrying.

GitHub App key rotated or revoked

See replace a rejected App key. A ready Agent is not evidence of usable Git material. A changed key retires issued material within about a minute, even when GitHub rejects the new key.

Move from the earlier trial

Use stop and settle before changing the old owner. New startup rejects the broker trial. Keep explicit native Git separate from protected delivery.

Earlier documentation URLs remain as anchor bridges: setup, refusals, and restarts.

Search documentation