Run the repository credential service
Use this procedure to launch independent Git and GitHub CLI clients without creating an OCC Agent or joining the platform lifecycle. To give a managed Agent repository access, follow the Agent guide.
You need a private service host, approved GitHub App access, TLS, and protected storage for service configuration and client sessions. Keep the gateway private to approved clients with enforced network access controls and HTTPS on port 443. A private address alone does not establish isolation.
Build and validate
From the repository root with Node 24 and the pinned pnpm dependencies prepared, build the emitted service and client artifacts:
pnpm credentials:buildpnpm credentials:check-config /absolute/path/service.jsonThe check validates protected configuration, RSA and TLS inputs without listeners
or GitHub calls. The builder emits separate service and client closures under
.build/repository-credentials, using Node built-ins without runtime node_modules.
For invalid-configuration, inspect the file and every directory in its absolute
path. Use root or service-user ownership, private configuration/key files, and
directories that other users cannot modify. A root-owned sticky temporary
directory is allowed above the protected immediate parent. Move files out of
shared writable deployment directories before retrying; the complete policy is
in the reference.
Create the protected configuration shown in the
reference. Use a GitHub
App installed on the selected repository with the permissions for your chosen
profile. Choose a gateway hostname resolvable by the intended clients and a
matching TLS certificate trusted by those clients; public DNS is not required.
Set gateway.publicOrigin to that HTTPS origin. GitHub CLI requires port 443
on that hostname. Restrict gateway access to the approved clients before starting
the listener. Provision a private control directory owned by the service/operator:
install -d -m 700 /absolute/path/control /absolute/path/sessionschmod 600 /absolute/path/service.json /absolute/path/app.pem /absolute/path/tls.keypnpm credentials:start --config /absolute/path/service.jsonThe configured control socket must be inside the private control directory. Keep the service's configuration, App key, TLS private key and control socket outside the Agent's filesystem mounts.
Open and use a session
Keep duration within maximumDurationSeconds and select an allowedProfiles
entry. Omitting --profile uses the configured default, git-write here.
Use Read-only (git-read) for reads and Contributor with issue management off
(git-write) for pushes and PR work. The example selects Contributor (git-full)
for issue management too.
The output directory must not exist, and its parent must be owned and mode 0700. The command writes private files atomically and prints the session identifier, deadline and directory, without printing the bearer:
pnpm credentials:operator open \ --socket /absolute/path/control/control.sock \ --duration-seconds 86400 --profile git-full \ --output /absolute/path/sessions/task \ --ca /absolute/path/gateway-ca.pemOmit --ca when the gateway uses a publicly trusted certificate. Protect the
resulting directory like any credential. It contains bearer, public metadata,
Git configuration, isolated gh configuration and optional public CA trust.
Mount only this private client material and the working directory into the
Agent. Preserve the session files' ownership and mode 0600, and their directories'
mode 0700. Bind-mount only the selected session directory, never its host parent
or sibling sessions. The client validates that directory beneath the protected
container root.
Use the emitted client launcher for each supported command. Its absolute path continues to work after changing into the cloned repository:
credential_client=/absolute/path/checkout/.build/repository-credentials/client/dist/drivers/repo/github/credentials/client/launch.jsnode "$credential_client" /absolute/path/sessions/task git clone \ https://credentials.example.internal/example/project.gitcd projectnode "$credential_client" \ /absolute/path/sessions/task git fetch originnode "$credential_client" \ /absolute/path/sessions/task git switch an-existing-branchnode "$credential_client" \ /absolute/path/sessions/task git push origin HEAD:refs/heads/agent-featureUse credential-free HTTPS URLs; the selected session helper supplies authentication. Git keeps its normal configuration, hooks and worktrees. These native settings are overridable defaults, so operators remain responsible for inherited URL credentials and configuration overrides.
Choose a session with the access level
required by the operation. The launcher checks that the
executable is exactly gh 2.100.0. Create a
request body file in the working directory, then use relative API paths:
node "$credential_client" /absolute/path/sessions/task gh api \ --method POST repos/example/project/pulls --input create-pr.jsonnode "$credential_client" /absolute/path/sessions/task gh api \ repos/example/project/pulls/1node "$credential_client" /absolute/path/sessions/task gh api \ --method PATCH repos/example/project/pulls/1 --input update-pr.jsonnode "$credential_client" /absolute/path/sessions/task gh api \ --method POST repos/example/project/issues --input create-issue.jsonnode "$credential_client" /absolute/path/sessions/task gh api \ --method POST repos/example/project/issues/1/comments --input comment.jsonnode "$credential_client" /absolute/path/sessions/task gh api \ --paginate repos/example/project/issues/1/commentsNative PR creation uses an explicit already-pushed head branch:
node "$credential_client" /absolute/path/sessions/task gh pr create \ -R github.com/example/project --base main --head agent-feature \ --title "Example change" --body-file body.mdSelect a separate branch when trying both REST and native PR creation. Do not
run gh auth login or inject a PAT when a command fails. The gateway routes and
exact App permissions define supported access.
Recover an admission
Run the operator commands from the checkout root. If open loses its response,
use the credential-admission ID printed to
stderr before dispatch. Repeat the command with the same duration and profile,
adding --admission-id:
pnpm credentials:operator open \ --socket /absolute/path/control/control.sock \ --duration-seconds 86400 --profile git-full \ --output /absolute/path/sessions/task \ --ca /absolute/path/gateway-ca.pem \ --admission-id ADMISSION_IDA recovered response contains recovered: true and credential-free session status,
without creating client files or returning the bearer again. Close that session
using its reported ID and inspect cleanup status. Then explicitly run open
without --admission-id, choosing a new output directory if needed.
Do not generate replacement admissions blindly after an ambiguous response.
An unknown stale ID or a service restart cannot recover the original session;
see the ephemeral-session limits.
Container images
From the checkout root, rebuild the artifacts from the source you intend to run, then build the two images:
pnpm credentials:buildpnpm credentials:imagepnpm credentials:client-imageThe service entrypoint is node /app/dist/repository-credentials.js; the client
entrypoint is
node /app/dist/drivers/repo/github/credentials/client/launch.js. Record the
source commit, working-tree changes and immutable image IDs with verification
results; a reused tag alone does not identify the tested source.
If build-time HTTPS downloads require an additional trusted CA, optionally pass a PEM CA bundle through a BuildKit secret:
docker build --secret id=build-ca,src=/absolute/path/build-ca-bundle.pem \ -f deploy/runtime/repository-credentials/Dockerfile.client \ -t repository-credentials-client:local .build/repository-credentials/clientThe secret supplies curl trust for that download step and is not stored in the image. Without it, curl uses the image's default CA trust. Runtime gateway trust still comes from the selected session configuration.
Each Dockerfile copies only its artifact's manifest and emitted code. The service
artifact excludes the client command modules; the client artifact excludes the
signing, session and listener owners. The client image installs Git and checksum-verifies pinned gh
2.100.0. Neither image includes service configuration, private keys, session
files or a control socket. The client entrypoint takes SESSION_DIRECTORY git|gh ARGS....
The optional deploy/examples/repository-credentials/compose.yaml maps port 443
on an explicitly selected private host IPv4 address to the service's port 8443.
Set CREDENTIAL_SERVICE_PRIVATE_ADDRESS to an address assigned to the host and
reachable from the separate client container. Do not use a wildcard address or
loopback: the client's loopback address refers to its own container. Before
starting the service, enforce host/container-network access controls that allow
only approved clients to reach this published port and the container listener.
The example does not install those controls; a private address alone is not an
access policy. Keep this endpoint off public forwarding, Ingress and load balancers.
Set CREDENTIAL_GATEWAY_HOSTNAME to the hostname in gateway.publicOrigin, such
as credentials.example.internal, with a matching trusted TLS certificate. The
client's extra_hosts entry resolves it to the selected private host address so
the client uses HTTPS port 443. Retain gateway.listen as 0.0.0.0:8443 inside
the service container. Any approved host client also needs hostname resolution
to the private address and the same CA trust; Compose supplies neither for the host.
Supply CREDENTIAL_SERVICE_UID, CREDENTIAL_SERVICE_GID,
CREDENTIAL_SERVICE_INPUTS, CREDENTIAL_SERVICE_CONTROL,
CREDENTIAL_CLIENT_SESSION, and CREDENTIAL_CLIENT_WORKSPACE. Match the UID/GID
to the protected files. Set CREDENTIAL_CLIENT_SESSION to only the selected
directory, such as /absolute/path/sessions/task; it appears as /session in
the client. Service/control mounts remain separate from client mounts, and the
reference configuration's file paths match these mounts. After configuring
private access and certificate trust, render the configuration and run the client:
docker compose -f deploy/examples/repository-credentials/compose.yaml configdocker compose -f deploy/examples/repository-credentials/compose.yaml up -d --build servicedocker compose -f deploy/examples/repository-credentials/compose.yaml run --rm --build client \ /session git clone https://credentials.example.internal/example/project.gitRendering Compose checks declared configuration. To verify delivered separation, inspect the running service/client mounts and client surfaces using the container qualification procedure. Also verify approved client connectivity and denied access from an unapproved workload and the relevant external network, without copying a session bearer into reachability probes. Neither a Compose rendering nor image inspection establishes deployment isolation or live GitHub compatibility.
Inspect and close
From the OCE source checkout:
pnpm credentials:operator status --socket /absolute/path/control/control.sock \ --session SESSION_IDpnpm credentials:operator close --socket /absolute/path/control/control.sock \ --session SESSION_IDInspect cleanup status after local closure. Pending or uncertain cleanup remains an obligation. Shutdown stops after its finite grace period even when cleanup remains unresolved; process exit is not proof of revocation. If writing client files fails after admission, the CLI attempts local closure and prints the affected session ID so you can inspect it.
For an uncertain push or mutation, inspect remote state before deciding on a new operation. For authentication failures, check the selected profile, installation permission, repository identity and TLS/DNS configuration. A moved repository requires trusted configuration refresh. After a process restart, admit a new session; old gateway bearers are invalid. Remove the old private client directory after closing its session and recording any pending cleanup.
