Skip to content

feat(cli): open Studio on a configured server target - #1137

Open
ragnorc wants to merge 4 commits into
ModernRelay:mainfrom
ragnorc:feat/cli-studio-open
Open

ragnorc wants to merge 4 commits into
ModernRelay:mainfrom
ragnorc:feat/cli-studio-open

Conversation

@ragnorc

@ragnorc ragnorc commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Implements "Opening Studio" from the accepted graph access RFC (docs/rfcs/2026-10-16-graph-access-serving-and-cross-process-writes.md, discussion #1017). Installing Studio stays a later phase and is not implemented.

What it does

omnigraph studio --target NAME [--graph G] [--json] validates a server target offline, builds one link, hands it to the platform, prints it and exits:

omnigraph-studio://open?target=NAME&binding=sha256:HEX&home=sha256:HEX[&graph=G]

It sends no request, binds or reads no credential (Studio obtains its own through the CLI), and starts nothing that serves a graph. --json prints {"protocol":1,"target":…,"binding":…,"home":…,"link":…}.

Target Result
Server, token/helper or anonymous auth Opens
Direct graph or root, inline path, managed target_kind_unsupported, before any I/O
Server on a Unix socket studio_transport_unsupported (defensive: a version-1 document cannot name one, which a test pins)

--graph is optional (the link carries it only when given), so studio has its own requirement, ServedTarget, rather than ServedGraph, which requires a graph. --as and --profile are refused.

Platforms

The opener sits behind a command-runner seam; in-source tests inject a recording runner and the integration test puts stand-in openers on PATH, so no test opens anything.

Platform Behavior
macOS open -b com.modernrelay.omnigraph-studio LINK
Linux and other Unix The Exec of the desktop entry xdg-mime query default x-scheme-handler/omnigraph-studio names, found in $XDG_DATA_HOME/applications then each $XDG_DATA_DIRS/applications (each - also tried as a subdirectory), run directly with the link in place of %u; no shell, never xdg-open
Windows rundll32 url.dll,FileProtocolHandler LINK, only after reg query finds HKCR\omnigraph-studio with URL Protocol and shell\open\command

Without a handler the command prints the link and where to download Studio and refuses with studio_unavailable (the JSON refusal carries link and install); a generic opener is never asked, so no browser opens. On Linux an entry that is hidden, runs in a terminal, takes no URL through %u/%U, or uses an Exec form the CLI cannot run exactly as written also refuses with studio_unavailable. A launched program gets its own process group and a 2 s wait (a freedesktop handler is Studio itself and keeps running); registration queries keep 10 s. The Windows path is unit-tested through the seam on every platform but has not run on Windows.

Digests

Both live in crates/omnigraph-cli/src/target_identity.rs:

  • home = sha256: + SHA-256("omnigraph operator home v1\0" ‖ canonical operator file path), the path credential custody is keyed by (--operator-config, else config.yaml under OMNIGRAPH_HOME/~/.omnigraph; directory aliases resolved, file name kept).
  • binding = sha256: + SHA-256("omnigraph server target binding v1\0" ‖ JSON of the server target's kind, canonical endpoint, auth provider with its source identity (store / env name / lexical file path / helper argv) and transport (CA mode and path, proxy mode/URL)). A token's value, a helper's timeout and default_graph never enter it; equivalent spellings digest alike.

target inspect NAME --json now reports home and, for a server target, binding, so Studio can resolve the target through the CLI and compare. target list --json is unchanged: its rows are the configuration entries verbatim, the shape target add reads back.

Protocols

version --json gains protocols.studio: 1, which versions the link, studio --json and the inspected digests. TARGET_PROTOCOL stays 2: target inspect only gains fields, and the documented rule bumps a protocol for incompatible changes only.

Tests

  • In-source: target_identity (digest format, equivalent spellings, token rotation, distinct endpoints/kinds/sources/transports, canonical home), commands::studio (link format, JSON and human output, studio_unavailable, refusals before the platform is asked, unconfigurable Unix socket), commands::studio::desktop (base directories, desktop file ID lookup with subdirectories, Exec quoting and field codes, refused forms, bounded UTF-8 reads) and commands::studio::open (each platform's exact argv; the Linux handler from its desktop entry and never xdg-open or a browser; a real launch of a handler script through a resolved entry; no rundll32 without a registered protocol).
  • planes actor matrix gains studio; the operation inventory lists studio as CLI-only; protocol assertions include studio: 1.
  • integration/cli_data.rs: studio_opens_a_server_target_whose_digests_target_inspect_reports (stand-in xdg-mime, desktop entry, handler, xdg-open and www-browser on Linux; a registration without an entry refuses and runs neither of the last two), and target_list_and_inspect_have_the_same_versioned_binding now asserts the digests.

Docs

New docs/user/cli/studio.md; rows in the CLI reference, user index, install (protocols), troubleshooting (exits), targets (inspect digests), testing map, skill command index and condensed reference; changelog.d/cli-studio-opener.added.md.

`omnigraph studio --target NAME [--graph G] [--json]` validates a server
target offline, builds one
`omnigraph-studio://open?target=NAME&binding=<digest>&home=<digest>[&graph=G]`
link, hands it to the platform, prints it and exits. It sends no request,
binds or reads no credential, and starts nothing that serves a graph.

The platform opens the link only through a handler it names for Studio:
`open -b com.modernrelay.omnigraph-studio` on macOS; `xdg-open` on other
Unix desktops only after `xdg-mime query default
x-scheme-handler/omnigraph-studio` names a handler; on Windows `rundll32
url.dll,FileProtocolHandler` only after the registry holds an
`omnigraph-studio` URL protocol with an open command. Without one it prints
the link, says where to download Studio and refuses with
`studio_unavailable`, never reaching a generic opener that would hand the
link to a browser. The opener sits behind a command-runner seam, so no test
opens anything.

`home` digests the canonical location of the operator file the invocation
read, the path credential custody is keyed by. `binding` digests a server
target's endpoint, auth provider and its source, and transport; a token's
value and `default_graph` never enter it, and equivalent spellings digest
alike. Both are defined once in `target_identity` and reported by
`target inspect --json`, so Studio can resolve the target through the CLI
and compare. Direct, managed and inline targets refuse with
`target_kind_unsupported` before any I/O; a Unix-socket server refuses with
`studio_transport_unsupported`, though a version-1 document cannot name one.

`version --json` reports `protocols.studio: 1`, which versions the link,
`studio --json` and the inspected digests; the target protocol stays 2,
since inspection only gains fields. `studio` is a CLI-only operation with
the command index, condensed reference, user guide and release note.
@ragnorc
ragnorc requested a review from aaltshuler as a code owner October 22, 2026 15:25
@cursor

cursor Bot commented Oct 22, 2026

Copy link
Copy Markdown

Recommendation: do not merge until the Linux opener launches the registered handler. A browser on PATH must not be able to make omnigraph studio succeed.

Why

A person or an agent needs to open OmniGraph Studio on a server target that is already configured. The command must send no HTTP request, must pass no credential, and must keep the link away from a web browser.

What

omnigraph studio --target NAME [--graph G] [--json] checks the target, builds one link, and asks the platform to open it.

The link is omnigraph-studio://open?target=NAME&binding=sha256:HEX&home=sha256:HEX[&graph=G]. protocols.studio is 1. Success JSON is {"protocol":1,"target","binding","home","link"}. A refusal prints the link, names studio_unavailable or target_kind_unsupported, and exits 2.

How

target_identity is the one place that computes binding and home. target inspect --json reports the same digests. The binding covers the server kind, the canonical endpoint, the auth provider and source identity, and the transport. It leaves out token values, the helper timeout, and default_graph.

A direct target, a managed graph, or an inline path returns target_kind_unsupported before any platform command. A server target does not call auth.token() and does not open storage. macOS uses open -b com.modernrelay.omnigraph-studio. Windows queries the URL-protocol registry and then uses rundll32. Linux queries xdg-mime and then runs xdg-open.

Impact

The link, the shared digest, and the refusal-before-I/O behavior hold. The Linux launch does not hold the browser promise. On a generic desktop with no display, xdg-open can pass the link to a browser and still exit 0. The CLI then reports that Studio opened.

Blocking

  1. The Linux launch uses xdg-open, and that program does not keep the contract "it never opens a browser".

crates/omnigraph-cli/src/commands/studio/open.rs launches xdg-open when xdg-mime query default prints a handler name. The mime check is the gate the code and the docs describe. It does not control the next step inside xdg-open.

xdg-open 1.1.3 (xdg-utils 1.1.3-4.1ubuntu3) reaches open_generic when XDG_CURRENT_DESKTOP is unset. open_generic calls the scheme handler only when has_display is true (DISPLAY or WAYLAND_DISPLAY is set). With neither variable set, it skips open_generic_xdg_x_scheme_handler and tries www-browser, links2, elinks, links, lynx, and w3m.

Reproduction. A desktop file omnigraph-studio.desktop had an absolute Exec handler. DISPLAY, WAYLAND_DISPLAY, XDG_CURRENT_DESKTOP, and BROWSER were unset. xdg-mime query default x-scheme-handler/omnigraph-studio printed omnigraph-studio.desktop.

  • A www-browser stub that exits 0 was first on PATH. xdg-open exited 0. Its log line was BROWSER omnigraph-studio://open?target=staging&binding=sha256:ab&home=sha256:cd. The handler did not run. This command would report success.
  • The same registration on PATH=/usr/bin:/bin made xdg-open exit 3. stderr ended with xdg-open: no method available for opening the studio link. The handler did not run. This command would refuse with studio_unavailable while the registered handler was executable.
  • With DISPLAY=:99 and the same registration, xdg-open exited 0 and the handler log recorded the link.

freedesktop_opens_only_through_a_named_scheme_handler records a call to xdg-open and treats exit 0 as Opening::Opened. FakeOpeners implements xdg-open as exit 0. Those tests pass. They do not run /usr/bin/xdg-open.

Change the launch. When xdg-mime names a desktop file, read that file's Exec and run it with the link in place of %u. xdg-open's search_desktop_file already does that replacement. Leave xdg-open out of this path. A browser on PATH then cannot satisfy the command, and a missing display cannot hide a registered handler.

Point the unit test and the integration stand-in at that handler binary. Update docs/user/cli/studio.md and the opener bullet in docs/rfcs/2026-10-16-graph-access-serving-and-cross-process-writes.md. Both tell the next reader to call xdg-open after the mime query. The RFC sentence "It never opens a browser" is the contract. The named mechanism misses that contract on a headless generic desktop. An agent or an SSH session often has no display and no XDG_CURRENT_DESKTOP, so that path is a normal one for this command.

Optional

None.

Tradeoffs and liability

The feature adds one command, one digest module, and protocol number 1. target inspect and studio share the digest. Studio can check a link with target inspect and does not need a second copy of the preimage. Direct targets fail before platform I/O. A server target does not read a token and does not open a dataset. The command stays off the publication path and off the credential path.

Exit 2 with {error, code} and no disposition matches the current mcp and target path. docs/user/operations/troubleshooting.md lists studio in that carve-out. The RFC's single disposition and exit 1 belong to a later phase. Keeping studio on the current unclassified path avoids a private exit model for this one command. Five similar client handoffs can reuse target_identity. Each of them should use this opener, and this opener should launch a named handler.

The Linux branch adds liability. Success currently means "xdg-open exited 0". The contract requires that Studio's handler received the link. A later command that copies this branch copies the browser fallback. The fix belongs in this opener, because this opener owns the handoff. Another mime check in front of xdg-open would leave the same cause in place.

macOS code signing stays in the installer phase. That matches the RFC. Windows checks the registry before rundll32. The seam tests cover that argv. This change does not touch Lance, the engine, or the catalog. Lance 13.0.0 and a later Lance version do not change the link.

Sibling pull requests that edit shared surfaces:

Validation

Reviewed head: d533a9793565c597efb225805a0ad18a57312491. Isolated checkout: /tmp/pr-1137. No review and no review comment was present on that head at post time.

Local checks used toolchain 1.97.1, --locked, and CARGO_TARGET_DIR=/workspace/target.

  • cargo test -p omnigraph-cli --lib -- target_identity:: commands::studio:: planes::tests::actor_flag_matrix — 15 passed, 0 failed, 346 filtered out.
  • cargo test -p omnigraph-cli --test integration -- studio_opens_a_server_target_whose_digests_target_inspect_reports target_list_and_inspect_have_the_same_versioned_binding version_command_prints_cli_version with RUST_MIN_STACK=16777216 — 3 passed, 0 failed, 349 filtered out. The three tests were cli_data::studio_opens_a_server_target_whose_digests_target_inspect_reports, cli_data::target_list_and_inspect_have_the_same_versioned_binding, and cli_schema_config::version_command_prints_cli_version_http_contract_and_storage_formats_issue_977.

Those tests use a recorded runner and PATH stand-ins. They do not execute /usr/bin/xdg-open. The shell reproduction in the blocking finding is the evidence for that defect.

CI on this commit, run 39804779752, concluded success. Test Workspace, GQ Logic Tests (ordinary, dst, served), clippy, and rustfmt passed. Devin Review is marked pass and its body says the full review was skipped. That is not a substantive review. CI does not run the real xdg-open script.

GQT regression proof does not apply. This is a new CLI handoff, and the defect is in process launch. GQT expresses rows, counts, column types, and query errors. It does not express PATH, operator-home digests, or xdg-open. The owner is the omnigraph-cli in-source tests plus tests/integration/cli_data.rs. I ran that owner. I added no temporary GQT case.

Limitations

I did not run the Windows registry path on Windows. I did not run the macOS open -b path. The Unix integration test exercises the freedesktop stand-in. I did not re-run a GNOME, KDE, or other named-desktop xdg-open session in this pass. open_gnome calls gio open when gio is present, and that path can differ from open_generic. The failure reproduced here is detectDE=generic with no display. That is the path an agent or an SSH session hits when XDG_CURRENT_DESKTOP is unset. I make no performance claim.

success: true,
stdout,
}) if !stdout.trim().is_empty() => launched(
runner.launch("xdg-open", &[link]),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This launch treats a non-empty xdg-mime answer plus xdg-open exit 0 as "Studio opened". Stock xdg-open 1.1.3 does not do that on a generic desktop with no display.

open_generic runs the scheme handler only when DISPLAY or WAYLAND_DISPLAY is set (has_display in /usr/bin/xdg-open). With both unset, and with XDG_CURRENT_DESKTOP unset, it skips the registered handler and tries www-browser, links2, elinks, links, lynx, and w3m.

I registered omnigraph-studio.desktop with an absolute Exec and left the display unset. xdg-mime query default x-scheme-handler/omnigraph-studio printed omnigraph-studio.desktop.

  • A www-browser stub that exits 0 made xdg-open exit 0 and recorded BROWSER plus the studio link. The handler did not run. This arm would return Opening::Opened.
  • On PATH=/usr/bin:/bin, xdg-open exited 3 with no method available for opening the link. The handler did not run. This arm would return studio_unavailable while the handler was executable.
  • With DISPLAY=:99, the same registration ran the handler and xdg-open exited 0.

freedesktop_opens_only_through_a_named_scheme_handler and FakeOpeners both accept an xdg-open that exits 0. They never execute this script, so CI stays green.

Launch the desktop file's Exec with the link in place of %u, the replacement search_desktop_file already performs. Leave xdg-open out of this path. Point those two tests at the handler binary. A browser on PATH must not be able to satisfy the command. Update the platform row in docs/user/cli/studio.md and the opener bullet in the serving RFC so they describe that launch. The RFC promise is "It never opens a browser." The mime query plus xdg-open misses that promise for a headless generic desktop, which is a normal environment for this CLI.

On a freedesktop desktop the opener ran `xdg-open` once `xdg-mime query
default x-scheme-handler/omnigraph-studio` named a handler. Stock
`xdg-open` with no `XDG_CURRENT_DESKTOP` and no display skips the
registered handler and tries `www-browser`, `links2`, `elinks`, `links`,
`lynx` and `w3m`. A browser on `PATH` then made `studio` report success,
and without one a runnable handler read as `studio_unavailable`.

The opener now resolves the desktop file ID `xdg-mime` names in
`$XDG_DATA_HOME/applications` (default `~/.local/share/applications`),
then each `$XDG_DATA_DIRS/applications` (default
`/usr/local/share:/usr/share`), trying each `-` as a subdirectory. It
parses the `[Desktop Entry]` group's `Exec` with the string escapes and
the specification's quoting, puts the link in place of the one `%u` or
`%U`, drops the other field codes, and runs that program directly, with no
shell and no `xdg-open`. An entry that is hidden, runs in a terminal, is
not an application, takes no URL, or uses a form it cannot run as written
refuses with `studio_unavailable`.

A launched program now gets its own process group, and the opener waits
for it two seconds rather than ten: a freedesktop handler is Studio
itself, which keeps running once it has the link. Registration queries
keep their ten-second bound. Windows and macOS are unchanged.

The unit test points at the handler named in a stand-in desktop entry,
and the integration stand-ins add the handler, `xdg-open` and
`www-browser`; neither of the last two may run. A new in-source test
really launches a handler script through a resolved entry. Stand-in
scripts run once before they are measured, since a platform may hold a
new executable's first run for a scan longer than the opener waits. The
Studio guide, the testing map and the RFC's opener bullet describe the
launch, with a decision-log entry.
@ragnorc

ragnorc commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

Addressed at 0ef1bf3.

The Linux opener no longer runs xdg-open. After xdg-mime query default x-scheme-handler/omnigraph-studio names a desktop file ID, the CLI does three things:

  • It resolves the ID in $XDG_DATA_HOME/applications (default ~/.local/share/applications), then in each $XDG_DATA_DIRS/applications (default /usr/local/share:/usr/share). Each - is also tried as a subdirectory, as the spec's ID rule says.
  • It parses the [Desktop Entry] Exec value. The string escapes apply first, then the spec's whole-argument quoting. The one %u/%U becomes the link and the other field codes are dropped.
  • It spawns that program directly, with no shell.

An entry that is hidden, runs in a terminal, is not an application, has no single %u/%U, or uses a form it can't run exactly as written (an embedded field code, an unquoted reserved character, a relative program path) refuses with studio_unavailable. A browser on PATH therefore can't satisfy the command, and a missing display can't hide a registered handler. The new code is in commands/studio/desktop.rs.

Tests:

  • freedesktop_runs_the_registered_handler_and_no_generic_opener points at the handler in a stand-in desktop entry. Its recording runner rejects any unanswered command, so xdg-open or a browser would fail it.
  • A new in-source test really launches a handler script through a resolved entry.
  • The integration stand-ins now include the handler, xdg-open and www-browser. XDG_DATA_HOME/XDG_DATA_DIRS are isolated and DISPLAY/WAYLAND_DISPLAY/XDG_CURRENT_DESKTOP/BROWSER are unset. The test asserts that a registration with no entry refuses and runs neither xdg-open nor www-browser.

Two related launch changes:

  • The launched program gets its own process group, and the opener waits 2 s for it instead of 10. On Linux the handler is Studio itself, which keeps running. Registration queries keep their 10 s bound.
  • Stand-in scripts are run once before they're measured. macOS holds the first run of a new executable for a scan (17 s here), which caused the earlier one-off failure.

docs/user/cli/studio.md and the RFC's opener bullet now describe the launch, and the RFC has a decision-log entry. Windows is unchanged.

Checks: cargo fmt --check and clippy -D warnings (CLI, API types, server) are clean. CLI lib tests: 367 passed. The studio, target, schema-config and MCP-refusal integration tests: 38 passed. check-docs and typos are clean. I ran all of this on macOS, so the integration test's Linux branch runs only in CI. The in-source tests cover the freedesktop path on any Unix.

@cursor

cursor Bot commented Oct 22, 2026

Copy link
Copy Markdown

Recommendation: the Linux opener fix holds. Do not merge this head into current main until the branch is on the v0.14.1 release line. The required docs check fails on that merge.

Earlier finding

Resolved. The Linux opener no longer runs xdg-open. A non-empty xdg-mime answer plus exit 0 is no longer success. The CLI reads the desktop file that ID names and runs its Exec with the link. A browser on PATH cannot make the command succeed. The earlier review had no optional findings.

Why

A person or an agent needs Studio to open on a server target that is already configured. The command must send no request, must pass no credential, and must keep the link away from a web browser.

What

omnigraph studio --target NAME [--graph G] [--json] checks the target, builds one link, and asks the platform to open it.

The link is omnigraph-studio://open?target=NAME&binding=sha256:HEX&home=sha256:HEX[&graph=G]. Success JSON is unchanged. When no handler opens the link, the command prints the link, names studio_unavailable, and exits 2.

How

Linux has one opener path, freedesktop in crates/omnigraph-cli/src/commands/studio/open.rs. It runs xdg-mime query default x-scheme-handler/omnigraph-studio. It resolves that desktop file ID under XDG_DATA_HOME and then XDG_DATA_DIRS. A - in the ID is also tried as a subdirectory. crates/omnigraph-cli/src/commands/studio/desktop.rs parses the Exec key. The CLI spawns that program with the link in place of the one %u or %U. It uses no shell.

The path does not call xdg-open, gio, or a browser. A hidden entry, an entry that is not an application, a terminal entry, an entry without one URL field, and an Exec form the parser cannot run exactly all return studio_unavailable. macOS still uses open -b. Windows still checks the registry and then uses rundll32. Launch waits are now 2 seconds. Registration queries still stop at 10 seconds.

Impact

A headless generic desktop no longer reports that Studio opened when a browser received the link. A registered handler runs when no display is set. A named handler with no desktop file is refused, and the browser stays unused.

Blocking

  1. The merge with current main fails Check AGENTS.md Links. The opener code is sound. The failure is the release-note set.

changelog.d/cli-studio-opener.added.md is a new note. Main 60bfd19d has the v0.14.0 snapshot, and changelog.d/release.json is still v0.14.0. verify_snapshot rejects a note set that the snapshot does not record. CI run 39819582743 reports release notes changed after snapshot generation. I merged this head into main locally. The notes check failed with working notes or release configuration changed after snapshot generation. I merged this head into #1140 (4ec8a987, version v0.14.1). The same check passed with no errors, and skills/omnigraph/SKILL.md auto-merged. Rebase after that version bump. Leave the v0.14.0 snapshot as it is.

Optional

None.

Tradeoffs and liability

The fix removes one false success. Success no longer means that xdg-open exited 0. Success means the program named by the registered desktop entry was started with the link. The cause lived in this opener, because this opener owns the handoff. A further check in front of xdg-open would leave the same cause. The class is every launch that treated a generic opener's exit 0 as proof that Studio received the link. One Linux path closes that class. gio and a named desktop are not a second path.

The parser is new liability. It has to track the desktop entry Exec rules. It fails closed when it cannot run the line as written. Five similar handoffs should call this opener. They should not add a second parser. The extra module is the contract.

A process that is still running after 2 seconds counts as opened. A handler that exits with an error after those 2 seconds is reported as opened. Studio stays running, so the wait matches that program.

Sibling pull requests at this head:

This change does not touch Lance, the engine, or the catalog. Lance 13.0.0 and a later Lance version do not change the link.

Validation

Reviewed head: 0ef1bf3cf353152c12631929ab53cc8f5e827f31. Parent of the fix: d533a9793565c597efb225805a0ad18a57312491. Isolated checkout: /tmp/pr-1137. No review of this head was present at post time. The review on d533a979 is the earlier one.

Local checks used toolchain 1.97.1 and CARGO_TARGET_DIR=/workspace/target.

  • cargo test -p omnigraph-cli --lib -- target_identity:: commands::studio:: planes::tests::actor_flag_matrix at the head: 21 passed, 0 failed, 346 filtered out.
  • RUST_MIN_STACK=16777216 cargo test -p omnigraph-cli --test integration -- studio_opens_a_server_target_whose_digests_target_inspect_reports target_list_and_inspect_have_the_same_versioned_binding version_command_prints_cli_version at the head: 3 passed, 0 failed, 349 filtered out. The three tests were cli_data::studio_opens_a_server_target_whose_digests_target_inspect_reports, cli_data::target_list_and_inspect_have_the_same_versioned_binding, and cli_schema_config::version_command_prints_cli_version_http_contract_and_storage_formats_issue_977.

Before and after used the same Linux host and xdg-open 1.1.3. DISPLAY, WAYLAND_DISPLAY, XDG_CURRENT_DESKTOP, and BROWSER were unset. A www-browser stub that exits 0 was first on PATH. A logging xdg-open then exec'd /usr/bin/xdg-open. The before binary was built at d533a979. The after binary was built at 0ef1bf3c.

  • Flat desktop entry. Before: exit 0, the browser received the link, the handler did not run, and xdg-open ran. After: exit 0, the handler received --from-desktop and the link, the browser did not run, and xdg-open did not run.
  • The same after result holds for subdirectory ID omnigraph/studio.desktop, for an Exec path that contains a space, and for real /usr/bin/xdg-mime. That query printed omnigraph-studio.desktop with no display. The before binary sent the link to the browser in each of those cases.
  • No desktop file, with xdg-mime still naming the ID. Before: exit 0 and the browser received the link. After: exit 2, studio_unavailable, and neither the browser nor xdg-open ran.

GQT regression proof does not apply. The contract is process launch, PATH, and a desktop file. GQT expresses rows, counts, column types, and query errors. The owner is the omnigraph-cli in-source tests plus tests/integration/cli_data.rs. I ran that owner at the head. The shell cases above are the before and after proof. I added no GQT case and I pushed no code.

CI on this head, run 39819582743, has Test Workspace, clippy, rustfmt, and GQ Logic Tests green. Check AGENTS.md Links and CI Gate are red for the release-note reason above. Devin Review is marked pass. That mark is not a substantive review of this head. CI does not run /usr/bin/xdg-open. The shell cases above do.

Limitations

I did not run the Windows registry path. I did not run macOS open -b. I did not run a GNOME or KDE session. The Linux code has one path, and that path does not call those desktop tools. I make no performance claim.

# Conflicts:
#	docs/user/operations/troubleshooting.md
@cursor

cursor Bot commented Oct 24, 2026

Copy link
Copy Markdown

Recommendation: merge this change. The two earlier blockers are resolved. This head adds no new defect.

Earlier findings

Resolved. The Linux opener does not run xdg-open. A browser on PATH cannot make studio succeed. The opener sources are the same bytes as 0ef1bf3c.

Resolved. changelog.d/release.json records v0.14.1. The Studio note is part of that set. Check AGENTS.md Links passed on this head.

Why

A person or an agent needs Studio to open on a server target that is already configured. The command must send no request, must pass no credential, and must keep the link away from a web browser.

What

omnigraph studio --target NAME [--graph G] [--json] checks the target, builds one link, and asks the platform to open it.

The link is omnigraph-studio://open?target=NAME&binding=sha256:HEX&home=sha256:HEX[&graph=G]. Success JSON is {"protocol":1,"target","binding","home","link"}. When no handler opens the link, the command prints the link, names studio_unavailable, and exits 2.

How

Linux has one opener path. It runs xdg-mime query default x-scheme-handler/omnigraph-studio. It resolves that desktop file ID under XDG_DATA_HOME and then XDG_DATA_DIRS. A - in the ID is also tried as a subdirectory. The CLI reads the Exec key and starts that program with the link in place of the one %u or %U. It uses no shell.

The path does not call xdg-open, gio, or a browser. macOS still uses open -b. Windows still checks the registry and then uses rundll32.

ServedTarget is the requirement for studio. authentication returns no credential for that requirement, so auth.token() does not run. binding and home are computed once in target_identity. target inspect --json reports the same digests. A token value, a helper timeout, and default_graph stay out of the binding.

Impact

The two merges of main do not change the opener. The first merge, 9b18a505, was clean. The second merge conflicted only in docs/user/operations/troubleshooting.md. The resolution keeps main's credentials wording and recovery text, and it keeps studio in the exit list. The other doc and skill edits from main are present, and the Studio rows remain.

Blocking

None.

Optional

None.

Tradeoffs and liability

The opener owns the handoff. Success means the program named by the registered desktop entry was started with the link. A check in front of xdg-open would leave the old cause. One Linux path closes that class.

The desktop Exec parser is the new liability. It fails closed when it cannot run the line as written. Five similar handoffs should call this opener. They should not add a second parser.

A process that is still running after 2 seconds counts as opened. Studio stays running, so the wait matches that program. This command is an occasional operator action. The wait is not on a query path. Correctness outranks that small delay.

This change does not touch Lance, the engine, or the catalog. Lance stays 13.0.0. A later Lance version does not change the link. No Lance format page describes this handoff.

Sibling pull requests at this head:

Validation

Reviewed head: 946f783cdcda0f8b4e46a237f17e4d2dac77bd12. Parents: 9b18a50533f9c9c3eefdec98473d215d7bd7dee8 and main 7711e4a3def5c179cf95ec2525a6263819155916. The previous reviewed head is 0ef1bf3cf353152c12631929ab53cc8f5e827f31. Isolated checkout: /tmp/pr-1137. No review of this head was present at post time.

git diff --shortstat 0ef1bf3c 946f783 is 111 files changed. git diff 0ef1bf3c 946f783 -- crates/omnigraph-cli/src crates/omnigraph-cli/tests crates/omnigraph-api-types/src is empty. git diff origin/main 946f783 -- Cargo.lock is empty. Lance in Cargo.lock is 13.0.0.

Local checks used toolchain 1.97.1 and CARGO_TARGET_DIR=/workspace/target.

  • cargo test -p omnigraph-cli --lib -- target_identity:: commands::studio:: planes::tests::actor_flag_matrix skill::tests::skill_command_index_lists_every_public_command skill::tests::skill_condensed_reference_names_every_top_level_command: 23 passed, 0 failed, 344 filtered out.
  • The first integration run of studio_opens_a_server_target_whose_digests_target_inspect_reports, target_list_and_inspect_have_the_same_versioned_binding, and version_command_prints_cli_version panicked in the Studio stand-in warmup with Text file busy (ETXTBSY). The other two tests passed.
  • Isolated rerun cargo test -p omnigraph-cli --test integration -- cli_data::studio_opens_a_server_target_whose_digests_target_inspect_reports --exact --test-threads=1: 1 passed, 0 failed, 351 filtered out, in 0.30s.
  • bash scripts/check-agents-md.sh: OK, 64 links and 60 docs.

The integration test uses token source OMNIGRAPH_STAGING_TOKEN and removes that variable. Success is the proof that studio does not acquire the token. The same test asserts the handler argv and that xdg-open and www-browser do not run. It also asserts that a direct target and a managed target exit 2 with target_kind_unsupported, and that the endpoint receives no connection.

GQT cannot express this contract. The observable facts are process launch, PATH, a desktop file, and an absent credential variable. GQT expresses rows, counts, column types, and query errors. The owner is the CLI tests. The bug-fix before/after proof is the shell cases in the review of 0ef1bf3c. The opener sources are unchanged, so that proof still applies. I added no GQT case and I pushed no code.

CI on this head is run 40101068523. Test Workspace, Check AGENTS.md Links, rustfmt, clippy, and GQ Logic Tests succeeded. Dependency Guard failed on RUSTSEC-2026-0387 (crypto-bigint 0.5.5). CI Gate failed because that job failed. The nightly Dependency Guard on main 7711e4a3 (run 40060793527) reports the same advisory. This diff does not change Cargo.lock. Devin Review is a skipped trial, not a review of this head.

Limitations

I did not run the Windows registry path. I did not run macOS open -b. I did not run a GNOME or KDE session. The Linux code has one path, and that path does not call those desktop tools. I make no performance claim. I did not rerun the xdg-open 1.1.3 shell cases. The opener sources match 0ef1bf3c, where those cases already ran.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant