* Codex auto mode: grant the orx data dir as a sandbox writable root workspace-write denies writes outside the workspace, and the orx SQLite store lives under ~/.local/share (WAL + read-write open), so every store-touching orx command died with 'unable to open database file' under Auto mode. Add store::data_dir() to the turn's writable_roots alongside the shared git dir. * Review fixes: pin ORX_DATA_DIR to the granted root, ensure_ naming - Pin ORX_DATA_DIR on the codex child (after prepare_env) so the store the sandboxed orx resolves is the exact path granted as a writable root — synced env or a relative override could otherwise diverge. - Rename orx_data_dir -> ensure_orx_data_dir (codebase ensure_ prefix for create-if-missing helpers); note the grant is dir-wide. - Dedent the '-c replaces user roots' note: it covers both roots. * Review round 2: match-expression pin, stale options() doc, asymmetry note * Codex chat: migrate to app-server protocol with exec fallback One long-lived 'codex app-server' child per session (CodexHost, the AgentHost pattern), JSON-RPC over stdio. Threads persist as the session's native id and resume across orx up restarts; the playbook rides developerInstructions; the sandbox policy travels per turn as a first-class sandboxPolicy param (writable roots + network) instead of -c config overrides that clobbered user config. approvalPolicy stays 'never' on every mode — this is a pure transport swap; the follow-up flips Auto to on-request and surfaces approval requests as permission cards (validated live against codex-cli 0.144: sandbox escalations DO arrive as item/commandExecution/requestApproval, upstream #21982 does not reproduce). codex < 0.144 keeps the legacy exec path for one release (ORX_CODEX_EXEC=1 forces it); detect() notes the upgrade. * Review round 1 fixes: turn-id filter, drop race, bounded interrupt Majors: - Filter turn events by the turn/start response's turn id so an aborted predecessor's tail (fatally, its turn/completed) can't leak into a successor turn's transcript. - TurnRoute::drop only detaches its own channel (same_channel guard) — an aborted task's late drop can no longer clobber a successor's registration. - interrupt_active_turn bounded to 5s so a wedged child can't hold the user-facing interrupt/delete paths hostage; reader EOF now also kills a half-dead child instead of leaving a zombie in the registry. - thread/resume falls back to a fresh thread only on a server-reported error (try_request); transport failures propagate so a resumable thread is never discarded over a timeout. - delete_project now kills codex children too (was opencode-only). Nits: options()/sandbox_policy_json doc rewrites, tool_part/tool_status/ part_exists/find_codex_required/respond_decline helpers, HashSet for unanswered ids, REQUEST_TIMEOUT const, outputDelta creates its part, turn/completed(inProgress) guard, double-error dedup, truthy ORX_CODEX_EXEC, softer agent_note, import cleanups. * Review round 2 fixes: abort-proof child bring-up, shared-helpers section Major: spawn + register + handshake now run in a detached task, so an aborted turn task (interrupt/delete during the first message) can no longer orphan an unregistered app-server child; a no-displace insert keeps a raced successor's live client intact, and handshake failure kills + deregisters only its own entry. Nits: shared helpers moved out of the legacy section under their own banner; set_active_turn armed from the turn/start response (deletes the turn/started special case and closes the early-interrupt no-op window); parse_codex_version takes the first parseable token; resume-rejection logged; HANDSHAKE_TIMEOUT const; TurnEnd/apply_notification docs; has_error_part test; fold-test guard tightened; comment accuracy. * Round 3 polish: doc word order, eprintln prefix, mid-handshake reuse note * fmt * Codex Auto: on-request approvals surfaced as permission cards Auto now runs approvalPolicy on-request: the writable roots keep routine orx traffic prompt-free, and anything escalating past the sandbox arrives as item/*/requestApproval, surfaced as a permission card (native_id = JSON-RPC request id) and answered inline via resume_from_prompt -> {"decision": accept|decline}. Invariant: no unresolved codex card outlives its turn — every turn exit sweeps open cards (resolve + decline with codex); interrupt paths cancel pending approvals natively before turn/interrupt; serverRequest/resolved reconciles codex-side settlement (its ~5min approval deadline). Stale-turn requests are declined, never surfaced; Bypass auto-accepts; unknown request types get a JSON-RPC error instead of blocking codex. A 10-minute no-event watchdog (suspended while a card is pending) interrupts wedged turns. * PR2 review round 1 fixes: stale-card defense, Bypass reply schema, card content - Consent integrity: codex request ids restart per child, so a stale unresolved card could deliver a click to a different live request. Turn-entry resolve_stale_prompts sweep (store-side, codex-only — end-turn harnesses keep their cross-turn cards) + turn-scoped part ids (appr-{turn}-{id}) close it from both ends. - Bypass now auto-accepts only card-able approval methods; unknown request types get the JSON-RPC error reply in every mode (a {decision} reply to item/tool/requestUserInput would fail to parse). - Permission cards show the request's human-readable reason (fileChange cards carry nothing else); UI dist rebuilt. - serverRequest/resolved also clears the id from the client's pending set (stale-answer guard stays truthful after codex self-settles). - Reconciliation computes the part id directly (approval_part_id) instead of a reverse scan; resolve_card returns (); sweep declines unconditionally (respond's pending set is the single arbiter); watchdog bumped to 30min with an honest false-positive note; resume_from_prompt gains a kind guard. * PR2 round 2 polish: schema-aware fallback replies, immediate card flush Non-approval request methods now get the JSON-RPC error reply in every fallback path (read_loop unrouted, stale-turn arm) via a shared is_approval_request; codex-settled cards flush immediately instead of waiting for the next event; message_json reuse; Handled doc drift; watchdog comment dedup. * Bump version to 0.1.52
OpenResearch CLI (orx)
A small command-line interface for OpenResearch. Log in via the browser, then query your projects from the terminal.
Requirements
-
Rust (stable) with Cargo. Install via rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Build & run
# Build (debug):
cargo build
# Run during development:
cargo run -- login
cargo run -- projects
# Build an optimized release binary at target/release/orx:
cargo build --release
./target/release/orx login
# Or install `orx` onto your PATH (~/.cargo/bin):
cargo install --path .
orx login
orx projects
Run the tests with cargo test.
Commands
| Command | Description |
|---|---|
orx login [--api-url <url>] |
Opens your browser, authenticates, and stores a personal access token. |
orx logout |
Removes the stored token. |
orx install-skills [--agent claude|codex|opencode|cursor|all] |
Installs the orx skill shim into local coding agents (Claude Code, Codex, OpenCode, Cursor) so they auto-discover the CLI (orx login also offers this). |
orx projects [--all] |
Lists your projects, grouped by organization. --all includes archived. |
orx compute [--gpu <id>] [--count <n>] [--provider <name>] | --cpu] |
Lists the GPU compute catalog across all providers and regions, or CPU-only offers with --cpu, sorted by price. |
orx instance create <orgId> (--gpu <id> [--count <n>] [--disk <gb>] [--provider <name>] | --cpu <flavor> [--vcpus <n>]) |
Spins up a standalone instance in an organization (not tied to an experiment), like the dashboard's "Spin up". |
orx exp status <expId> |
Shows an experiment's status, branch, parent, run command, and latest run, plus a local git diff recipe for what the run changed. |
orx exp cmd <expId> [--set <command>] |
Views or sets the experiment's run command. |
orx exp run <expId> (--gpu <id> [--count <n>] [--disk <gb>] [--provider <name>] | --cpu <flavor> [--vcpus <n>] | --sandbox <id>) |
Launches a run on new GPU, new CPU-only, or existing compute. |
orx exp cancel <expId> |
Cancels the in-flight run. |
orx lit "<query>" [--limit <n>] [--json] |
Full-text search alphaXiv's paper corpus (no login required). |
orx paper <id|url> [--full] |
Fetch a paper's machine-readable report, or its full text with --full. |
orx version [--check] [--json] |
Prints the CLI version; --check compares it to the latest release. |
orx update [--dry-run] [--force] |
Updates orx by re-running the release installer (installer installs only). |
Updating
orx update only manages binaries installed by the release installer script
(it checks the cargo-dist install receipt at
${XDG_CONFIG_HOME:-~/.config}/openresearch-cli/). If you installed with
cargo install, update the same way instead. In interactive terminals orx
also checks for new releases in the background at most once a day and prints
a one-line notice to stderr; set ORX_NO_UPDATE_CHECK=1 (or
OPENRESEARCH_CLI_DISABLE_UPDATE=1) to turn that off. Agents, scripts, pipes,
and CI never see the notice.
Configuration
-
API URL resolves from
--api-url→OPENRESEARCH_API_URL→ the built-in default (http://localhost:4000). Point it at the production API host for real use:export OPENRESEARCH_API_URL=https://api.openresearch.sh -
Credentials are stored at
${XDG_CONFIG_HOME:-~/.config}/openresearch/credentials.json(mode0600).
How login works
orx login starts a temporary HTTP listener on a random 127.0.0.1 port, opens
{api}/auth/cli/login in your browser, and waits for the API to redirect back
with a freshly minted personal access token after you authenticate. The token is
sent as Authorization: Bearer … on every subsequent request.