Skip to content

cli: tell users how to update, for the way they installed #1245

Open

Description

@pronskiy

Problem

There are five ways to install Omnigraph: install.sh/install.ps1, install-source.sh, Homebrew, Nix and Docker. Each one updates differently. To update, a user has to remember which one they used and find the matching command in docs/user/install.md. Nothing tells them a new version exists.

#79 added a self-updater. It was closed because a CLI that replaces its own binaries does not fit a project that also ships a server. This proposal replaces no binaries.

Proposal

  1. Install receipt. install.sh, install.ps1 and install-source.sh write .omnigraph-install.json next to the binaries: method, channel (stable, edge or a pinned VERSION), install dir, SHA-256 of omnigraph. The CLI trusts it only when current_exe() is in that dir and the hash matches.
  2. Detection without a receipt.
    • Homebrew: resolved current_exe() contains /Cellar/omnigraph/.
    • Nix: path starts with /nix/store/.
    • Docker: the Dockerfile sets OMNIGRAPH_INSTALL_METHOD=docker.
    • Anything else: unknown.
  3. Update notice. At most once a day, on stderr. Off when CI is set, stderr is not a TTY, or OMNIGRAPH_NO_UPDATE_CHECK=1. The check runs in the background and never delays a command.
    omnigraph 0.15.0 is available (you have 0.14.1).
    Update: brew upgrade ModernRelay/tap/omnigraph
    0.15 changes the HTTP contract (0.14 → 0.15). Upgrade omnigraph-server too.
    
    The last line appears only when the new release changes the HTTP contract or the storage format.
  4. omnigraph update. Runs the method's own updater after confirmation:
    • script: rerun the installer with the receipt's channel and install dir
    • Homebrew: brew upgrade ModernRelay/tap/omnigraph
    • Nix, Docker, unknown: print the instruction, run nothing

Out of scope

  • Replacing binaries from inside the CLI
  • Updating or restarting omnigraph-server
  • Version pinning per graph or per cluster

Open questions

  • Where the check reads the latest release: GitHub Releases API, or a small JSON published with each release that also lists its HTTP contract and storage format
  • Whether edge installs get the notice. A new edge build publishes daily, so it would show almost every day.

Activity

  1. ragnorc commented on Nov 4, 2026

    @ragnorc
    Contributor

    Summary

    A user who installed Omnigraph has to remember the install method in order to update. The method decides the command. The CLI never says that a newer release exists, and it never says when that release changes the HTTP contract or the storage format. The CLI and the server must move together. omnigraph version already prints the contract and the format of this binary. It does not contact a server.

    This is a missing feature. It is not a defect in a command that already exists.

    Checked again just before this comment. Issue #1245 is open. It has no comments. It has no linked pull request. The timeline has one event: the needs-triage label at 2026-11-04 13:14Z.

    Reproduction

    Checkout: main at 40bb2a223b9b9958a212857076f3602b5d8f1702 (2026-11-04 06:05Z, "fix(engine): read a commit of a deleted branch while a live branch retains it (#1240)"). git fetch origin main left HEAD and origin/main on that SHA. The worktree was clean.

    Host: Linux 6.12.94+ x86_64, rustc 1.97.1. brew, nix, docker, and PowerShell are not installed. /nix cannot be created (Permission denied). macOS and Windows were not run. The installer scripts were not executed against GitHub. A live install would download binaries and write an install directory. The script text is enough to see that they write no receipt.

    Binary: target/debug/omnigraph in this checkout (ELF x86-64, BuildID b4b4fdce5c9bc7dd3c3ac29847bd908aa1c6b7eb). It was not rebuilt for this check. It reports package 0.14.1 with channel null and commit null. That is the unstamped source-build shape in crates/omnigraph-api-types/src/lib.rs. The output matches the source at this SHA.

    omnigraph version (stdout and stderr separated, then again with stderr on a pseudoterminal):

    exit 0
    stdout:
    omnigraph 0.14.1
    internal-schema 16 (serves v14 to v16)
    http-contract 0.14
    stderr: empty (0 bytes), including when stderr is a TTY
    

    omnigraph version --json prints the object below and writes nothing to stderr. omnigraph --version prints only omnigraph 0.14.1.

    {
      "version": "0.14.1",
      "channel": null,
      "commit": null,
      "http_contract": "0.14",
      "protocols": { "studio": 1, "target": 2, "credential": 2, "access": 2 },
      "internal_schema": { "writes": 16, "serves": [14, 16] }
    }

    CI=true, OMNIGRAPH_NO_UPDATE_CHECK=1, and OMNIGRAPH_INSTALL_METHOD=docker leave that stdout unchanged and leave stderr empty. Those variables do not exist in the CLI.

    omnigraph update exits 64:

    error: unrecognized subcommand 'update'
    
      tip: a similar subcommand exists: 'upgrade'
    

    omnigraph --help lists upgrade (offline storage conversion) and version. It has no binary-update command.

    Path detection, on this Linux host only. A small program printed std::env::current_exe() and std::fs::canonicalize. A symlink at /tmp/og-path-exp/bin/showexe pointed at /tmp/og-path-exp/Cellar/omnigraph/0.14.1/bin/showexe.

    argv0=/tmp/og-path-exp/bin/showexe
    current_exe=/tmp/og-path-exp/Cellar/omnigraph/0.14.1/bin/showexe
    canonicalize=/tmp/og-path-exp/Cellar/omnigraph/0.14.1/bin/showexe
    cellar=true
    nix_store=false
    

    An extra symlink outside that tree resolved the same way. A copy of the program outside Cellar resolved to its own path (cellar=false). On this host, current_exe() already returns the symlink target, because Linux exposes /proc/self/exe. That is not a macOS result. Homebrew's main bottle is macOS arm64. This host has no Homebrew cellar.

    A failing test that expects an update notice is not the right proof. The notice was never part of the contract. version_command_prints_cli_version_http_contract_and_storage_formats_issue_977 in crates/omnigraph-cli/tests/integration/cli_schema_config.rs pins the current version text. A notice on stdout would fail that test. A notice on stderr would not. The runs above are the user-visible proof.

    Claims

    Confirmed. There are five install channels, and they update in different ways.

    docs/user/install.md documents the shell installer, Homebrew, Nix, Windows install.ps1, and install-source.sh. The container is documented there as an image and in docs/user/deployment.md. The image also contains the CLI: the Dockerfile copies omnigraph to /usr/local/bin/omnigraph. A manual cargo build is documented beside the source installer. npm packages are clients. docs/dev/versioning.md says current binaries ship through the installer, Homebrew, Docker, and GitHub Releases, and it says not to document cargo install.

    What each channel does today:

    • scripts/install.sh downloads a GitHub release archive, checks the archive SHA-256, and copies omnigraph, omnigraph-server, and omnigraph-azure-admission into INSTALL_DIR (default ~/.local/bin). The default channel is stable, with a fallback to edge. RELEASE_CHANNEL=edge and VERSION=<tag> select a channel. The script prints the channel. It does not store it.
    • scripts/install.ps1 does the same for omnigraph-windows-x86_64.zip into %USERPROFILE%\.local\bin. Parameters are -ReleaseChannel, -Version, and -InstallDir.
    • scripts/install-source.sh builds the local checkout, or clones SOURCE_REF (default main), and installs the same three binaries. It records no ref.
    • Homebrew: Formula/omnigraph.rb in ModernRelay/homebrew-tap installs the three binaries with bin.install and points at the v0.14.0 archives. .github/workflows/release.yml has an update_homebrew_tap job. The user command is the normal brew upgrade ModernRelay/tap/omnigraph. This assessment did not run brew.
    • Nix: flake.nix builds the three release programs from source. install.md shows nix build and nix run, and a pinned github:ModernRelay/omnigraph/<ref>. It does not show nix profile upgrade.
    • Docker: pull a new tag or digest. install.md says latest tracks stable, edge tracks the rolling edge release, and a deploy should pin a digest. The entrypoint starts omnigraph-server. It does not update the CLI.

    Partly confirmed. The user can find the update command in docs/user/install.md.

    The page tells the user how to install. It has no update section. A search of docs/ finds no brew upgrade, no nix profile, and no docker pull. Re-running install.sh is the same text as the install. The Homebrew, Nix, and Docker update commands are not written down. docs/user/operations/upgrade.md is the storage-format procedure (omnigraph upgrade). It is not a binary update.

    Confirmed. Nothing in the product tells the user that a new release exists.

    omnigraph version is local. docs/user/install.md says it reports what the binary speaks, without opening a graph or contacting a server. There is no startup notice, no OMNIGRAPH_NO_UPDATE_CHECK, and no install receipt. Release pages exist on GitHub. The CLI does not point at them.

    Partly confirmed. The sample notice.

    The lines in the issue (0.15.0 is available, you have 0.14.1, HTTP contract 0.14 to 0.15) are an example. They are not the live release state. GitHub releases/latest is v0.14.0, published 2026-10-22. The package version in this tree is 0.14.1 (crates/omnigraph-cli/Cargo.toml). There is no 0.15.0 release. The rolling edge release currently holds edge-20261103-03e0b62. Its body says storage format 16 and HTTP contract 0.14, the same as v0.14.0. A correct notice today would not print a contract-change line for that pair.

    Confirmed, with a narrower close reason. #79 added a self-updater and was closed.

    #79 is pull request #79, not an issue. Title: "feat(cli): add self-update mechanism (omnigraph update)". Closed 2026-05-12, not merged. It downloaded a release, verified SHA-256, and replaced omnigraph and omnigraph-server when they were in the same directory. It also added a background stderr notice.

    The close comment from @aaltshuler says the shape does not fit the project. The stated blocker is the adjacent-server swap: omnigraph update replaces the on-disk server binary while the process keeps the old file. The mismatch appears at the next restart, with no log line. The same comment records a smaller direction: omnigraph version --check reads GitHub Releases and prints whether a newer release exists. Docs point CLI users at brew upgrade or the install script, and server operators at their orchestrator. The comment says no self-replacement, no background notifier, and no hidden subcommand.

    The new proposal keeps the download-and-rename code out of the CLI. The script branch still runs install.sh. install_from_dir in scripts/install.sh copies omnigraph-server into the same directory. That is the same on-disk swap, done by the installer the CLI would start.

    Issue #11 proposed that self-updater. It is closed as completed (2026-04-12) and has no comments. The implementation never landed. Closing #11 does not mean the feature is on main.

    Confirmed. The receipt, the path rules, and the Docker variable are absent.

    No file under this SHA mentions .omnigraph-install.json, OMNIGRAPH_INSTALL_METHOD, or /Cellar/omnigraph/. The only omnigraph-install strings in the three scripts are temporary directory names. The Dockerfile sets ENV OMNIGRAPH_BIND=0.0.0.0:8080 and no install method. docker/entrypoint.sh starts the server. It does not export an install method.

    Partly confirmed. Edge builds publish daily.

    docs/user/install.md says edge builds are prereleases built once a day from the tip of main, after CI passes, when main has moved. Recent tags match that pattern (edge-20261103-03e0b62, edge-20261103-36d3ea0, edge-20261101-3e0e467, edge-20261031-269299b). A quiet main does not get a build. A notice on every new edge tag would appear on almost every such day.

    Root cause

    The CLI has one version report and one storage-upgrade command. They answer different questions.

    VersionOutput::current in crates/omnigraph-cli/src/commands/local/output.rs copies the compiled version, OMNIGRAPH_RELEASE_CHANNEL, OMNIGRAPH_SOURCE_VERSION, HTTP_API_CONTRACT ("0.14"), and the storage stamps. write_version prints those facts to stdout. main does not spawn a checker. The command enum in crates/omnigraph-cli/src/cli.rs has Version and Upgrade. Upgrade calls omnigraph::db::upgrade_storage_as_with_storage. It converts a standalone graph at stamp 8, 9, or 13, or it stamps 14 or 15 up to 16. It does not replace a binary.

    The stamps are INTERNAL_MANIFEST_SCHEMA_VERSION = 16 and MIN_SUPPORTED_INTERNAL_SCHEMA_VERSION = 14 in crates/omnigraph-catalog/src/migrations.rs. docs/dev/versioning.md says the HTTP contract must match exactly, and the CLI, the server, and the integrations move together. There is no old-client fallback.

    The release workflows already know these facts. scripts/edge_release.py reads omnigraph version --json and writes the storage format and the HTTP contract into the edge release body, next to the latest stable release. The stable v0.14.0 notes say in prose that the HTTP contract changed. The v0.14.0 assets are the four archives and their .sha256 files. There is no JSON manifest. The GitHub "latest" API returns a tag and a markdown body. It has no http_contract field and no storage-format field. Parsing that prose is a brittle contract.

    Two install trees also explain the receipt. install.sh and install-source.sh both write ~/.local/bin. After that copy, the path does not say which script ran, which channel was selected, or which tag was pinned. Homebrew and Nix can be inferred from a resolved path. Docker cannot. /usr/local/bin/omnigraph is also a normal copy.

    The name update collides with upgrade. The CLI already points a typo for update at the storage conversion.

    Recommended fix

    The user need is two questions. "Is there a release newer than this binary?" "What command updates the method that installed it, and must the server move too?"

    The smallest design that answers both, and that does not start an installer:

    1. Add an "Update" section to docs/user/install.md. Give one command per channel. Say that install.sh and install.ps1 also replace omnigraph-server and omnigraph-azure-admission. Say that a running server keeps the old file until the operator restarts it. Point server installs at the orchestrator, the image tag, or the package manager.

    2. Keep omnigraph version offline, as the install guide promises. Add omnigraph version --check. The user asks for it. It performs one HTTPS GET of a small release file, then it prints the method's command. It writes no file. It starts no installer. It does not run on query, mutate, cluster apply, or server boot. Exit 0 when the check completes, whether or not a newer release exists. A network failure is a message on stderr and a non-zero exit for that flag only.

    3. Publish omnigraph-release.json as a release asset, generated from omnigraph version --json in the release workflow. The edge workflow already runs that command. The file carries the tag, the package version, the channel, http_contract, and internal_schema.writes / serves. The CLI compares those fields with its own compiled facts. It prints the contract line only when http_contract or writes differs. The GitHub Releases API remains the index of tags. It is the wrong source for the contract line. Do not parse release notes.

    4. For the shell installers only, write .omnigraph-install.json beside the binaries: method (script or source), channel (stable, edge, or a pinned tag), and install directory. Trust it only when the resolved current_exe() is inside that directory and the recorded SHA-256 matches the running binary. Record the hash of each installed binary, not only omnigraph. A CLI and a server from different releases are the failure this notice exists to prevent. The fields are data. The file has no URL. The CLI must not pass the fields through a shell. A hash mismatch means "unknown". The receipt is not a signature. A person who can write the install directory can replace the binary and the receipt. That person can already replace the binary.

    5. Detection when there is no trusted receipt. Use the resolved current_exe() path. On Linux, current_exe() already follows symlinks. Test path components, not a raw substring of an untrusted string, and confirm the macOS result before relying on /Cellar/omnigraph/. The Homebrew formula's prefix is the Cellar prefix. Also accept the Linuxbrew prefix. Nix: the resolved path is under /nix/store/. This was not executed in a store. Docker: do not use an environment variable as proof. The image can document the pull command. An unknown path prints that generic command. An env var is easy to set and easy to forge, and the Dockerfile does not set one today.

    6. Edge. Do not notify on each edge build. Notify when the stable manifest is newer than this package version, or when its HTTP contract or storage stamp differs. A pinned edge-<date>-<commit> install stays on that pin until the user asks.

    7. Do not add omnigraph update as a command that runs install.sh, install.ps1, or brew upgrade. That command replaces the server binary beside the CLI. It is the behavior PR feat(cli): add self-update mechanism (omnigraph update) [MR-612] #79 closed to avoid. A print-only hint under version --check does not need a second verb. upgrade must stay the storage command.

    8. omnigraph-server, the container entrypoint, and cluster operations do not check for releases. A check inside the image would run from production hosts. Air-gapped and CI machines stay quiet because they never get a background check. An explicit --check is the only network read, so a missing network cannot change query or cluster apply.

    9. Windows is a real channel (install.ps1, omnigraph-windows-x86_64.zip). Any receipt and any printed command need a Windows test. This assessment did not run one.

    10. A --check JSON object would be a new CLI contract. docs/rfcs/README.md asks for an RFC before a new CLI contract. Human text can land with the docs change. Stable JSON should wait for the RFC. Tests belong in the existing version owner, cli_schema_config.rs. Path tests can use a temporary symlink, as in the experiment above. They must not call the live GitHub API.

    Alternatives and liability

    A docs section alone removes the "which command?" gap. It adds no runtime behavior. It leaves "does a new release exist?" to the user.

    A static line on omnigraph version can repeat the install guide: this command does not look for a newer release, and the update commands are in the install guide. The line is local and permanent. It still does not know that a release exists.

    Package-manager text alone misses the curl installer. That installer is the first method in the README.

    A background daily notice adds a network client, a cache, TTY and CI rules, and a second record of "what the latest release is". Review comments on PR #79 already had to chase cache identity and retry timing. The cache can be wrong, and every successful command pays for the process. The privacy cost is a GET from the user's network. The GET must not send a graph path, a token, or an actor. A static release file does not need the current version in the query. The command must still honor an HTTPS proxy and must give up on a short timeout. That behavior is justified only when the user asked for the check.

    The full proposal (receipt, background notice, and omnigraph update that runs the installer) removes the memory burden. It adds a receipt format and a command whose script path changes the server file. Five more changes of that kind become a second installer inside the CLI. The release asset and omnigraph version --json are already the source of the contract and the format. The check should read that source when asked, and it should print. It should not grow a writer.

    The trade is deliberate. The automatic notice is the part we drop. A database command must not contact the network, and it must not change a server file, unless the user asked for that operation. Convenience of a hint on every run is the lower priority.

    Lance

    Lance does not affect this issue. Omnigraph pins unmodified Lance 13.0.0 (Cargo.toml, Cargo.lock). That tag is https://github.com/lance-format/lance/releases/tag/v13.0.0 (2026-10-07). Newer upstream tags include stable https://github.com/lance-format/lance/releases/tag/v14.0.2 (2026-10-29) and prerelease https://github.com/lance-format/lance/releases/tag/v15.0.0-beta.9 (2026-11-03). A search of open lance-format/lance issues for a self-update, an update notifier, or an install receipt found nothing. The storage format in this proposal is Omnigraph's manifest stamp (16, serves 14 through 16), which omnigraph version already prints. docs/dev/versioning.md keeps storage upgrades and the Lance analyzer boundary separate. A Lance bump would not tell a user how they installed Omnigraph. No Lance page is the matching domain for an install-method notice.

    Related work

    Supersession

    No open issue supersedes #1245. Issue #11 and PR #79 are the rejected self-replacing updater. They are closed. They are context. They are not a broader open tracker. This issue should stay open.

    Limitations

    macOS current_exe() was not measured. Homebrew, Nix, Docker, and Windows were not executed. The install scripts were read in full and were not run. The debug binary was not rebuilt at this SHA. Its version output matches this tree. This comment does not change the repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageAwaiting maintainer triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions