ovm.sh / how it works

technical overview

How OVM works

OVM keeps several versions of Claude Code, Codex, and Pi installed at once and decides which one runs when you type cc. Everything else — instant switching, rollback that always works, updates that never make you wait — falls out of one design choice: versions are immutable directories, and the active one is a pointer. This page is the whole system, from the symlink under your shell to the release pipeline that got the bytes there.

Immutable versions, mutable pointer
Installing writes a new directory and never touches an existing one. Switching moves a symlink. That is why a switch is instant and a rollback cannot fail halfway.
The foreground never waits on the network
Launching reads local state only. Version checks and downloads happen in a detached background process, so a slow or wedged network can't delay the tool you're trying to run.
OVM manages itself the same way
OVM is its own product: immutable snapshots, a pointer, an activation probe, and automatic rollback if a new version can't run.

01The layout on disk

Everything lives under ~/.ovm/. Each product gets a directory of installed versions plus a current pointer, and ~/.ovm/bin/ holds the launchers that go on your PATH. Nothing is installed system-wide and nothing outside ~/.ovm/ is modified.

storage layout

~/.ovm/
├── bin/                        // on your PATH — these are what you run
│   ├── claude → ovm            // multi-call launcher: argv[0] picks the product
│   ├── codex  → ovm
│   └── ovm                     // the control plane (a real binary, not a link)
├── products/
│   └── codex/
│       ├── current → versions/rust-v0.145.0   // the pointer
│       └── versions/
│           ├── rust-v0.145.0/release/bin/codex
│           ├── rust-v0.130.0/release/bin/codex
│           └── dev:my-fix/dev/bin/codex       // your own build, same treatment
├── self/                       // OVM's own versions — see 05
│   ├── current → versions/0.0.3-alpha.7
│   └── versions/…
├── cache/                      // version indexes; never authoritative
└── config.json

A version directory is complete or it does not count: each carries a .complete marker written last, so an install interrupted by a crash or a lost connection is invisible rather than half-usable.

02Switching versions

A switch is a single atomic symlink replacement. The versions themselves are untouched — the one you leave stays on disk, byte for byte, which is what makes going back an instant, guaranteed operation rather than a re-download and a hope.

ovm use codex rust-v0.145.0

The bin launcher points at a current symlink, which points at one of several installed version directories; switching moves only that pointer. your shell $ cx launcher ~/.ovm/bin/codex pointer products/codex/current installed versions — immutable, all kept rust-v0.145.0 ACTIVE rust-v0.130.0 idle — one flip away dev:my-fix your build Switching rewrites one symlink. Nothing is downloaded, deleted, or rebuilt.

Because ~/.ovm/bin/codex is a link to the OVM binary itself, the same mechanism handles your own local builds. A patched Codex installs as dev:my-fix and sits in the list beside the released versions, so flipping between "what shipped" and "what I'm hacking on" is the same one-second operation.

03What happens when you launch

The launch path is deliberately boring: resolve the pointer, then exec the real binary so it inherits your terminal directly — OVM does not sit between you and the tool while it runs. Anything that might touch the network is pushed into a detached child that cannot delay you.

launch path

Launching resolves the active version from local state and execs it, while a separate detached process handles version checks and downloads. $ cx resolve pointer local filesystem only read cache banner / notice, no I/O exec codex YOUR TERMINAL detached — never blocks the launch above is a check due? rate-limited, default 4h refresh indexes registry → cache stage new OVM install, stay inactive A wedged network stalls only the detached child. The launch above already exec'd.

This split is enforced by a test that points every upstream at a black-hole socket — one that accepts connections and never answers — and fails if a launch takes longer than three seconds. Reintroduce a synchronous fetch on the launch path and it goes red.

04Installing a version

Downloads are treated as untrusted until proven otherwise, and a version becomes visible only at the end, in one atomic step. There is no window where a partly-written version can be selected.

  1. Resolve. The version index says where the bytes are — a signed CDN binary, an npm tarball, or a GitHub release asset, depending on the product.
  2. Download into a private staging directory. Redirects are constrained, response sizes are bounded, and the archive is never extracted over a live install.
  3. Verify. Checksums are enforced, and on macOS the extracted binary's code signature is checked before it is ever executed.
  4. Publish atomically. The staged directory is moved into place and the .complete marker is written last. A crash before that leaves an unreferenced directory, which is cleaned up — never a half-installed version.
  5. Switch, if asked. Installing and activating are separate: ovm install leaves your active version alone.

Concurrency is real here, not theoretical: two shells can install the same version at once. An install holds a lock, and if the owning process is killed mid-download the next one recovers the staging directory and completes it — the tested outcome is exactly one complete install.

05OVM updating itself

A version manager that can strand itself is worse than none. So OVM treats itself as another managed product, with one addition: a control plane. The binary on your PATH (~/.ovm/bin/ovm) is a stable copy that survives any swap; it hands off to whichever immutable snapshot is currently selected.

atomic self-update, with rollback

A self-update snapshots the current state, switches the pointer, and probes the new control plane; a failed probe restores the previous selection. the swap — signals blocked, one lock held snapshot current + control refresh control new plane in place move pointer self/current probe DOES IT RUN? probe fails probe ok restore everything exact prior selection + plane ↑ OVM 0.0.3 previous kept Fault injection at every mutation stage is a test, not a hope: each one must land back on the exact prior selection. If the control plane itself is ever damaged, `~/.ovm/self/control-previous self repair-control` restores it.

06Staying current without waiting

Updates are split across two invocations so that no command ever pays for a download. The first invocation notices work is due and stages the new version in the background; the next one activates it atomically and tells you it happened. If a staging attempt dies mid-download, the retry is immediate rather than waiting out the check window.

update lifecycle

One invocation stages an update in the background; the next invocation activates it and prints a notice. any ovm command check due → stage download + verify installed, not active pending marker on disk activate NEXT RUN invocation 1 — you notice nothing invocation 2 Policies: `on` stages and activates silently · `notify` asks first · `off` does nothing. Explicit control always wins: `ovm self update`, `ovm self use <version>`, `ovm self rollback`.

07How a release reaches you

OVM's own releases go through a pipeline built on one rule: the bytes you download are built from the public source tag they claim to come from. Development happens in a private repository, but no private binary is ever copied into a public release — the public build is rebuilt and attested from the public tag, and a release is only published after the artifacts have been verified.

private development → public release

A private tag is built and canary-tested, then exported to the public repository where it is rebuilt, attested, staged as a draft, validated, and finally published once. private — qualification only private tag version aligned build ×4 macOS + Linux canary REAL TOOL USE must pass public — ovm-sh/ovm-oss export source allowlisted rebuild + attest from public tag draft unpublished validate install, run publish ONCE Published releases are immutable and release tags are protected — a shipped release cannot be edited or re-pointed afterwards. Every checked identity must agree: public tag · source version · attested commit · archive checksum · the version the binary reports. Moving the “latest” marker — what installed clients update to — is a separate, deliberate step.

The same discipline decides which upstream versions OVM offers you: releases are held back until they provably run. That system is documented in methodology.

08claudex: Claude Code on GPT-5.6

One feature deserves its own diagram, because it routes your prompts somewhere unusual. claudex runs the Claude Code interface against OpenAI models through a local translating proxy, on your own ChatGPT subscription. It is an unofficial integration and is opt-in.

where a claudex prompt goes

Claude Code talks to a local proxy on the loopback interface, which translates to OpenAI's API shape and forwards to OpenAI; nothing is sent to Anthropic. Claude Code UI isolated home CLIProxyAPI 127.0.0.1 — LOCAL OpenAI — your ChatGPT account Codex OAuth grant Anthropic credentials are scrubbed from the session, and claudex sessions never mix with your normal Claude history. The proxy is OVM-managed: checksum-verified, identity-probed before traffic flows, and updated only while no session is using it. Ordinary `cc` sessions are untouched by any of this — they talk to Anthropic exactly as before.

09The properties this buys

Each of these is a consequence of the design above rather than a feature bolted on top, which is why they hold even when things go wrong.

propertywhy it holds
instant switchSwitching moves a pointer. No download, no rebuild, no reinstall — the other versions were never removed.
rollback always worksThe previous version is still on disk, complete and immutable. Going back is the same pointer move, in reverse.
no launch latencyThe foreground reads local state only; every network operation is detached and rate-limited. Enforced by a black-hole-socket test.
no half statesVersions are published atomically with a completion marker written last; interrupted work is invisible and reclaimed.
self-update is safeA new OVM must answer a probe before it keeps the pointer; if it can't, the exact prior selection is restored automatically.
verifiable downloadsChecksums on every path, macOS signature checks before execution, and public builds attested to the tag they came from.
your builds are first-classA local dev build installs as a normal version and switches identically, so comparing it against a release is one command.

10Where to go next

Methodology covers the other half of the system: how upstream releases are verified, benchmarked, and published before OVM offers them to you. Releases is the live registry. The source, including the architecture notes this page summarizes, is on GitHub.