Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4a21944
Windows: WSL boundary as one wrap seam (#397)
Aug 18, 2026
b123c25
Merge branch 'coder/windows-support-hardening-wsl-boundary-d-d3d627' …
Aug 18, 2026
2a893e8
Merge branch 'coder/windows-support-hardening-wsl-boundary-d-d3d627' …
Aug 18, 2026
d1cf7bb
Merge branch 'coder/windows-support-hardening-wsl-boundary-d-d3d627' …
Aug 18, 2026
0d6305f
Merge branch 'coder/windows-support-hardening-wsl-boundary-d-d3d627' …
Aug 18, 2026
cb85c11
feat(threads): per-thread sandbox badge (#436)
Aug 18, 2026
246b39b
Windows doctor on project add (#435)
Aug 18, 2026
954a62c
Windows: CI matrix on windows-latest, no fake winget (#437)
Aug 18, 2026
6db23b4
Merge branch 'coder/fork-windows-support-hardening-wsl-bound-a11a21' …
Aug 18, 2026
702a803
Windows: keep worktrees on the repo's side of the WSL boundary
Aug 18, 2026
e61a606
doctor: run the three probes in parallel (#435)
Aug 18, 2026
e614a73
Merge branch 'coder/fork-windows-support-hardening-wsl-bound-e59f7d' …
Aug 18, 2026
da5af3d
Merge branch 'coder/fork-windows-support-hardening-wsl-bound-93db45' …
Aug 18, 2026
54ce9b6
ci: run every win32-safe suite on the Windows leg (#437)
Aug 18, 2026
160e14e
Windows: make agent dispatch possible at all (#442, #441)
Aug 18, 2026
6b54e75
Merge branch 'coder/windows-support-hardening-wsl-boundary-d-d3d627' …
Aug 18, 2026
468b651
Merge branch 'coder/windows-support-hardening-wsl-boundary-d-d3d627' …
Aug 18, 2026
296020b
Merge branch 'coder/windows-support-hardening-wsl-boundary-d-d3d627' …
Aug 18, 2026
4e1fdd6
Windows: remaining POSIX assumptions outside agent dispatch (#448)
Aug 18, 2026
c4cbc6d
Windows: run electron/smoke.js on macos and windows CI (#449)
Aug 18, 2026
225f6a9
Merge branch 'coder/fork-windows-support-hardening-wsl-bound-4080d8' …
Aug 18, 2026
eb26a77
Windows: shared win32-safe fake CLI for electron tests (#450)
Aug 18, 2026
ffe3ef3
Merge branch 'coder/fork-windows-support-hardening-wsl-bound-89b04f' …
Aug 18, 2026
5f0ca3b
test: cross-reference the two writeFakeBin helpers (#450, #449)
Aug 18, 2026
bf75aad
Merge origin/main into the Windows branch
Aug 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
# The gate: every suite on every push. `npm run acceptance` stays manual —
# it spends a real claude turn and needs credentials.
#
# windows-latest keeps the win32 paths honest (WSL wrap in electron/wsl.js).
# There is no node-pty in this repo, so the ConPTY rebuild named in #397 is
# a no-op — do not add a rebuild step. Most of the electron suite still
# cannot run on win32 (shebang fakes); scripts/test-electron.js skips those
# and runs the WSL contract only. Winget is blocked on an installer — see
# the note in scripts/package-cross.sh.
#
# `smoke` is a separate job on purpose: a boot/dispatch failure must show
# up as a boot failure, not a needle inside `npm test`. It is the first
# thing in CI that actually starts the app on Windows (#449).
name: test

on: push

jobs:
test:
runs-on: macos-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Expand All @@ -18,3 +33,23 @@ jobs:
- run: npm ci --prefix core
- run: npm ci --prefix memory-server
- run: npm test

smoke:
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: "**/package-lock.json"
- run: npm ci
- run: npm ci --prefix core
- run: npm run build --prefix core
- run: npx vite build
- run: npm run test:smoke
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ Grab an archive from the [latest release](https://github.com/currentbits/solenta
`Solenta.app` to `/Applications`. Not notarized, so right-click → **Open** the
first time (or `xattr -dr com.apple.quarantine /Applications/Solenta.app`).
- **Windows (x64)** — `Solenta-<v>-win32-x64.zip`. Unzip anywhere, run
`solenta.exe`. Unsigned, so SmartScreen asks once.
`solenta.exe`. Unsigned, so SmartScreen asks once. There is no installer
and no winget package; see the note in `scripts/package-cross.sh`.
- **Linux (x64)** — `Solenta-<v>-linux-x64.tar.gz`. Extract, run `./solenta`.

### Updates
Expand Down
6 changes: 5 additions & 1 deletion electron/agent.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"use strict";

const { spawn } = require("node:child_process");
// cross-spawn, not child_process: on Windows the agent CLIs install as
// .cmd shims and Node refuses to exec those directly. cross-spawn routes
// them through cmd.exe with correct escaping, which matters because the
// prompt travels in argv (#442).
const spawn = require("cross-spawn");
const { killTree } = require("./proc.js");

const CHUNK_THROTTLE_MS = 250;
Expand Down
6 changes: 5 additions & 1 deletion electron/claude.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"use strict";

const { spawn } = require("node:child_process");
// cross-spawn, not child_process: on Windows the agent CLIs install as
// .cmd shims and Node refuses to exec those directly. cross-spawn routes
// them through cmd.exe with correct escaping, which matters because the
// prompt travels in argv (#442).
const spawn = require("cross-spawn");
const { getClaudeMcpArgs } = require("./memory-sup.js");
const { killTree } = require("./proc.js");

Expand Down
6 changes: 5 additions & 1 deletion electron/codex.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"use strict";

const { spawn } = require("node:child_process");
// cross-spawn, not child_process: on Windows the agent CLIs install as
// .cmd shims and Node refuses to exec those directly. cross-spawn routes
// them through cmd.exe with correct escaping, which matters because the
// prompt travels in argv (#442).
const spawn = require("cross-spawn");
const { killTree } = require("./proc.js");

const SIGKILL_AFTER_MS = 3000;
Expand Down
6 changes: 5 additions & 1 deletion electron/commitmsg.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"use strict";

const { spawn } = require("node:child_process");
// cross-spawn, not child_process: on Windows the agent CLIs install as
// .cmd shims and Node refuses to exec those directly. cross-spawn routes
// them through cmd.exe with correct escaping, which matters because the
// prompt travels in argv (#442).
const spawn = require("cross-spawn");
const { getProvider, resolveBin, isBinAvailable } = require("./providers.js");
const { diff, assertNoOutboundSecrets } = require("./worktrees.js");
const { fmRun } = require("./fm.js");
Expand Down
47 changes: 39 additions & 8 deletions electron/devservers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

const fs = require("node:fs");
const path = require("node:path");
const { spawn } = require("node:child_process");
// cross-spawn, not child_process: on Windows npm is npm.cmd and Node
// refuses to exec .cmd shims without a shell. Same reason as agent.js (#442).
const spawn = require("cross-spawn");
const { wrapCommand } = require("./ssh.js");
const { wslTarget } = require("./wsl.js");

const PREFERRED_SCRIPTS = ["dev", "start", "serve"];
const RING_LIMIT = 50;
Expand Down Expand Up @@ -75,6 +79,7 @@ function detectScripts(root) {
* pending: string,
* dead: boolean,
* deadAt: number | null,
* platform: NodeJS.Platform,
* }} DevServerRecord
*/

Expand Down Expand Up @@ -165,11 +170,16 @@ function toState(rec) {

/**
* @param {number} pid
* @param {NodeJS.Platform} [platform]
*/
function killProcessGroup(pid) {
function killProcessGroup(pid, platform = process.platform) {
if (!pid) return;
// Windows has no POSIX process groups; process.kill(-pid) throws and
// SIGTERM is terminate. Kill the pid directly instead of failing closed
// through the catch.
const target = platform === "win32" ? pid : -pid;
try {
process.kill(-pid, "SIGTERM");
process.kill(target, "SIGTERM");
} catch {
try {
process.kill(pid, "SIGTERM");
Expand All @@ -181,7 +191,7 @@ function killProcessGroup(pid) {
const timer = setTimeout(() => {
pendingKills.delete(pid);
try {
process.kill(-pid, "SIGKILL");
process.kill(target, "SIGKILL");
} catch {
try {
process.kill(pid, "SIGKILL");
Expand All @@ -198,21 +208,40 @@ function killProcessGroup(pid) {
* Spawn `npm run <script>` for this thread. Already-running returns
* the current state without spawning again.
*
* No /bin/sh: the command is argv (`npm`, `run`, script), not a user
* shell string. On win32, cross-spawn finds npm.cmd. A WSL-side root
* is wrapped through ssh.js so npm runs inside the distro.
*
* @param {string} threadId
* @param {string} root
* @param {string} script
* @param {{
* platform?: NodeJS.Platform,
* spawn?: typeof spawn,
* project?: { remoteHost?: string, remotePath?: string, path?: string } | null,
* }} [opts]
*/
function start(threadId, root, script) {
function start(threadId, root, script, opts = {}) {
const existing = records.get(threadId);
if (existing && !existing.dead && isAlive(existing.pid)) {
return toState(existing);
}
if (existing) records.delete(threadId);

const platform = opts.platform || process.platform;
const spawnFn = opts.spawn || spawn;
const project = opts.project || { path: root };
// WSL-side only — do not wrap ssh remotes (would change macOS behaviour).
const wsl = wslTarget(project, platform);
const raw = { bin: "npm", args: ["run", script] };
const wrapped = wsl
? wrapCommand(project, raw.bin, raw.args, platform)
: raw;

let child;
try {
child = spawn("npm", ["run", script], {
cwd: root,
child = spawnFn(wrapped.bin, wrapped.args, {
cwd: wsl ? undefined : root,
detached: true,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
Expand All @@ -228,6 +257,7 @@ function start(threadId, root, script) {
pending: "",
dead: true,
deadAt: Date.now(),
platform,
};
records.set(threadId, rec);
return toState(rec);
Expand All @@ -244,6 +274,7 @@ function start(threadId, root, script) {
pending: "",
dead: !pid,
deadAt: !pid ? Date.now() : null,
platform,
};
records.set(threadId, rec);

Expand Down Expand Up @@ -276,7 +307,7 @@ function start(threadId, root, script) {
function stop(threadId) {
const rec = records.get(threadId);
if (!rec) return { running: false };
if (rec.pid) killProcessGroup(rec.pid);
if (rec.pid) killProcessGroup(rec.pid, rec.platform);
records.delete(threadId);
return { running: false };
}
Expand Down
Loading
Loading