Skip to content

vault_run

vault_run is the core tool. It decrypts the requested secrets locally, spawns a subprocess with them as env vars, and returns only the exit code and run metadata — never the subprocess output. Since v0.16.0, stdout/stderr are diverted to a channel the user owns (the daemon’s terminal when running in the foreground, or the agent log: ~/.local/state/ov/agent.log on Linux — $XDG_STATE_HOME/ov/agent.log if set — or ~/Library/Logs/ov/agent.log on macOS) instead of the MCP response, because tool responses are visible to the AI model. The plaintext values are zeroed from memory immediately after the process exits.

Each call runs against the (app, env) pair resolved and verified from the project’s .ov.yaml. Since OV-305 the vault daemon enforces that environment end-to-end — previously it was bridge-verified only, and the daemon silently served its start-time env. If the environment no longer exists in the app, the call fails closed with ENV_NOT_FOUND.


Parameter Type Required Description
command string[] Yes The command to run as an argv array
secret_names string[] Yes Names of secrets to decrypt and inject as env vars (may be empty)
expected_app string Yes (OV_STRICT_CONTEXT, default on; optional when strict context is disabled) The app slug the caller expects to target — refused on mismatch with the resolved .ov.yaml (strict context)
expected_env string Yes (OV_STRICT_CONTEXT, default on; optional when strict context is disabled) The env slug the caller expects to target — refused on mismatch
scope string No Delegation token from ov token mint, narrowing this call within the session floor
work_dir string No Working directory for the command. Defaults to the current directory
timeout_seconds number No Timeout in seconds. 0 or omitted means no timeout

{
"exit_code": 0,
"invocation_id": "744e4fb9-c051-4f9c-bfa8-f784083746ef",
"redacted": true,
"redacted_fields": ["stdout", "stderr"],
"output_note": "subprocess stdout/stderr are not returned to the agent; the user can view them on the daemon terminal (foreground) or in the agent log",
"secrets_injected": ["DATABASE_URL"],
"timed_out": false
}
Field Description
exit_code Process exit code
invocation_id Daemon-minted UUID — the join key between this response, the audit trail, and the output block in the daemon sink
redacted / redacted_fields Always true / ["stdout", "stderr"] — subprocess output never rides the MCP response
output_note Static pointer to where the output went
secrets_injected Names of secrets that were injected (never values)
timed_out true if the process was killed due to timeout

The response never contains secret values — and since v0.16.0 it never contains subprocess output either. To read a run’s output, check the daemon terminal or the agent log (~/.local/state/ov/agent.log on Linux, ~/Library/Logs/ov/agent.log on macOS) and match the invocation_id header. If your agent needs to act on output, have the subprocess write a non-secret artifact (a file, a summary line count, an exit code) and inspect that instead.


Run database migrations:

vault_run({
"command": ["go", "run", "./cmd/migrate"],
"secret_names": ["DATABASE_URL"],
"expected_app": "myapp",
"expected_env": "dev"
})

Run tests with multiple secrets:

vault_run({
"command": ["npm", "test"],
"secret_names": ["DATABASE_URL", "STRIPE_SECRET_KEY", "OPENAI_API_KEY"],
"expected_app": "myapp",
"expected_env": "dev"
})

When Claude Code calls vault_run, it typically says something like:

“I’ll run the database migrations using vault_run with your DATABASE_URL secret.”

Then calls the tool. The MCP response comes back with the exit code and the redaction notice. Claude never sees the database URL — and never sees the subprocess output either; it reasons from the exit code, and you read the output on the daemon terminal or in the agent log.


Error Meaning
secret_not_found One or more requested secrets don’t exist. Use vault_list_secrets to check available secrets.
decryption_failed KEK/DEK mismatch — session may have expired. Re-authenticate.
command_timeout Command exceeded the timeout. Increase timeout_seconds or check the command.
vault_run blocked The command was blocked by the inbound interceptor — it could exfiltrate injected secrets.