Platform support

Edit on GitHub

Hand supports Linux, macOS (Apple silicon; Intel builds are released but not tested) and native Windows (amd64, Windows 10 or 11). Supported means the project fixes problems that people report on those systems, not that every step has run on a real machine; the table below says how each part is verified. Known gaps: hand-desktop builds on all three systems and its headless UI tests run on all three, but its real window has run only on Linux, and the binary refuses to start elsewhere with that reason; a release carries its asset for Linux amd64 and arm64 only, so hand update installs it there and hand doctor calls it unsupported elsewhere; Windows has run in CI, with the real Luvus, and not yet on a real Windows machine; the real Mac run of 2026-10-17 (#895) used a stand-in claude; Windows has no desktop notifications; the macOS LaunchAgent's RunAtLoad at a real login is unverified. omp has run on one Linux host only: neither Windows nor macOS has run a real omp, and a provider limit screen or a blocked approval screen was not captured, so Hand has no omp limit line.

Report a problem with the bug form, and a vulnerability as SECURITY.md says.

CI runs the tests on GitHub's latest Ubuntu (amd64), macOS (Apple silicon) and Windows Server (amd64) runners. Every job installs the tested Luvus and runs the TestLive* tests against it; the other tests use a fake one. A separate workflow, e2e-install.yaml, installs a build of the commit with the real installer on each of the three runners and uses it as a user does, with no harness installed. It gates every release: release.yaml runs it on the tag's own tree, with the tag's own scripts and end-to-end tests, so a manual release works only for a tag that carries them (v0.12.0 and later). An older tag has neither .github/e2e nor .github/scripts, and its behavior is not what the current tests assert, so attach binaries to such a tag by hand. The Linux arm64 and Intel macOS builds are released but not run in CI.

In the table:

WhatLinuxmacOSWindows
Install scriptCI e2ereal Mac (#895), CI e2eCI e2e, in PowerShell 5.1 and 7
Install script fetches hand-iroh next to hand, and keeps the old binaries when its checksum, version or archive failsCI e2e, CICI e2eCI e2e, in PowerShell 5.1 and 7, for a corrupted sidecar archive
hand init and the Luvus pindaily, CI e2ereal Mac (#895), CI e2eCI e2e
Download the tested LuvusCI, real LuvusCI, real LuvusCI, real Luvus
Luvus server, panes and eventsdailyreal Mac (#895), stand-in claudeCI, real Luvus over its named pipe
Start claude, and accept its trust screendaily; CI, real harnessCI, real harnessCI, real harness
Start codex or agyreal; codex: CI, real harnesscodex: CI, real harness; agy: CI, fakecodex: CI, real harness; agy: CI, fake
Accept Codex's trust screens for its own worktree and projectCI, real harnessCI, real harnessCI, real harness
Start opencodeCI, fakeCI, fakeCI, fake
Start copilot (a worker only), and accept its trust screenreal, auto only; CI, fakeCI, fakeCI, fake
Start omp as a worker or the supervisor, list its models, and find and resume its supervisor sessionreal, Linux onlynot runnot run
Find a harness off PATH: in mise's shims folder (MISE_DATA_DIR/shims, $XDG_DATA_HOME/mise/shims, ~/.local/share/mise/shims, on Windows %LOCALAPPDATA%\mise\shims), then for copilot in npm's global bin folder (npm prefix -g) and the folder gh copilot downloads to (~/.local/share/gh/copilot, $XDG_DATA_HOME/gh/copilot, on Windows %LOCALAPPDATA%\GitHub CLI\copilot\copilot.exe)real mise and both copilot folders, then a worker started and reported; unit testsunit tests; the mise and gh paths are read from mise's documentation and the gh source, not from a rununit tests; the mise and gh paths are read from mise's documentation and the gh source, not from a run
Hand refuses a harness found only as a .cmd or .bat (except the .exe that npm puts inside the package of claude, codex, opencode and copilot, found beside the shim in a layout copied from the npm tarballs, not from a Windows install), and prefers a .exe anywhere on PATHnot needednot neededCI
A prompt reaches the harness byte for byte when it fits both limits (16 KiB, and on Windows 32,766 characters of quoted command line), and Hand refuses one that does notCI, real Luvus, fake codex.exeCI, real Luvus, fake codex.exeCI, real Luvus, fake codex.exe
Keys reach an agent whose program has an extension (codex.exe) after a process scanCI, real Luvus, fake codex.exeCI, real Luvus, fake codex.exeCI, real Luvus, fake codex.exe
A worker reports through the footerdaily; CI, real harnessCI, real harnessCI, real harness
A 15 KB briefing reaches the real harness unchangedCI, real harnessCI, real harnessCI, real harness
A wake reaches the supervisor's harness and is answeredCI, real harnessCI, real harnessCI, real harness: Hand presses Enter for Codex and types the text into Claude Code where Luvus will not prompt
Stop a workerdaily; CI, real harnessreal Mac (#895), stand-in claude; CI, real harnessCI, real harness
hand attachCI, fakereal Mac (#895), stand-in claudenot run
Start the watcher on demand, and the board keeps watchers alivedailyreal Mac (#895)CI, fake
hand updatedaily, CI e2e from v0.13.0real Mac from v0.11.0 (#895) and CI e2e from v0.13.0CI e2e from v0.13.0, the running executable renamed to hand.exe.old
Luvus switch on repin: stop the server through UHP and server.pidCI, fakeCI, fakeCI, fake
Luvus switch of a quiet fleet: stop the supervisor and the server, start the pin, resume the supervisorCI, real Luvus, fake claudeCI, real Luvus, fake claudeCI, real Luvus, fake claude
hand board --update-every, the timer's hand updateCI, fakeCI, fakeCI, fake
hand board --iroh runs hand-iroh as its child, shows its state in the Phone page and hand doctor, and the child leaves when the board stopsCI e2e and CI, fake sidecarCI e2e, fake sidecarCI e2e, fake sidecar: the board is ended with TerminateProcess and the closed pipe stops the child
hand update fetches hand-iroh with a new handCI e2e, CI, fake releaseCI e2e, fake releaseCI e2e, fake release
The real hand-iroh starts, keeps its ID, holds its key with modes 0700 and 0600 and leaves when its input closesCI, real sidecarCI, real sidecarCI, real sidecar
First run with no harness: hand init, doctor, orient, projects, tasks, attempt track, modelsCI e2eCI e2eCI e2e
The Board over HTTP: fleet page, /events beat, document viewer, phone pairing and revokeCI e2eCI e2eCI e2e
The Board in Chromium at 1440 and 390 px: five regions, document dialog, project badges, no console errorCI e2eCI e2eCI e2e
Upgrade from v0.13.0 with backups, the old binary refusing a migrated database, and the documented restoreCI e2eCI e2eCI e2e
Desktop notificationsdailyreal Mac (#895)no
hand open starts the board and opens the browserCI e2e starts the board, fake browserreal Mac (#895)CI e2e starts the board, fake browser
Paths over 260 charactersnot needednot neededCI for Hand's git; not run for a worker's
hand-desktop: the MyGo window builds cgo-free, hand-desktop --version prints the same version and commit as hand version, and its ui.NewTester scenes, navigation and screenshots pass headlessCI, headlessCI, headless; the binary refuses to startCI, headless; the binary refuses to start
The release carries hand-desktop for Linux amd64 and arm64; hand update checks its .sha256 and its version and commit, places it next to hand, and reports none in this release or unsupported on OS/ARCH, and hand doctor has a desktop rowCI, fake releaseCI, fake release: unsupportedCI, fake release: unsupported
hand-desktop over a running board: the fleet switch, Status, Needs, Conversation (with its composer), Tasks and the four detail screens read /api/v1, and a message, an answer and a Mark read post to itthe window itself has run; the API path has the golden-JSON testsnot runnot run
hand-desktop --open LINK and the single-instance lock: an alert's Open shows the item in the windowthe parser, the hand-off and the click command have tests; the lock has not run between two real windowsnot runnot run
the single-instance lock's socket is private to the user only under XDG_RUNTIME_DIR (mode 0700); without it MyGo puts mygo-<uid>-<hash>.sock in $TMPDIR or /tmp, where another local user could hold the name and make a launch exit without opening a windowread from MyGo v0.7.4not runnot run
Start the board at logindailyreal Mac (#895) by bootout and bootstrap; not at a real loginnot run
The watcher starts a killed Luvus server againCI, real Luvus, fake claudereal Mac (#895), stand-in claudeCI, real Luvus, fake claude

The two .exe rows prove Hand's side: that the lookup picks a .exe over a .cmd and names the shim it refuses (temporary PATH folders with files of those names), and that real Luvus refuses a key to a fake codex.exe until Hand has asked it for a process scan, and accepts it after. The real claude.exe and codex.exe run in the end-to-end tests. Those do not prove what cmd.exe does with a real npm shim (docs/dogfood/windows.md W4b checks Hand's refusal with a stand-in), or anything that depends on a user profile with a space or a non-ASCII letter (W11 checks a temporary folder with both). The prompt row proves that real Luvus hands a fake codex.exe the exact bytes of prompts of the sampled sizes from 1 byte to 16 KiB, with quotes, backslashes, %VAR%, &, Unicode and CRLF, for a worker and a supervisor, and that hand attempt start passes a representative brief whole; the end-to-end tests show the real claude.exe and codex.exe receiving a 15 KB brief of that kind unchanged, and W4c checks the real Codex on a real machine.

On Linux, the setup without a systemd user session (plain SSH, for example) has run only in CI. Hand configures neither commit signing nor ssh on any OS.

Real-machine checks

To help, run the checks in docs/dogfood/ on a real machine, yourself or with a coding agent: one set of checks, with a throw-away home that never touches your own fleets. The checks live only there, riskiest first, and so do the checks for Linux without a systemd user session and for Linux arm64. Report what fails through the issue form; when a run passes, the row of the table above changes from "CI" to a real-machine result, with a link to the run issue.