Repository navigation
feat: run Computer Use on another Mac over SSH - #7
Merged
Merged
Conversation
A new `ssh` option starts `codex app-server` on a Mac reached over SSH, so OpenCode can run on another machine (such as a Linux VM). Finding codex, Codex's working folder, OCR and the doctor's checks all happen on the machine that runs Computer Use; without the option everything runs locally as before. - src/host.ts: a Host is this machine or an SSH destination; commands run there with exact POSIX quoting. - OCR reads the screenshot from stdin instead of temporary files, so one path works locally and over SSH. - When the app-server exits, its last stderr lines (for example SSH errors) are part of the error. - Tests run the SSH path through a stand-in ssh that executes the remote command locally.
A computer_use call made while the Mac is unreachable now fails with "Could not reach <destination> over SSH: <reason>", the same wording the doctor showed, because sshHost's info() adds it. The remote tool description also shows how to copy a file to the Mac with scp.
Finding codex can take an SSH round trip. If the plugin was unloaded meanwhile, the startup went on and left an app-server (an ssh process and a remote codex) that nothing closed. dispose() now waits for a startup in flight, which gives up after discovery or closes the client it just started.
…processes runOnHost only settled on "close", which waits for every process holding the output pipes: descendants of the killed command, or an ssh ProxyCommand. On timeout or maxBuffer it now rejects at once, kills the process and closes its own ends of the pipes.
The platform check caches the Mac's details, so when SSH failed afterwards the doctor's codex lookup threw instead of returning a report. Looking for codex over SSH now fails with the same "Could not reach <destination> over SSH" wording as the first contact, and the doctor shows it as a failed codex check.
…xits The exit error was built as soon as the process exited, so stderr still in flight, such as a last line without a newline, was lost. Requests now fail as soon as the process exits, and pending ones are rejected once stderr has ended, or after at most 500 ms when a leftover process keeps it open.
Waiting for stderr to drain before calling onExit let a process from a failed startup, exiting late, clear the replacement that had taken over (both used the same generation number). onExit now runs at the exit and only the rejection of pending calls waits for the rest of stderr; every startup attempt gets its own number; and an unfinished last stderr line is kept even when stderr stays open past the drain.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A new
sshoption lets OpenCode run on one machine (for example a Linux VM or server) while Codex Computer Use runs on a Mac that it reaches over SSH:{ "package": "opencode-codex-computer-use", "options": { "ssh": "my-mac" } }Leave
sshunset and the plugin runs Codex on the same machine as OpenCode, as it does today.Why
With OpenCode 2's client/server split, many people run the OpenCode server on a VM or server and keep their desktop on a Mac. The plugin already only talks to
codex app-serverover stdio, so runningcodexover SSH is a small step. It still needs care in a few places, because some work happens on whichever machine runs the plugin:cwd. That folder doesn't exist on the Mac, and the firstcua_replcall then fails withfailed to get client: MCP startup failed: No such file or directory (os error 2).codex. The ChatGPT/Codex app paths were checked on the local disk.osascriptran locally.sw_vers, the Computer Use app andplutilwere checked locally.How
src/host.ts: the machine that runs Computer Use.localHost()is this machine;sshHost(destination)is another machine reached with the systemssh.ssh -T -o BatchMode=yes -o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4 -- <destination> <command>.sh -cscripts as positional arguments, never inside the script text.codex: thecodexPathoption, then the env var, then the app bundles if the host runs macOS, then the host'sPATH.codex app-server.$HOMEwhen the host is remote.osascripton stdin, so there are no temp files and local and remote use the same code.Could not reach my-mac over SSH: <ssh's reason>, from tool calls and from the doctor.sshis set, it tells the model that the apps are on another machine than its shell and file tools, with anscphint.scripts/smoke.tsaccepts--ssh <destination>.ssh(test/fixtures/fake-ssh/ssh) runs the remote command locally throughsh, so the quoting is exercised for real.Tested
bun run checkbun run checkon the committed treebun scripts/smoke.ts --ocrbun scripts/smoke.ts --ssh <mac>cua_replrunning, 17 apps listedrecognizeText(sshHost(...))on a synthetic PNGbun scripts/smoke.ts --ssh nonexistent-host.invalidCould not reach nonexistent-host.invalid over SSH: ssh: Could not resolve hostname …{"ssh": "<mac>"}, then/computer-use-doctorrun through the APINot exercised: screenshots, clicks and Chrome tabs. On the test Mac no app is approved for Computer Use yet, so the doctor's Finder probe was declined as expected, and no browser was connected.
Side observation (not changed here)
Codex
codex-cli 0.155.0-alpha.9.2(ChatGPT.app) was started with-c approval_policy="never"on the command line, andconfig/readreported"never". It did not approve app access, though: Finder was declined without an elicitation ("Computer Use was not approved to use Finder"). Settingapproval_policy = "never"in~/.codex/config.tomlwas not tried, so the README's claim may still hold for that route, but it might be worth re-checking.