Skip to content

feat(sandbox): add Orca per-workspace environment - #78

Merged
pratikbin merged 3 commits into
mainfrom
feat/orca-per-workspace-env
Aug 25, 2026
Merged

feat(sandbox): add Orca per-workspace environment#78
pratikbin merged 3 commits into
mainfrom
feat/orca-per-workspace-env

Conversation

@pratikbin

Copy link
Copy Markdown
Contributor

Adds createos setup orca, which lets Orca run a workspace on a disposable CreateOS Sandbox instead of the user's laptop.

The companion Orca plugin lives in NodeOps-app/createos-plugins under packages/orca-plugin. It contains no code — it registers one VM recipe that shells out to this command.

How it works

Orca drives a per-workspace environment by running one command for every lifecycle phase and selecting the phase with ORCA_VM_MODE. The command prints one JSON object on stdout; everything else goes to stderr.

createos setup orca has two halves:

  • human--doctor checks login, git, and ssh, then prints install instructions.
  • machine--recipe (hidden) runs one lifecycle phase.

On create it provisions a microVM, wires SSH through the gateway, waits for sshd to actually accept connections, packs the working tree, uploads it, and checks it out on a branch named after the workspace.

The checkout is pushed, not cloned, so no git token ever reaches the sandbox and private repositories work with no extra setup. Uncommitted edits travel with it, which is the point of the path.

Suspend and resume are not declared

SSH does not reliably come back after a sandbox resume. Orca requires the pair or neither, so this recipe declares neither and every workspace is destroy-and-recreate.

Coding agents (--agents / CREATEOS_AGENTS)

Opt-in, nothing by default: claude, codex, cursor, opencode, pi.

Orca passes no agent identity to a recipe (ORCA_VM_MODE, ORCA_REPO_*, ORCA_WORKSPACE_NAME, and nothing naming the agent), so this cannot be inferred and has to be asked for.

Two details worth review attention:

  • Installs run over SSH, not ExecSandbox. Those two do not share a mount namespace outside /workspace. Verified directly: a file written via the exec API to /tmp or /usr/local/bin cannot be read back over SSH, while /workspace/repo can. Installing via the exec API produced agents that reported success and were then invisible to the session Orca launches them in.
  • Outcome is reported by a trailing marker, not an exit code. set -e propagates a vendor installer's own exit status, so a status reused as "already present" would silently turn a failed install into a skip.

Each agent links into /usr/local/bin and is skipped when already present. Note cursor installs a binary named cursor-agent.

These run the vendors' own install scripts, unpinned and not checksum-verified. Documented in the plugin README, with a custom CREATEOS_ROOTFS image as the way to control it.

Also: ControlPath fix in editor.go

This one affects sandbox editor today, independently of Orca.

Every tunnel-mode sandbox resolves to the same HostName 127.0.0.1 + User root, so a ControlPath built from %h/%r/%p — including the common personal Host * default — collides across sandboxes. With ControlMaster auto, ssh silently reuses a stale multiplexed connection to a different, often already-destroyed, sandbox instead of opening one to the box actually requested.

That was the cause of assorted "sshd didn't answer" and hang symptoms, reproduced against the stock sandbox editor command with no Orca involved. %n (the alias) is the one token guaranteed unique per sandbox. Same family as #76.

probeSSH now takes the wait as a parameter so the recipe can allow longer than the editor's default for a cold box.

Testing

Verified end-to-end against a real sandbox, through Orca's UI and standalone:

  • create → SSH wire → seed → agent install → destroy, with no leaked sandboxes (createos sandbox list checked after each run)
  • all five agents resolve over SSH after install, which is the check that caught the namespace bug
  • a bad --agents value fails before any sandbox is created
  • SIGTERM mid-provision still tears the sandbox down

10 unit tests cover the argument-injection regexes, sandbox-name sanitizing, shell quoting, agent parsing/dedup/rejection, the agent table's name→binary mapping, marker parsing, and that the generated script links into /usr/local/bin.

go build, go vet, go test ./..., and gosec all pass; gosec reports 0 issues in orca.go.

Notes for the reviewer

  • Every env-sourced value that reaches exec.Command or a git refspec is pinned by regex (orcaSHARE, orcaBranchRE, orcaRootRE) — argv stops shell injection but not argument injection, and git reads a leading - as an option.
  • Agent names are validated map keys and the install commands are package constants, so nothing in the generated script is interpolated from caller input.
  • A failed create tears the sandbox down; if even that cleanup fails it logs the id and the exact removal command, because Orca's UI only surfaces "Recipe exited with code 1".
  • Known limit: uploads around 400 MB and above can return a 503 partway through. Tracked separately; not introduced here.

Add `createos setup orca`, which lets Orca run a workspace on a
disposable sandbox instead of the user's laptop. The command has two
halves: a human one that checks prerequisites and prints install
instructions, and a hidden `--recipe` one that Orca calls for each
lifecycle phase, selected by ORCA_VM_MODE.

Create provisions a microVM, wires SSH through the gateway, waits for
sshd, then packs and uploads the working tree and checks it out on a
branch named after the workspace. The checkout is pushed rather than
cloned, so no git token reaches the sandbox and private repositories
need no extra setup; uncommitted edits travel with it.

Suspend and resume are not declared. SSH does not reliably come back
after a resume, and Orca requires the pair or neither.

Coding agents are opt-in via --agents/CREATEOS_AGENTS. Orca passes no
agent identity to a recipe, so this cannot be inferred. Installs run
over SSH rather than the exec API: the two do not share a mount
namespace outside /workspace, so anything the exec API installs is
invisible to the session Orca launches the agent in. Each agent links
into /usr/local/bin and is skipped when already present.

Also pin ControlPath per sandbox in renderSSHBlock. Every tunnel-mode
sandbox resolves to the same HostName/User, so a ControlPath built from
%h/%r/%p -- including a common personal `Host *` default -- collides
across sandboxes, and ssh reuses a stale multiplexed connection to a
different, often destroyed, box. This affects `sandbox editor` today,
independently of Orca. %n is the one token unique per sandbox. Same
family as #76.

probeSSH takes the wait as a parameter so the recipe can allow longer
than the editor's default for a cold box.
errcheck runs with check-blank, so blank-assigned errors need an
explicit nolint and a reason. Reuse the outer err instead of shadowing
it, and drop the client argument orcaInstallAgents stopped using when
agent installs moved from the exec API to SSH.

No behaviour change.
`createos setup orca` becomes `createos sandbox setup orca`. The command
only ever configures sandboxes, so it belongs in the sandbox tree rather
than as a second top-level group beside it.

root.go is no longer touched by this feature: the group registers itself
through NewSandboxCommand like every other sandbox subcommand, and
NewSetupCommand becomes unexported.

Document the shape in CLAUDE.md, since a harness integration is not like
the other commands: it has a human half and a hidden machine half, the
machine half owns stdout, and the command string is duplicated in a
plugin that lives in another repo.
@pratikbin
pratikbin merged commit 713e82b into main Aug 25, 2026
1 check passed
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