Upgrading
OpaqueVault is pre-1.0. Breaking changes land on minor versions and are always called out in the CHANGELOG. This page condenses each one into what breaks and what to do about it.
To upgrade the binary itself: brew upgrade ov on macOS, or re-run the installer on Linux (curl -fsSL https://get.opaquevault.com | sh). See Updating.
Upgrading the binary does not upgrade running processes. Two OpaqueVault processes are long-lived and keep executing the old binary until restarted: the agent daemon (ov agent) and the MCP bridge (ov mcp serve). After every upgrade, restart both — ov agent stop && ov agent start …, then reconnect MCP in your AI session (e.g. /mcp in Claude Code). The bridge prints its version, commit, and build time at startup; check the running daemon’s version with ov agent status. Running mismatched halves is exactly how version-skew bugs bite.
Next release (unreleased) — daemon fails closed on untagged run clients
Section titled “Next release (unreleased) — daemon fails closed on untagged run clients”What breaks: against the next release’s daemon, an older ov CLI will see empty ov run stdout/stderr, and a pre-v0.16.0 ov mcp serve gets empty stdout/stderr fields back in vault_run responses. Exit codes are unaffected either way, and the output isn’t lost — it lands on the daemon terminal (foreground) or in the agent log, headed by the run’s invocation ID.
Why: a run request that doesn’t identify its client class is wire-indistinguishable from a stale pre-v0.16.0 MCP bridge — the client shape that could surface subprocess output (and any secrets it contains) to the AI agent after an in-place upgrade. The daemon now defaults to diverting output for any run it can’t positively attribute to the user’s own terminal, instead of defaulting open. The daemon also logs a warning in agent.log each time this happens, naming the fix.
Fix: upgrade the ov binary everywhere, and restart the daemon and any long-lived ov mcp serve (see the note at the top of this page). No flags, no config — a current CLI is all it takes.
Next release (unreleased) — KEK relock: OV_SESSION_TTL removed
Section titled “Next release (unreleased) — KEK relock: OV_SESSION_TTL removed”What breaks: ov agent start refuses to start while OV_SESSION_TTL is set in its environment, printing a migration error. Both remaining timer vars now enforce a 24-hour ceiling — values above 86400 seconds also fail agent start. (The session_ttl_remaining_sec field disappears from the agent-socket status reply; released daemons did emit it, but it never appeared in ov agent status --json or on the MCP wire and no released client read it, so there are no client-visible JSON changes.)
Why: the three-timer relock model (idle 30m / max-lifetime 8h / session-TTL 1h) collapsed to two: 8h idle (OV_KEK_IDLE_TIMEOUT) or 24h absolute since unlock (OV_KEK_MAX_LIFETIME), whichever fires first. Silently ignoring a set OV_SESSION_TTL would loosen a deployment that pinned it low by up to 288× with zero signal, so the gate fails closed instead.
Fix: unset OV_SESSION_TTL. If it was your binding cap, set OV_KEK_MAX_LIFETIME (seconds, max 86400) to the same value, and OV_KEK_IDLE_TIMEOUT for the idle window. Note the defaults are much looser than before (8h/24h vs 30m/8h/1h) — to keep the old tighter behavior, set both vars explicitly, e.g. OV_KEK_IDLE_TIMEOUT=1800 OV_KEK_MAX_LIFETIME=3600.
v0.16.0 — vault_run no longer returns subprocess output
Section titled “v0.16.0 — vault_run no longer returns subprocess output”What breaks: any agent workflow, script, or MCP client that read stdout/stderr out of vault_run responses. The response now carries exit_code, an invocation_id, redacted: true with redacted_fields: ["stdout","stderr"], and a static output_note, plus secrets_injected (names only) and timed_out — never the output itself.
Why: MCP tool responses are visible to the AI model. Anything a subprocess prints — including a secret it echoes, transforms, or embeds in a stack trace — was one tool call away from the model’s context. Output now goes to a channel the user owns: the daemon’s terminal in --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), as one block headed by the response’s invocation_id.
Fix: treat exit_code as the agent-facing signal. Where the agent genuinely needs to act on run results, have the subprocess write a non-secret artifact (a file, a status line, a count) and read that; humans read the full output in the daemon sink.
v0.11.2 — ov auth derive-machine-key --app removed
Section titled “v0.11.2 — ov auth derive-machine-key --app removed”What breaks: any script or runbook that calls ov auth derive-machine-key --app <slug>. The flag returns a deprecation error in v0.11.2 and is hard-removed in v0.12.0.
Why: the --app-scoped derivation never worked correctly — it re-keyed the vault session onto a divergent HMAC namespace, so cross-namespace reads and writes 404’d. The machine key was always master-KEK-equivalent on the only path that worked. --app on ov run and ov agent start is unchanged — those are routing flags, not key-scoping.
Fix — regenerate without the flag:
ov auth derive-machine-key --out /path/to/machine.kekRecovering orphaned secrets. If you ever wrote secrets using an --app-scoped machine key, those rows landed under a divergent key and are invisible to your normal master-password session (cross-key operations never completed, so only same-key writes left rows behind). To recover the plaintext:
- Do this on a quiescent host — no untrusted co-tenant workloads while secrets are in a subprocess environment.
- Use the old
--app-scoped key one last time to read each value out:Terminal window ov run --machine-key /path/to/old-scoped.kek --app X --secrets FOO -- printenv FOO - Re-store each value normally (interactive
ov secret set, under your regular session). - Delete the old key file.
The orphaned rows themselves can’t be deleted client-side in this release — they’re unreachable from both the machine-key path (ov secret delete doesn’t accept --machine-key) and the master-password path (different HMAC namespace). They’re encrypted under a key you’ve now discarded, so they’re inert — but if you want them purged from storage, file a support ticket referencing OV-273.
v0.11.0 — OV_STRICT_CONTEXT defaults to ON
Section titled “v0.11.0 — OV_STRICT_CONTEXT defaults to ON”What breaks: non-Claude MCP clients (or custom agents) that call vault_run in “ambient” mode — without declaring which app/env they expect to be operating on. With strict context on, vault_run requires expected_app and expected_env parameters and refuses the call when they don’t match the bridge’s resolved context.
Fix, in order of preference:
- Declare intent. Have the agent pass
expected_app/expected_envon everyvault_run, and use thevault_check_contexttool as a preflight after any directory change (side-effect-free except acontext_forgedaudit event on mismatch). This is the designed flow — it’s what stops acdinto the wrong repo from injecting production secrets. - Opt out (not recommended): set
OV_STRICT_CONTEXT=falsein the environment ofov mcp serveto restore permissive ambient mode. Unset, empty, and unparseable values all resolve to ON.
Also in this release:
- Sessions now expire after
OV_SESSION_TTL(default 1 hour) — stricter than the previous effective 8-hour max lifetime. If your agent sessions start locking mid-day (SESSION_LOCKED), setOV_SESSION_TTL(max24h) in the agent’s environment. See Troubleshooting. (Historical:OV_SESSION_TTLwas later removed by the KEK-relock change above — don’t set it on current versions.) - Self-hosted operators: migration
000020_audit_event_idis mandatory — runmake migrate-upafter upgrading the API server. - v0.10.2 was documented but never tagged; upgrading from v0.10.1 delivers both the v0.10.2 changes (including the strict-context flip and
vault_check_context) and the v0.11.0 additions at once.
.ov.yaml v1 to v2
Section titled “.ov.yaml v1 to v2”What changed: the v2 schema pins your project to the app’s immutable UUID via an app_id field (and optionally env_id), instead of relying on the mutable slug alone. v1 files (slug-only, no app_id) have been deprecated since v0.9.6.
What breaks: the MCP bridge refuses legacy v1 files — MCP tool calls in that project won’t resolve your app. The CLI still accepts v1 for now, printing a warning on every invocation.
Fix — one command in the project directory:
ov app pinov app pin takes no arguments — it reads the slug from the .ov.yaml’s existing app: field, resolves it to its UUID on the server, and writes the file to v2 in place. It’s fail-closed: if the file already has an app_id that disagrees with the slug lookup, it refuses to overwrite rather than silently repoint your project.
Don’t confuse it with ov app use — that sets your global config default app in ~/.config/ov/config.toml and never touches .ov.yaml.
Verify the result:
ov status --explainA clean trace shows .ov.yaml matching with an app_id, and no legacy-ovyaml warning.
Related
Section titled “Related”- CHANGELOG — the full, canonical change record
- Troubleshooting — symptom-first fixes
- Machine keys — current machine-key security model
- .ov.yaml — project config reference