Vault
A workflow refers to its secrets by name, such as vault:github, and never
contains a value. The same name resolves from two places:
- the local Vault, an encrypted file in your repository that
oci workflow runreads when you run in the container; and - the workspace Vault in OutcomeCI, which cloud runs and
oci workflow run --cloudread.
Store each secret under the same path in both, and the workflow runs unchanged in either place.
How a credential authenticates
A workflow binds a secret to a connector and says nothing about how it authenticates:
secrets:
github: vault:github
apis:
github: {uses: github, auth: secrets.github}The credential stored at that path decides. A plain value is a token. A typed credential carries its type, its settings, and its secret fields, and the runtime authenticates with it the way its type describes: it sends a token as it is, or exchanges client credentials, a refresh token, or a signing key for a short-lived token first. Switching a workflow from a token to an OAuth app is a change in the Vault, not in the workflow.
Each connector lists the kinds of credential its API accepts (see Connectors). A credential the connector cannot use fails before any request is sent, with a message that names the connector, the Vault entry, and the kinds it accepts:
github cannot use a basic credential (Vault entry github); it accepts: token, app_installationThe connector supplies what it already knows, such as a token endpoint or the headers its API expects, so you only store what belongs to your account.
Credential types
| Type | Stores | Settings |
|---|---|---|
| plain value | the secret itself, used as a token | none |
auth_header |
value |
--header-name, --scheme |
api_key |
api_key |
--header-name, --prefix |
basic |
username and password |
none |
oauth2 |
client_secret, and refresh_token for the refresh_token grant |
--client-id (required), --grant-type, --scope, --token-url, --audience, --account-id |
oidc |
client_secret |
--client-id (required), --issuer-url, --scope, --audience |
jwt_bearer |
private_key |
--issuer (required), --subject, --token-url, --audience, --scope |
app_installation |
private_key |
--app-id and --installation-id (required) |
Settings a connector fixes, such as a token URL, can be left out. Supply
secret fields through stdin: --value-stdin fills a type's single secret
field, and --secrets-json-stdin reads a JSON object when a type has several,
such as a username and password.
When a provider replaces a secret as it is used, for example a refresh token that is valid only once, the runtime saves the new value back to the Vault it came from before the run continues. You never re-enter a rotated secret.
Local Vault
Create the local Vault once per repository:
oci vault local initThis writes .outcomeci/vault.enc, encrypted with AES-256-GCM, and adds it to
.gitignore. The key is written separately to
~/.config/outcomeci/vault-keys/<vault-id>.key with owner-only permissions.
Set OUTCOMECI_CONFIG_HOME to keep keys somewhere else, or
OUTCOMECI_VAULT_KEY_FILE to point at one key file. Back up the key: nothing
can recover a lost one.
Store a secret under the path the workflow references. Pipe it through stdin so it stays out of your shell history:
printf %s "$GITHUB_TOKEN" | oci vault local put github --value-stdinStore a typed credential with --credential-type and its settings:
# A GitHub App installation, with its private key from a file.
oci vault local put github --credential-type app_installation \
--app-id 123456 --installation-id 7890123 --value-stdin < app.private-key.pem
# A Slack app with token rotation.
printf '{"client_secret": "%s", "refresh_token": "%s"}' "$SECRET" "$REFRESH" |
oci vault local put slack/app --credential-type oauth2 \
--client-id "$CLIENT_ID" --grant-type refresh_token --secrets-json-stdinput checks a typed credential's fields before storing it, and refuses one
that is incomplete. Without a value option, it prompts for the value. List what
is stored, with paths and timestamps but never values:
oci vault local listoci workflow run resolves only the secrets the workflow references, and
passes their values to the runner container on stdin. The key and the
encrypted file never enter the container. oci workflow sync never uploads
vault.enc.
Agent logins
The local Vault can also hold the agent login a container run uses, under
agents/<provider>:
| Agent | Where oci workflow run finds its login |
|---|---|
| Codex | ~/.codex/auth.json, or $CODEX_HOME/auth.json, from codex login |
| Claude Code | CLAUDE_CODE_OAUTH_TOKEN, or the local Vault entry agents/claude |
| OpenCode | OPENROUTER_API_KEY, or the local Vault entry agents/opencode |
For Claude Code, create a long-lived token with claude setup-token and store
it:
oci vault local put agents/claudeWorkspace Vault
The workspace Vault holds the credentials that cloud runs use. A workflow can use a credential only after it is granted access, and secret values are write-only: they never return to the browser, the CLI, or an agent.
Store a credential
In the dashboard, open Vault and choose Add credential. Pick the credential type, enter its settings and secret values, and select the workflows that can use it.
From the CLI, pipe the value through stdin so it stays out of your shell history:
printf %s "$GITHUB_TOKEN" | oci vault put github \
--workspace-id WORKSPACE_ID --value-stdin --workflow-id WORKFLOW_IDoci vault put takes the same --credential-type and settings as
oci vault local put. A typed credential also needs --provider, the service
it belongs to:
printf '{"username": "%s", "password": "%s"}' "$USER" "$PASSWORD" |
oci vault put service/login --workspace-id WORKSPACE_ID \
--provider service --credential-type basic --secrets-json-stdinList the workspace's entries, with their IDs and grants but never their values:
oci vault list --workspace-id WORKSPACE_IDGrant access to a workflow
A workflow can read a credential only while it holds a grant. In the dashboard, choose Manage workflow access on the credential. A workflow's Settings page links to the same place under Vault access.
From the CLI, grant sets the complete list of workflows that can use the
entry:
oci vault grant ENTRY_ID --workspace-id WORKSPACE_ID \
--workflow-id WORKFLOW_ID --workflow-id OTHER_WORKFLOW_IDEach cloud run receives a short-lived lease scoped to its workflow's grants. Cancelling the run revokes the lease immediately. When a provider rotates a leased secret during a run, the run saves the new value as a new version of the entry.
Rotate and revoke
Rotating stores a new version of the secret value. Choose Rotate secret on the credential, or:
printf %s "$NEW_TOKEN" | oci vault rotate ENTRY_ID \
--workspace-id WORKSPACE_ID --value-stdinThe next run uses the new value. Rotation does not create a workflow version, so no workflow needs to be synced again.
Revoking disables an entry for every workflow. Choose Revoke credential, or:
oci vault revoke ENTRY_ID --workspace-id WORKSPACE_IDRotating a revoked entry makes it active again with the new value.
Let an agent request a credential
An agent connected to the OutcomeCI MCP server never asks for a secret in chat.
Its create_credential_deposit tool creates a single-use link, valid for 15
minutes, where you enter the secret directly into the Vault. The credential is
granted to the workflow the agent is configuring, and
get_credential_deposit_status tells the agent when it has arrived.
Deposits support every credential type. A type with several secret fields,
such as basic or oauth2 with a refresh token, shows one input per field.
Agents and workspace encryption
The Vault page also holds the workspace's Agents: the Codex, Claude Code, and OpenCode connections that managed runners execute with. They are not granted per workflow.
Workspace encryption is the versioned AES-256 key that protects managed artifacts. OutcomeCI manages it and never exports it; you can rotate it from the Vault page, and earlier versions remain available for data they already protect.