Thanks to visit codestin.com
Credit goes to github.com

Skip to content

OR-199 Windows support for the CLI and dashboard - #299

Merged
myles332 merged 50 commits into
mainfrom
myles/windows-version
Sep 11, 2026
Merged

OR-199 Windows support for the CLI and dashboard#299
myles332 merged 50 commits into
mainfrom
myles/windows-version

Conversation

@myles332

@myles332 myles332 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

orx builds, tests and runs on Windows — double-click orx.exe and the dashboard opens — and a release now ships a Windows zip and a PowerShell installer. Scope is the local CLI and dashboard, not a packaged desktop app.

What was broken on Windows

Found by reading the code: verbatim \\?\ paths broke every git call; CRLF checkouts corrupted the agent system prompt; harness discovery found nothing without PATHEXT; NTFS exec bits moved the demo's pinned SHAs; remote paths were validated with local rules; cancel did nothing.

Found by running it on a real machine, after CI was green: double-click printed usage and exited; every ssh call failed with getsockname failed because Win32-OpenSSH cannot multiplex (#302, merged in here); git fatals on a NUL config path; mkdir: command not found because bash splits PATH on :; tar read C: as a host; cp1252 UnicodeEncodeError; the 260-character path cap on worktrees; os error 1314 on symlinks killed onboarding; the demo's runcpu.sh could not run.

Keeping macOS and Linux unchanged

Six review rounds — three before this PR opened, three after, the last three focused on mac/linux. They caught regressions this branch itself had introduced, all fixed and tested:

  • No release would have published, on any platform. verify-build-channel could not read the Windows .zip, and it gates host.
  • A second orx up silently opened whichever dashboard held the port. Now only a Windows double-click reuses one, and only one speaking this build's protocol.
  • Moving EXPERIMENT_SHA stranded existing demo installs: every demo chat would have failed on its next turn. An install now resolves its own experiment commit; PREVIOUS_EXPERIMENT_SHA is accepted throughout.
  • orx up --remote rejected a remote data path containing ..

The final round swept every shared code path against origin/main; each is identical or deliberately changed (for example PYTHONIOENCODING=utf-8 on all backends, which a user-set value still overrides).

Load-bearing choices

  • orx up --remote-host is refused on Windows (the control channel is a Unix socket); the PATH guard is absent; ownership and mode checks are unix-only.
  • The Windows CI job gates releases, since a release now ships a Windows artifact.
  • No code signing yet — SmartScreen warns on every download.

Testing

  • cargo fmt --check, clippy -D warnings, build --locked, test --locked — 778 tests, macOS
  • Same on windows-latest, green on this HEAD
  • Final review round: NO BLOCKERS from all four reviewers
  • orx up serves the dashboard on a real Windows machine
  • Double-clicking orx.exe starts the dashboard
  • The harness picker finds an installed agent
  • release.yml dry run (-f tag=dry-run) builds and verifies the Windows artifact — the only exercise of the verify-job fix
  • Upgrade an existing macOS install with the demo seeded, then send a message in a demo chat
  • A second double-click opens the running dashboard
  • A harness spawns and completes a turn on Windows
  • Demo onboarding end to end on Windows, including a completed run
  • Prompt in a new project — a worktree past 260 characters
  • Connect an SSH host from Settings on Windows
  • Run an experiment locally, cancel it, confirm no orphaned processes

Follow-ups, not in this PR: orx update on Windows suggests Unix commands; code signing; mirroring install.ps1 on openresearch.sh.

🤖 Generated with Claude Code

myles332 and others added 17 commits September 8, 2026 11:06
Block A of the Windows beta: the fixes that need no Windows machine to
write, so the branch is ready to clone the moment one is set up.

- Extend a bare binary name with PATHEXT when searching PATH. Every
  harness is looked up by bare name, so on Windows the picker reported
  Claude Code, Codex and OpenCode all absent and the dashboard could do
  nothing.
- Compose a child's PATH with `;` rather than a hardcoded `:`.
- Find bash through `git` rather than PATH. The `bash.exe` usually on
  PATH is the WSL launcher in System32, which sees a different
  filesystem than the run directory it would be handed.
- Give git an empty path it understands. `/dev/null` reads as a missing
  relative path on Windows, silently re-enabling repository hooks.
- Check a local run's liveness with a zero-timeout wait; there is no
  `ps` to shell out to.
- Link through native_store::create_symlink, which already has both
  branches, instead of calling the unix symlink directly.
- Add a windows-latest CI job so none of this regresses.

The Windows branches here have not been compiled yet — that is the next
step, on the machine.

Co-Authored-By: Claude Opus 5 <[email protected]>
Round one of the Windows compile, from the first CI run: 25 diagnostics,
seven root causes.

- Give remote_host the `#[cfg(not(unix))]` twins its uid/mode helpers were
  missing, matching what `directory_writable` already does. Windows keeps
  only the symlink and directory checks in `ensure_private_dir`, which is
  a weaker guarantee than the unix path makes.
- Gate the control channel itself. It is a Unix domain socket, so
  `--remote-host` is now refused up front on Windows rather than starting
  a host nothing could attach to. Local `orx up` is untouched.
- Annotate `signal`, whose type came only from the cfg(unix) block.
- Gate the EXDEV rename fallback, the inode and mode assertions, and the
  shell-hook tests, all of which assert on things Windows does not have.
- Silence the parameters that are only read on unix.

Co-Authored-By: Claude Opus 5 <[email protected]>
Round two, from the cascade the unresolved socket import had been
suppressing: every caller of the six control-channel functions.

The module splits cleanly in two. `orx up` uses its types and its
dashboard locking — HostDescriptor, DashboardLock, canonical_data_dir —
and none of that needs a socket. The `orx remote-host` command surface is
socket-backed all the way down, so it is gated as a unit and the command
now refuses on Windows instead of failing somewhere further in.

Also: the two imports only the gated half reached, and `signal`, which
loses its one assignment on Windows and with it the need for `mut`.

Co-Authored-By: Claude Opus 5 <[email protected]>
Round three was 24 dead-code and unused-import lints and no real compile
errors — the tail of removing a feature's entry points while its
machinery stays behind.

One scoped allow on the module says that plainly. Gating each constant,
helper and request type would be bulkier than the code it guards, would
have to be unpicked the moment the control channel gets a Windows
transport, and would say nothing the module comment does not.

updates.rs is the same shape at smaller scale: relaunch_target and
relaunch_args keep their tests on every platform and lose only their
caller in the binary.

Co-Authored-By: Claude Opus 5 <[email protected]>
The Windows suite ran for the first time: 662 passed, 46 failed. Two
causes account for 27 of them.

`repository_git_dir` canonicalizes, and Windows answers canonicalize with
a `\\?\C:\…` verbatim path. Git rejects those outright — "not a git
repository" — so importing any existing repository failed. This is a real
bug, not a test artifact; the prefix now comes off for drive paths, which
are the only ones with a plain form.

The other 21 were `remove_dir_all(root).unwrap()` in test teardown, which
Windows refuses while any handle into the tree is still open. The
codebase already prefers a tolerant cleanup in 146 other places; these 86
now match. All of them are inside `#[cfg(test)]`.

Also: the probe fixtures asserted on POSIX PATHs, which are not absolute
on Windows and so were rejected by the parser under test. They are
parameterized rather than gated — the parsing they cover is not
platform-specific, only the fixture was.

Co-Authored-By: Claude Opus 5 <[email protected]>
Git for Windows defaults to core.autocrlf=true, and SYSTEM_PROMPT.md and
the agent-skills SKILL.md files are include_str!'d at build time. A
Windows build therefore embedded CRLF and then failed to parse it:
playbook_md() splits the template on `-->\n\n` to drop its leading HTML
comment, that split stopped matching, and `unwrap_or` kept the comment —
so every agent session would have been handed OpenResearch's internal
notes as the opening of its system prompt.

Two tests caught it, one of them by scanning the rendered playbook for
unresolved `{token}` placeholders rather than a fixed list. That is the
only reason this surfaced as a test failure and not as a shipped binary.

Nothing renormalizes: no tracked text file has CRLF in the index today,
so this only governs what lands in a working tree.

Co-Authored-By: Claude Opus 5 <[email protected]>
Round two of the Windows suite: 691 passed, 17 failed. Four of those were
real bugs rather than test artifacts.

Demo onboarding rejected the demo it had just built. NTFS has no
executable bit, so runcpu.sh committed as 100644, the tree hash moved,
and the commit ids drifted off the BASELINE_SHA/EXPERIMENT_SHA the demo
validates itself against. The mode now reaches the tree through the
index, where it does not depend on the filesystem.

Remote paths were validated with local rules. `storage_root` asked
`Path::is_absolute` about `/home/me/.cargo/bin/orx`, which is false under
Windows, so a Windows client rejected every legitimate remote path — and
the PathBuf it built would have reached a Linux shell separated by
backslashes. Remote paths are POSIX whatever the client runs, so they are
handled as strings now.

`manage_local_file` took `reports/result.md` from the dashboard and
answered `reports\summary.md`, which the next request would have carried
straight back. Relative paths cross the API `/`-separated.

Cancelling a local run did nothing: there is no process group to TERM.
`taskkill /T` takes the tree instead, which is what TERMing the group was
for.

The rest were fixtures asserting on POSIX shapes, a filename using a
character Windows reserves, and two LaTeX tests that need a symlink and a
shebang.

Co-Authored-By: Claude Opus 5 <[email protected]>
Setting the mode through `update-index` fixed the commit ids but left the
index at 100755 against a working tree NTFS always reports as 0644. With
`core.filemode true` git believed the filesystem, read runcpu.sh as
modified, and refused to check main back out. Windows trusts the index
instead, which is where the mode came from.

The two telemetry failures are not diagnosed. HTTP delivery itself works
on Windows — `sender_posts_the_first_party_endpoint` asserts an
Acknowledged post and passes — so what fails is the removal that follows
it, and reading has not told me why. Both assertions now report what a
second removal says, which separates a blocked delete from a delete that
succeeded while the name lingered.

Co-Authored-By: Claude Opus 5 <[email protected]>
Three down to two.

`join("demo-repos/nanochat.git")` embeds a separator the platform did not
choose, so on Windows the bare origin came out as
`...\data\demo-repos/nanochat.git` — mixed, and textually unequal to what
git echoes back for the remote it was given. Filesystem calls did not
care; comparing the recorded URL to git's answer did. Split into two
joins, in product code as well as the tests that caught it.

`taskkill /T` returns non-zero when any descendant has already exited on
its own, which says nothing about whether the run stopped. Cancellation
now reads the leader's liveness, which is the question actually being
asked.

The telemetry pair is one step from diagnosed. The retry in the previous
round answered Ok(()), so the file was there and freely deletable and the
removal was never reached — delivery must have come back Retryable. The
assertion now names the outcome, which separates a request that never
landed from a response classified unexpectedly.

Co-Authored-By: Claude Opus 5 <[email protected]>
The last two Windows failures, and they were the tests' fault rather than
delivery's.

Two of the three mock servers accepted a connection and wrote their
response without ever reading the request. Closing a socket that still
holds unread data aborts the connection on Windows instead of closing it
gracefully, so the client saw a reset rather than the reply it had
already been sent, and classified a 400 as Retryable — which left the
outbox file the assertion expected to be gone. The third server always
read, which is why it passed and misled the search toward the removal.

The read loop it already had is now shared by all three.

Co-Authored-By: Claude Opus 5 <[email protected]>
An audit of the `orx up` paths no test exercises. Every bug this branch
has fixed so far was in code a test happened to touch; the dashboard's
own request handling was the largest area where that was not true.

Windows `canonicalize` answers with a verbatim `\\?\C:\…` path, and
`CreateProcessW` will not take one as a working directory. The dashboard
canonicalizes a session's checkout root and then runs git in it — twelve
call sites behind `resolve_checkout_root`, covering the code tree, file
reads and writes, and diffs — so none of that could have worked. The same
root reaches `run_shell_command` as the cwd for a `!` command, and Codex
receives canonicalized paths as its sandbox writable roots.

Consistency matters as much as the spelling: containment checks compare a
canonicalized child against a canonicalized root, and a mix of the two
forms denies access to paths that are genuinely inside. So every
canonicalization in the crate now goes through one helper rather than the
handful that happened to be reachable from a failing test — that is what
makes the property hold rather than the individual fixes.

The earlier one-off in `repository_git_dir`, which the import failure
found, is folded into it.

Co-Authored-By: Claude Opus 5 <[email protected]>
Second audit pass, over the spawn path.

`find_claude` and `find_opencode` fall back to their installers' drop
locations when PATH does not answer, and both spell them as bare names —
`~/.local/bin/claude`, `~/.opencode/bin/opencode` — which match no file
on Windows. `~/.local/bin` is exactly where Claude Code's own Windows
installer puts claude.exe, so the fallback was dead precisely where it
was needed. Both now look for the same names PATH does.

The rest of the spawn path holds up: config directories travel as
environment variables, where a raw path is correct; Codex's config.toml
is written through serde_json, whose backslash escape is the one TOML
wants; and the binaries themselves come from find_on_path, which learned
about PATHEXT earlier on this branch.

One gap left deliberately: the PATH guard writes zsh and bash startup
hooks and needs ZDOTDIR or HOME to place them, neither of which Windows
sets, so it returns early and the guard is simply absent there. It fails
closed — a session gets no hook rather than a broken one — and building a
Windows equivalent is worth more than a beta needs.

Co-Authored-By: Claude Opus 5 <[email protected]>
Both conflicts were additive: main gave ci.yml a `workflow_call` entry
point and a new `local::browser_cookies`, alongside this branch's
`workflow_dispatch`, windows job, and `local::bash`.

Two things the merge is worth noting for:

release.yml now calls ci.yml to gate publishing, so the windows job added
here gates releases too — a Windows regression will hold up a macOS or
Linux release for a platform that has no users yet.

overleaf_live's tests arrived with a raw `std::fs::canonicalize`, which
this branch had just finished routing through `crate::paths` everywhere.
Folded in, so the property still holds tree-wide.

Co-Authored-By: Claude Opus 5 <[email protected]>
Six blockers, all real.

`orx_bin_dir` dropped any directory whose name contains a colon, which on
Windows is every absolute path, so agents were never handed the running
orx on PATH — and the separator constant added earlier sat in a branch
that never ran. It filters on that constant now.

A dashboard shell command that hit its timeout killed nothing on Windows
and then waited on the child with no timeout, hanging the turn forever.
The non-unix arm terminates the tree, the shape latex already used.

BIBINPUTS/BSTINPUTS joined with a colon, so bibtex on Windows lost both
the paper's own directory and the system defaults.

`restore_local_repository` never pinned core.autocrlf, so a restored demo
clone was written CRLF against LF blobs and then failed the cleanliness
check that reads it with global config disabled — telling the user their
demo "is not the OpenResearch nanochat demo".

The remove_dir_all sweep had converted four setup removals along with the
teardown, letting tests pass while testing nothing.

The Windows uid/mode stubs were unreachable: their whole chain is rooted
in cfg(unix) entry points. Gated instead, stubs deleted, and with them a
comment claiming a weaker-but-working private runtime directory that does
not exist.

Also from review: prefix stripping now matches the parsed prefix, so a
non-UTF-8 name cannot keep `\\?\` while its root loses it and fail its own
containment check; UNC verbatim paths are decoded rather than passed
through. PATHEXT set-but-empty no longer reports every tool missing. The
bash fallback no longer resolves back to the WSL launcher it rejects.
pdfPath goes through the same API path rule as every other relative path.
The telemetry signature widened for a test message is reverted.

Co-Authored-By: Claude Opus 5 <[email protected]>
Two blockers, both introduced by round one's own fixes.

Deleting the Windows uid/mode stubs was wrong: `open_lock` is ungated and
calls all three, and `DashboardLock::acquire` reaches it on every `orx
up`. That is a Windows compile error on the one platform this branch
exists for, and macOS cannot see it. The ownership and mode checks are
gated inside `open_lock` now, which keeps the file-type check everywhere
and states plainly what Windows does not enforce.

Teaching `orx_bin_dir` about the platform separator also handed
`ORX_BIN_DIR` to PATH_GUARD, a POSIX snippet that joins `PATH` on colons.
Under Git Bash a drive letter split it in two and the guard re-prepended
on every startup. The stamp is skipped off unix; `prepare_env` still
fronts the directory, which was the point.

Also: `cancel_job`'s cfg'd `return` is the function's last statement once
stripped, which the Windows clippy job would have rejected; the restore
path pins `core.eol` alongside autocrlf so a global `core.eol=crlf`
cannot reproduce the same dirty checkout; the verbatim UNC head is built
from `OsString` like the tail it is joined to; and the null-device
comment no longer claims git falls back to the repository's own hooks,
which it does not.

Co-Authored-By: Claude Opus 5 <[email protected]>
No blockers in either round-three review. Taking the nits: `open_lock`
fstats once instead of twice; the Windows branch of the `ORX_BIN_DIR`
stamp is a `#[cfg]` rather than a runtime-false `cfg!`, so it stops
resolving the executable only to discard it; two doc comments that
described one platform now describe both; and the ownership comment names
the case it does not cover — a lock under a shared `ORX_DATA_DIR` has
only its ACL on Windows.

Co-Authored-By: Claude Opus 5 <[email protected]>
@myles332
myles332 requested a review from sox8502 as a code owner September 9, 2026 01:11
myles332 and others added 5 commits September 8, 2026 18:14
release.yml calls ci.yml and requires it to pass, so the windows job
added here would have held up a macOS or Linux release over a platform
those releases ship nothing for.

`plan` is passed only by release.yml, so skipping on it draws the line
where it belongs: Windows still gates pull requests, which is what keeps
it from rotting, and never gates a release.

Co-Authored-By: Claude Opus 5 <[email protected]>
The artifact is what a tester is handed, and a debug build is slow enough
to colour their impression of the dashboard rather than the code's.

Co-Authored-By: Claude Opus 5 <[email protected]>
Win32-OpenSSH implements no connection multiplexing: it accepts
ControlMaster and ControlPath and then never creates a master. Omitting
them is not enough, because the user's own ssh_config can still set a
ControlPath, and one it honours fails the connection outright with
"getsockname failed: Not a socket". Passing ControlMaster=no and
ControlPath=none is what overrides that.

The socket machinery goes with it — the control directory, its path hash
and its preparation are unix's, and `master_is_running` can only answer
false where no master is ever created. Nothing changes on unix, where the
shared master still covers later status, log and job commands.

Co-Authored-By: Claude Opus 5 <[email protected]>
Two tests asserted the unix option set unconditionally and would have
failed on Windows — the very platform this change is for, and one CI
never builds. Both derive the expectation from the platform now.

`PathBuf`'s only uses became unix-gated, so its import would have failed
the Windows clippy job under -D warnings.

git shells out to ssh with its own command line, which never carried the
override, so a user's ssh_config could still break git-over-SSH on
Windows for exactly the reason this change exists. Routed through one
helper.

The rest: a doc comment my new test had come between and its rightful
test; four comments still promising multiplexing unconditionally, the
`interactive_args` one worst because it states the invariant that is
false on Windows; the new test pinned to the exact vector the way its
neighbour already is; and BatchMode coverage split out, since it holds on
every platform and was about to be gated away with the ControlPath half.

Co-Authored-By: Claude Opus 5 <[email protected]>
No blockers in either round-two review. Taking the nits.

Deriving the option count from `multiplexing_opts` had quietly removed
the only assertion pinning the unix vector — ControlPersist could have
been deleted and nothing in the tree would have failed. Pinned alongside
the Windows one.

The rest: a test whose name promised reuse on a platform that reuses
nothing; `git_ssh_command` says which command it builds; two more doc
comments of the class round one fixed, in the two other files that carry
them; and the Windows sentence dropped from `ssh_opts`, where it was the
third statement of the same fact within 150 lines.

Co-Authored-By: Claude Opus 5 <[email protected]>
myles332 and others added 6 commits September 9, 2026 14:57
Onboarding died on Windows at the demo's first `git init`:

    fatal: unable to access 'NUL': Invalid argument

`NUL` is right for `diff --no-index`, which recognizes it before touching
the path, and wrong everywhere a path is actually read. Git skips a
config file that reports ENOENT and fatals on any other error, and
Windows cannot `access()` its null device by name at all.

A real empty file behaves the same on every platform, and this file
already had that idiom in `clone_public_repository`. Shared now, and
pointing `hooksPath` at it still disables hooks, since git finds no hook
inside a file.

Co-Authored-By: Claude Opus 5 <[email protected]>
A local run died at `run.sh: line 6: mkdir: command not found`. bash was
running; it just had no coreutils. Git for Windows keeps those in
`usr\bin` and puts only `cmd` on the Windows PATH, so a bash started from
a Windows process inherits a PATH with no Unix tools at all.

Fronted for the two bash spawns only — a local run's launcher and the
composer's `!` command. Doing it for every child would shadow Windows'
own `find` and `sort` with the MSYS ones, which is a worse bug than the
one being fixed.

prepare_env's PATH is now a function the bash spawn can extend, so the
running orx still comes first and the toolchain sits ahead of both.

Co-Authored-By: Claude Opus 5 <[email protected]>
The previous commit set the toolchain PATH on the spawn, which run.sh
then overwrote on its second line: localrun exports the shell's PATH into
the script so a user's own command finds python and uv, and that export
wins over anything the process was started with.

Caught by the agent inside the dashboard, which read the generated run.sh
rather than the launcher and saw the hardcoded Windows PATH for what it
was.

Co-Authored-By: Claude Opus 5 <[email protected]>
Two things differ there, and the probe hit both. uv ships no shell
installer for Windows, so the setup uses its PowerShell one; and uv lays
the venv out as `Scripts/` rather than `bin/`, so both activations are
tried — either platform can have either layout depending on how the venv
was made.

These are Rust constants rather than files in the demo repository, so the
commit ids the demo validates itself against do not move. `runs/runcpu.sh`
carries the same POSIX assumption and is committed, so making the
project's own run command portable is a separate change that regenerates
those ids.

Co-Authored-By: Claude Opus 5 <[email protected]>
The run script exported the process PATH so a user's command could find
python and uv. On Windows that value is semicolon-separated, and bash
splits PATH on `:` — so it collapsed at the first drive colon and nothing
resolved, `mkdir` included. Adding the Git toolchain to it, as the last
two commits did, only added more entries to the same unusable string.

Git Bash converts the PATH it inherits into POSIX form for itself, so the
fix is to leave it alone: the launcher sets the process PATH, and the
script no longer overwrites it.

The `cd` on the next line had the same shape — bash reads a quoted
`C:\…` literally — so the run directory is forward-slashed now.

Found by the agent running inside the dashboard, which read the generated
run.sh, spotted the separator mismatch, and verified a candidate PATH
against both splitters before proposing anything.

Co-Authored-By: Claude Opus 5 <[email protected]>
`tar: Cannot connect to C: resolve failed` — GNU tar reads a colon before
the first slash as a `host:path` remote spec, so the snapshot archive's
Windows path made it try to resolve `C:` as a hostname. Forward slashes
would not have helped; the colon is what triggers it.

One conversion now covers both places a local path reaches the shell: the
archive tar unpacks, and the `cd` the launcher writes. `/c/Users/…` has
no colon at all. The HF backend passes a Linux container path and is
deliberately left alone.

Co-Authored-By: Claude Opus 5 <[email protected]>
myles332 and others added 22 commits September 9, 2026 17:37
A run died mid-training on `UnicodeEncodeError`: CPython encodes stdout in
the console's codepage, cp1252 on a default Windows install, and the
script printed a banner outside Latin-1. Nothing to do with the
experiment — it had already trained a tokenizer by then.

`PYTHONIOENCODING=utf-8` joins `PYTHONUNBUFFERED` as a default the author
can override, through the same helper every backend already calls, and
open-coded once more for kubernetes, whose env is a JSON array.

Set on every platform rather than only Windows: a Linux container with
`LANG=C` has the same ASCII default and would fail the same way.

Co-Authored-By: Claude Opus 5 <[email protected]>
Creating a session worktree for a deep repository failed partway through
with "Filename too long". Windows refuses paths over 260 characters
unless a program opts into the wide API, and a session worktree spends
about a hundred of them on `worktrees/<project>/chat_<session>` before
the repository's own paths begin.

`core.longpaths=true` on the invocations that write a working tree: the
shared helper every worktree and checkout goes through, the authenticated
fetch, and the anonymous clone. Read-only plumbing does not need it.

This was graded Minor in the original survey. It is a blocker for any
repository with a deep tree, which is most of them.

Co-Authored-By: Claude Opus 5 <[email protected]>
Windows grants symlinks only to an elevated process or one in Developer
Mode, so preparing the isolated agent home failed onboarding outright with
os error 1314. Fall back to the links that need no privilege: a junction
for a directory, which reads back exactly like a symlink, and a hard link
for a file, which does not — so the reconcile now recognizes one instead
of filing a conflict against it on every launch.

Co-Authored-By: Claude Opus 5 <[email protected]>
Windows' OpenSSH cannot create the AF_UNIX socket ControlMaster needs, and
rather than decline the option it kills the connection outright with
"getsockname failed: Not a socket" — so every ssh call orx made from Windows
failed, whatever the destination. Drop the Control* options there and pay a
handshake per call.

Same option vector, same platform: `UserKnownHostsFile=/dev/null` is a name
Windows' ssh takes literally, creating a `\dev\null` on the current drive.
It now gets a scratch file of orx's own.

Co-Authored-By: Claude Opus 5 <[email protected]>
PR #302 already turns Windows' multiplexing off, and does it by setting
ControlMaster=no explicitly rather than by omission — which is what
overrides a ControlPath in the user's own ssh_config — and covers git's
ssh besides. Take that one instead.

Co-Authored-By: Claude Opus 5 <[email protected]>
`UserKnownHostsFile=/dev/null` is a name Windows' ssh takes literally,
creating a `\dev\null` on whichever drive is current. Point it at a
scratch file of orx's own instead; StrictHostKeyChecking=no is what makes
a recycled provider key acceptable, so the behaviour is unchanged.

Co-Authored-By: Claude Opus 5 <[email protected]>
The demo's own run command reached for a POSIX uv installer and
`.venv/bin/activate`, neither of which exists on Windows, so the nanochat
demo could not run there; both now branch at runtime. That changes the
experiment tree, hence the new EXPERIMENT_SHA.

Releases built no Windows artifact at all, which is what the CI job's
non-blocking rationale rested on. Add the target and a PowerShell installer
so a beta tester has something to install.

Co-Authored-By: Claude Opus 5 <[email protected]>
The job skipped release runs because releases carried no Windows artifact.
They do now, so a regression on main would ship broken rather than fail
loudly.

Co-Authored-By: Claude Opus 5 <[email protected]>
Without it there is no usable bash, so experiments and the demo die on
their first command with a spawn error naming a path the user has never
heard of. The agents check next to it already warns this way.

Co-Authored-By: Claude Opus 5 <[email protected]>
Double-clicking orx.exe runs `orx up`. Doing it while orx is already
running failed the bind, printed an error into a console Explorer then
closed, and looked to the user like nothing happened at all. Open the
running dashboard instead, and hold the console open for whatever else
a startup failure has to say.

Co-Authored-By: Claude Opus 5 <[email protected]>
The console Explorer opens for a double-clicked orx.exe dies with the
process, so both ways this program exits with something to say — a
returned error and a panic — reached a window that was already gone. Put
the message in a dialog instead, and only when no terminal is attached to
read the printed one.

Co-Authored-By: Claude Opus 5 <[email protected]>
A bare `orx` prints usage and exits, which is right at a prompt and useless
from Explorer: the console it printed into closes with the process, so the
gesture looked like nothing happening. Owning the console is what tells the
two apart, and it now starts `up` — the same trade the macOS .app makes for
the same gesture.

Co-Authored-By: Claude Opus 5 <[email protected]>
Moving EXPERIMENT_SHA left every existing mac and linux install pointing
at a commit its repository does not contain. Each agent turn resolves the
session worktree from that commit, so all three demo chats would have
failed on their next message after upgrading. An install now starts its
sessions from whichever known experiment its demo branch descends from.

Co-Authored-By: Claude Opus 5 <[email protected]>
- Release verify job reads the Windows .zip and orx.exe; without it no
  release would have published for any platform.
- "Already running" reuse only for a double-click, and only of a
  dashboard speaking this build's protocol. On mac and linux a second
  `orx up` fails on the port again, as it did before this branch.
- An old demo install whose cache was wiped but origin kept restores its
  own experiment rather than building a sibling it cannot push.
- Same-file probe errors fall through to the old reconcile; the demo probe
  only puts ~/.local/bin on PATH after installing uv; the panic hook is
  Windows-only.
- Docs name the assets cargo-dist actually publishes.
- Comments cut to the one-or-two-line house rule; three doc comments moved
  back onto the functions they describe.

Co-Authored-By: Claude Opus 5 <[email protected]>
- A double-click keeps the flags it was given (`--no-telemetry` from a
  shortcut no longer turns telemetry back on).
- A kept demo origin is validated before a clone is restored from it, so a
  foreign or moved origin is named as the problem instead of the cache.
- The ssh opt-vector test reads the Windows known-hosts path once; it
  follows XDG_CONFIG_HOME, which telemetry tests mutate in parallel.
- Every comment this branch adds is now one or two lines.

Co-Authored-By: Claude Opus 5 <[email protected]>
- Remote storage paths accept an interior `.` again. `Path::components`
  skipped it on main, so `ORX_DATA_DIR=./orx` on a remote box — probed as
  `/home/u/./orx/orx.db` — broke `orx up --remote` on this branch.
- A foreign kept demo origin is rejected before anything is restored from
  it, now with a test; `install_repository` keeps main's shape, since the
  restore helper creates its own parent.
- Comments trimmed last round get back the why they lost, and the
  PYTHONIOENCODING one no longer claims the console codepage: redirected
  output uses the ANSI one.

Co-Authored-By: Claude Opus 5 <[email protected]>
@myles332
myles332 merged commit 9b05f97 into main Sep 11, 2026
17 checks passed
@myles332 myles332 changed the title Windows support for the CLI and dashboard OR-199 Windows support for the CLI and dashboard Sep 11, 2026
@myles332 myles332 mentioned this pull request Sep 11, 2026
11 tasks
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