From c60c0f5778ecce3a77953aee82626fc8c90ece76 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Mon, 12 Oct 2026 08:15:51 +1100 Subject: [PATCH 001/245] chore(porch): 1658 init spir --- .../status.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 codev/projects/1658-windows-native-non-wsl-support/status.yaml diff --git a/codev/projects/1658-windows-native-non-wsl-support/status.yaml b/codev/projects/1658-windows-native-non-wsl-support/status.yaml new file mode 100644 index 0000000000..f4f7273115 --- /dev/null +++ b/codev/projects/1658-windows-native-non-wsl-support/status.yaml @@ -0,0 +1,20 @@ +id: '1658' +title: windows-native-non-wsl-support +protocol: spir +phase: specify +plan_phases: [] +current_plan_phase: null +gates: + spec-approval: + status: pending + plan-approval: + status: pending + pr: + status: pending + verify-approval: + status: pending +iteration: 1 +build_complete: false +history: [] +started_at: '2026-10-11T21:15:51.801Z' +updated_at: '2026-10-11T21:15:51.801Z' From ccc3156e269964574c58b7127e3709df18ee476c Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Mon, 12 Oct 2026 08:21:54 +1100 Subject: [PATCH 002/245] [Spec 1658] Initial specification draft --- .../1658-windows-native-non-wsl-support.md | 530 ++++++++++++++++++ codev/state/spir-1658_thread.md | 18 + 2 files changed, 548 insertions(+) create mode 100644 codev/specs/1658-windows-native-non-wsl-support.md create mode 100644 codev/state/spir-1658_thread.md diff --git a/codev/specs/1658-windows-native-non-wsl-support.md b/codev/specs/1658-windows-native-non-wsl-support.md new file mode 100644 index 0000000000..94355e2e5a --- /dev/null +++ b/codev/specs/1658-windows-native-non-wsl-support.md @@ -0,0 +1,530 @@ +# Specification: Native (non-WSL) Windows Support + +## Problem Statement + +Codev runs on macOS and Linux only. On Windows the one working path today is WSL 2. On native +Windows, `afx tower start` hangs in its readiness wait because the daemon fails during startup +(field-tested 2026-09-09), and nothing after that point (persistent builders, forge, runnable +worktrees) has ever been exercised. + +WSL 2 already serves most Windows users. The segment it cannot serve is **machines where WSL 2 +is administratively blocked**: locked-down corporate IT that disables the Virtual Machine +Platform, Hyper-V, or the WSL optional component. For those users Codev is unusable. Native +support exists to unlock that segment, so the solution has to work on a locked-down machine. That +rules out designs that need admin rights, Developer Mode, or permissive PowerShell execution +policy where we can avoid them. + +The issue started as a tracking map with a go/no-go criterion. The owner has since ruled **go**, +with **full parity** as the end state (see Constraints). + +## Current State + +Codev assumes a POSIX host throughout. A codebase audit (2026-10-12) found roughly 145 +POSIX-dependent sites in the runtime package, clustered in five areas. The counts are +approximate; they size the work rather than enumerate it. + +1. **Shellper transport (blocking).** Persistent terminal sessions ("shellpers") talk to Tower + over Unix domain sockets in `~/.codev/run/shellper-.sock`. Four mechanisms depend on + socket files existing on disk: + - listen/connect; + - the post-spawn "wait for socket file" poll; + - stale-socket cleanup (`isSocket()` plus unlink); + - the attach fallback scan. + + Owner-only access comes from `0700` directory and `0600` socket modes, which Windows ignores. + On Windows a non-`\\.\pipe\` path cannot be listened on at all, so no shellper, which means no + persistent builders. +2. **Process lifecycle and introspection (blocking).** + - **Process-group kills.** About 9 sites tear down a process group with `process.kill(-pid)`, + including `PtySession.kill` (which every dev PTY goes through), the orphan sweep, + architect-session reaping, and `afx cleanup`. About 20 SIGTERM→SIGKILL escalations assume + SIGTERM is catchable; on Windows it is a hard kill, so graceful-shutdown handlers (such as + releasing the Tower owner lock) never run. + - **`ps`/`pgrep`/`lsof` shellouts.** About 11 sites use them: orphan and husk sweeps, fleet + RSS, Tower stop and start port-to-PID lookup, and doctor. + - **PID-reuse guard.** Process start time (the guard used on every shellper reconnect) has + only darwin and linux branches. On Windows it returns null, so **every reconnect after a + Tower restart is treated as PID reuse and fails**. The "detached shellpers survive a Tower + restart" guarantee is unproven on Windows. +3. **Launchers.** All 56 forge concept scripts are POSIX `#!/bin/sh`. The scripts are fine; the + **launch sites** are not. Five places assume a POSIX shell: + - Forge concepts run through `exec`/`execSync` with the default shell, which on Windows is + `cmd.exe` and cannot run a shebang script. + - Every builder starts as `/bin/bash .builder-start.sh`, a hardcoded absolute path. + - `worktree.postSpawn` hooks run with `shell: true`, which on Windows is `cmd.exe`. + - `afx dev` runs `/bin/sh -lc `. + - `afx shell` and the dashboard's "new shell" fall back to `$SHELL` or `/bin/bash`. +4. **node-pty / ConPTY.** node-pty supports Windows through ConPTY. The PTY base environment + assumes POSIX variables (`HOME`, `SHELL`, a `/usr/bin` PATH) and lacks the Windows ones + (`USERPROFILE`, `SystemRoot`, `COMSPEC`, `PATHEXT`). Commands such as `claude` and `afx` are + `.cmd`/`.exe` shims on Windows, and spawning them without a shell does not search `PATHEXT`. + The render gate (Spec 1313, #1573, #1664) classifies a byte stream whose echo, resize and + exit timing under ConPTY has never been captured. +5. **POSIX residue (wide, shallow).** + - `/tmp` hardcoding: 6 runtime sites and 565 occurrences across about 70 test files. + - `HOME` without `USERPROFILE`, and `~` expansion via string replace. + - About 30 `chmod`/mode sites, including the local auth key. + - Path handling: `split('/')` and `+ '/'` scope markers (about 20 sites), and absolute-path + checks via `startsWith('/')`. + - About 50 `split('\n')` sites parsing command output that would carry CRLF. + - Four `symlinkSync` sites for runnable worktrees, which need Developer Mode or admin rights + on Windows. + - `which`/`command -v` probes. + - The worktree write guard's `/tmp` and `HOME` allowlist. + +**CI** runs Ubuntu everywhere and macOS on the CLI e2e and post-release e2e matrices. No +workflow runs on Windows. **Docs** (`DEPENDENCIES.md`) say "use WSL2" with a one-line install +hint. + +## Desired State + +A Windows user on a machine where WSL 2 is blocked gets the same Codev behavior as a macOS or +Linux user, under two prerequisites: Git for Windows and jq. + +- `afx tower start` comes up. Architect, builder and shell terminals open and are usable. +- Builders are **persistent**: shellpers survive a Tower stop and restart, and Tower reconnects + to them. +- Forge concepts (all providers), porch, consult, `afx dev`, runnable worktrees and the VS Code + extension work. +- Shell roles are split: + - **PowerShell** is the interactive shell a human types into (architect and shell terminals). + - **Git Bash** is the only plumbing shell: forge concepts, the builder launch script, + `postSpawn`, and `devCommand`. +- `codev doctor` on Windows checks the Windows prerequisites and explains anything missing. +- Docs present two supported Windows paths: **native + Git Bash** and **WSL 2**. +- A Windows CI leg keeps the support from rotting. + +The macOS and Linux paths behave exactly as before. Every Windows change is a platform branch +or an abstraction whose POSIX implementation is today's code. + +## Success Criteria + +Each criterion is verified on native Windows (Windows 10 22H2 or Windows 11, x64) with WSL +disabled, unless it says otherwise. + +**Prerequisites and diagnosis** +- [ ] `codev doctor` on Windows reports three things: + - Git for Windows: found, version, and resolved bash path. + - jq: found, or not found with an install hint. + - A warning for each user-authored `postSpawn`/`devCommand` script file that has CRLF line + endings. + + It never reports System32 `bash.exe` (the WSL launcher) as Git Bash. +- [ ] Git Bash is resolved from the standard install locations, then `PATH`, then an explicit + override. Codev never invokes bare `bash`. The resolved path is exported to spawned builders + as `CLAUDE_CODE_GIT_BASH_PATH`; if the user already set that variable, Codev honors it as the + override. +- [ ] Missing Git Bash or jq fails fast with an actionable message naming the prerequisite. It + does not hang and does not fail with an obscure spawn error. + +**Tower and terminals** +- [ ] The root cause of the 2026-09-09 `afx tower start` hang is identified and recorded in the + review. `afx tower start` reaches ready, and `afx tower stop` stops it. Both work without + `lsof`/`pgrep`. +- [ ] Architect and shell terminals open in PowerShell by default. Typing, resize, scrollback, + and exit all behave correctly in the dashboard and the VS Code extension. +- [ ] `afx send` delivers onto a Windows architect or builder: it is held while the recipient is + mid-turn or a human is typing, and delivered at an empty prompt. This rests on render-gate + validation captures from real ConPTY sessions of each supported harness (at minimum claude and + codex). Gate profiles are adjusted where those captures show different timing. + +**Persistence and lifecycle (the load-bearing guarantee)** +- [ ] A spawned builder survives `afx tower stop` followed by `afx tower start`. Tower + reconnects to the same shellper with output continuity (replay), and the builder's agent + process is the same PID as before the restart. +- [ ] Reconnect still refuses to attach when the PID has been reused, and it does so on Windows + too. +- [ ] Killing a builder, a dev PTY, or `afx cleanup` leaves **no descendant processes** of the + killed session: the whole tree is gone, including `node`, `bash.exe` and harness children. +- [ ] Orphaned and husk shellpers are detected and reaped on Windows, as on POSIX. +- [ ] Shellper endpoints are not connectable or readable by other non-admin local users. + +**Launchers and forge** +- [ ] Every forge concept for every shipped provider runs on Windows through Git Bash (GitHub + exercised live; others via the same launcher plus unit coverage). This includes concepts whose + arguments are paths or slash-prefixed strings, which MSYS path conversion would otherwise + mangle. +- [ ] `afx spawn` (worktree mode and the other spawn modes) launches the builder through Git + Bash, and porch drives a SPIR/BUGFIX builder through at least one phase transition. +- [ ] `postSpawn` hooks and `afx dev` run through Git Bash with the worktree as cwd. `afx dev` + teardown leaves no orphaned dev-server process. +- [ ] Runnable-worktree links work without Developer Mode, through a documented fallback for + files and directories. When Developer Mode is on, real symlinks are used. + +**Breadth** +- [ ] `consult` (all three reviewers, with non-blocking skip semantics intact), porch checks, + `afx open`, and `afx attach` work on Windows. +- [ ] The local auth key and other owner-only files are protected on Windows by an equivalent of + the POSIX `0600` modes, not silently left world-readable. + +**Non-regression and CI** +- [ ] The macOS and Linux unit, integration, CLI e2e and post-release e2e suites pass unchanged. +- [ ] `windows-latest` runs the unit, CLI e2e, and post-release e2e workflows. It is introduced + as non-required, *pending owner confirmation at the spec gate* (see Open Questions). +- [ ] The test suite no longer hardcodes `/tmp` or POSIX-only helpers in tests that are meant to + run on Windows. Tests that are inherently POSIX-only are explicitly skipped on win32, with a + reason. + +**Docs** +- [ ] The install docs present native Windows (Git for Windows + jq, plus Node ≥ 20, 22 + recommended) and WSL 2 as the two supported Windows paths, with the known limitations of each. + The Linux/WSL docs state the `build-essential` + `python3` prerequisite (node-pty compiles from + source on Linux), which is the issue's near-term docs follow-up. + +## Constraints + +**Owner rulings (2026-10-12, issue #1658 comments). These are fixed, equivalent to Baked +Decisions. Quoted in substance:** + +1. **Protocol: SPIR. One SPIR, phased.** Not a parent spec with child lanes. The plan carries + the phases from the map: audit → transport + lifecycle → forge/launcher routing → POSIX + residue sweep → CI leg. Each phase has its own verification. +2. **Scope: full parity.** Native Windows must deliver the same guarantees as macOS/Linux: + persistent builders, detached shellpers surviving a Tower restart, runnable worktrees, forge, + consult, porch. A first cut with best-effort builders is not acceptable as the end state. +3. **Forge layer: Git Bash is a hard requirement on native Windows. No script rewrite.** + PowerShell twins and a Node port of the concept scripts are off the table. The forge phase + comes down to five things: + - Resolve the Git Bash binary. + - Route the launch sites through it. + - Translate Windows paths to bash paths for `cwd` and script arguments. + - Add `jq` and Git Bash to doctor. + - Document both as prerequisites. +4. **Shell roles on Windows.** PowerShell is the default *interactive* shell for architect and + shell terminals (`.codev/config.json`). Git Bash is the only *plumbing* shell: forge concepts, + the builder launch script, `postSpawn`. No PowerShell twins of any script. +5. **Git Bash resolution.** Resolve the Git for Windows bash explicitly (standard install paths, + then PATH, then an override setting). Never invoke bare `bash`: System32 `bash.exe` launches + WSL. Spawn sets or honors Claude Code's `CLAUDE_CODE_GIT_BASH_PATH`, so builders and Codev + agree on one binary. +6. **Prerequisites and docs.** Git for Windows and jq are hard Windows prerequisites, checked by + `codev doctor`, including a CRLF warning for user-authored `postSpawn` scripts. WSL 2 stays a + documented, supported alternative; docs present native + Git Bash and WSL 2 as two paths. +7. **Windows CI leg.** Recommended, but the owner confirms it at the spec gate: add + `windows-latest` to the unit, CLI e2e, and post-release e2e workflows. It starts as + non-required during stabilization and is then promoted to a required check. + +The ruling also named known items the spec must address: +- MSYS path translation on arguments, with a per-call opt-out. +- Signals don't cross the bash/native boundary, so Job Objects or `taskkill` are needed at the + process-group kill sites. +- A symlink fallback for runnable worktrees when Developer Mode is off. +- ConPTY render-gate validation captures. + +**Technical constraints** +- **POSIX behavior must not change.** Windows support enters through platform branches or + abstractions whose POSIX implementation is today's code, byte for byte where practical. +- **The framework tree split applies.** Changes to shipped framework content (skeleton docs, + `DEPENDENCIES.md`, config defaults written by `codev init`/`adopt`, skills) are mirrored in + `codev/` and `codev-skeleton/`. Shipped skill edits touch all four copies (`.claude` and + `.codex`, root and skeleton). +- **Mailbox invariant (Spec 1313).** All message delivery still goes through the mailbox and + render gate, and Windows does not get a bypass. Lock order stays per-agent → per-terminal. +- **Server/client isolation (#1189).** codev-core and codev-sdk do not import each other. The + sdk stays environment-agnostic, so any Windows logic lives server-side or in the sdk's + `/node` adapter. +- **One global state DB and one Tower per machine.** Unchanged on Windows. +- **Locked-down-machine target.** No requirement may depend on these unless a documented + fallback exists: + - admin rights; + - Developer Mode; + - a PowerShell *script* execution policy; + - WSL; + - installing a compiler toolchain. +- **Node ≥ 20.** Native dependencies (node-pty, better-sqlite3) must install on Windows x64 + without a local build toolchain, or the docs must state the toolchain requirement. + +## Assumptions + +- node-pty ^1.1 and better-sqlite3 ^12 ship Windows x64 prebuilt binaries for the supported + Node versions. If they don't, the docs add Visual Studio Build Tools as a prerequisite. This is + verified in the audit phase. +- Node's `net` module supports Windows named pipes (`\\.\pipe\...`) for both server and client, + and the shellper HELLO/WELCOME framing is transport-agnostic. +- Git for Windows ships `bash`, `sed`, `grep`, `head`, `tail`, `sort`, `date`, `curl` and `git`, + which covers every external tool the forge scripts use except `jq`, `gh` and `glab`. `gh` and + `glab` are separate installs on every platform already. +- Windows 10 1809+ (ConPTY) is the floor. Windows 10 22H2 and Windows 11 x64 are the tested + targets. Windows on ARM is not a tested target in this project (see Open Questions). +- The VS Code extension already branches for Windows where it touches the OS (for example + Explorer reveal). It needs only launcher fixes (`afx`/`.cmd` shims), not a redesign. +- GitHub-hosted `windows-latest` runners include Git for Windows, so CI exercises the same + Git Bash path users have. +- Claude Code honors `CLAUDE_CODE_GIT_BASH_PATH` on Windows, so a builder's own Bash tool uses + the same Git Bash Codev resolved. + +## Solution Approaches + +The owner rulings already settle the forge-layer fork (Git Bash) and the shell roles. The open +design space is in the **transport**, **process lifecycle and introspection**, and **link +fallback**. Each has competing approaches below. + +### Area A: Shellper transport + +**A1: Platform endpoint abstraction over named pipes (recommended).** +- **Endpoint.** A shellper's endpoint becomes a platform-resolved value: + - POSIX: the existing `.sock` path. + - Windows: `\\.\pipe\codev-shellper-`, namespaced by user so two users on one machine + cannot collide. +- **Framing.** HELLO/WELCOME and all frames stay unchanged. +- **File-based operations.** Each gets a Windows equivalent: + - "Wait for socket file" becomes "connect-probe until ready". + - `isSocket()` checks become endpoint probes. + - Stale cleanup is a no-op, because pipes vanish with their server. + - The attach fallback scan enumerates `\\.\pipe\` or relies on the DB registry, which is + already the primary mechanism. +- **Logs.** The per-session log moves to a real file path decoupled from the endpoint, since + today it is derived by string-replacing `.sock`. +- **Access control.** The pipe must not be readable or connectable by other users. Two options: + 1. Restrict the pipe's DACL to the current user. This needs native interop, because Node does + not expose pipe security attributes. + 2. Gate every connection on a per-session secret in HELLO. Tower generates the secret; it is + never on argv (argv is visible to other users) and is persisted in the user-private global + DB. The shellper sends nothing to an unauthenticated client. + + We recommend option 2. It needs no native code, and it also defends against an + already-connected read-only client, because the server writes nothing before HELLO is + verified. Name squatting is mitigated by unguessable UUID names and by the client verifying the + WELCOME (session id and pid). +- **Pros:** smallest conceptual change; one framing; reuses the existing persistence model. +- **Cons:** each file-based assumption needs a platform branch. +- **Risk:** medium. + +**A2: Loopback TCP with token auth on all platforms.** +- One transport everywhere. +- **Cons:** it changes the proven POSIX path (violating the non-regression constraint); uses + ports; is reachable by every local user; and needs auth everywhere. +- **Risk:** high, for no POSIX benefit. Rejected. + +**A3: Keep sessions in-process on Windows (no shellper).** +- **Cons:** violates full parity, because builders die with Tower. Rejected by ruling 2. + +### Area B: Process lifecycle (tree teardown and detached survival) + +**B1: Tree kill via `taskkill /T /F`, through the tree-kill package already in the dependency +tree, at every process-group kill site (recommended for teardown).** +- **Teardown.** A single "kill session tree" primitive replaces `process.kill(-pid, sig)`: + - POSIX keeps group signals and SIGTERM→SIGKILL escalation. + - Windows does a tree kill. +- **Graceful paths.** Where a graceful step matters, Windows uses an explicit protocol message + instead of a signal, because SIGTERM is a hard kill there. Two examples: + - shellper shutdown (an existing wire message); + - Tower stop, which needs an HTTP shutdown request so the owner lock is released. +- **Pros:** no native code. +- **Cons:** `taskkill /T` walks parent links, so a process whose parent already exited (a + re-parented grandchild) can escape. +- **Risk:** medium. + +**B2: Job Objects per session.** +- **Pros:** kernel-enforced, complete tree teardown, including re-parented descendants. +- **Cons:** needs native interop (a compiled addon or a prebuilt helper). That conflicts with + the no-toolchain constraint and adds a binary that AppLocker may block on locked-down machines. +- **Risk:** high. + +**Recommendation:** B1, with the escape case covered by the orphan/husk sweep (Area C). B2 is +the fallback if CI proves leaks that the sweep cannot catch. + +**Detached survival.** Shellpers are spawned detached so they outlive Tower. On Windows, a +detached child still outlives its parent, **unless** Tower itself runs inside a job with +kill-on-close (some terminal hosts and IDEs do this). Then the shellpers die with it, because +Node cannot request job breakaway. The spec requires the following: +- **Prove survival** under the documented launch paths: + - `afx tower start` from PowerShell / Windows Terminal; + - from the VS Code extension; + - from CI. +- **Detect** the kill-on-close-job case at Tower start and warn in doctor. + +Solving breakaway outright needs native interop (see Open Questions). + +### Area C: Process introspection (census, start time, port owner) + +POSIX uses `ps`/`pgrep`/`lsof`/`/proc`. Windows needs equivalents for four things: the process +census (pid, ppid, cmdline), process start time, the port-to-PID owner, and liveness. +`process.kill(pid, 0)` already works for liveness. + +**C1: Make identity endpoint-based, and confine introspection to sweeps (recommended).** +- **Identity.** On Windows, the reconnect identity check is the authenticated WELCOME (session + id and pid) over the pipe. The UUID-named, secret-gated endpoint *is* the identity proof, so + reconnect does not need process start time. This is stronger than a start-time comparison. +- **Tower PID.** Tower stop and start learn Tower's PID from the owner lock and from the + `/api/status` response, not from port-ownership lookups. +- **Sweeps.** The remaining census needs (orphan and husk sweeps, fleet RSS, doctor) use a + single Windows census provider. +- **Pros:** the load-bearing guarantee (reconnect) has no external-tool dependency. +- **Cons:** two identity mechanisms (start time on POSIX, handshake on Windows). +- **Risk:** low to medium. + +**C2: Port the existing introspection one-for-one.** +- Every call keeps its current shape, with Windows command equivalents. +- **Cons:** reconnect and Tower stop would then depend on a PowerShell/CIM call or a bundled + binary on the hot path. +- **Risk:** medium. + +**The census provider itself** has two options, and the choice is an open question for the +owner: +1. A `powershell.exe -NoProfile -NonInteractive -Command` CIM query. Inline commands are not + governed by script execution policy, but Constrained Language Mode or AppLocker may still + apply. +2. A dependency that bundles a small native census binary. + +Note that `wmic` is removed on current Windows 11, and Git Bash's MSYS `ps` cannot see native +command lines. + +### Area D: Launchers, Git Bash, and path translation + +Every plumbing launch site routes through one resolved Git Bash binary: +- forge concepts (sync and async); +- the builder launch script; +- `postSpawn`; +- `devCommand`; +- porch check commands that are shell strings. + +**Resolution** follows ruling 5: install paths, then PATH, then override, with System32 excluded. + +**Paths.** +- `cwd` is passed as a native Windows path; Git Bash accepts it. +- Arguments that embed Windows paths are converted to POSIX form where bash needs them. +- MSYS automatic argument conversion is disabled per call, through its opt-out environment + variables, where arguments must pass through unmangled (for example slash-prefixed API paths + or `/repos/...` strings given to `gh api`). + +**Executable shims.** The `.cmd`/`.exe` shims that commands like `claude` and `afx` become on +Windows are resolved through `PATHEXT` before spawn, both for PTY commands and for the VS Code +extension's `afx` calls. + +**Considered and rejected:** running each launch site with `shell: ` piecemeal, without +a shared resolver. That spreads the System32-WSL trap across every site. + +### Area E: Runnable-worktree links without Developer Mode + +**E1 (recommended).** +- **Directories:** use a junction (no privilege needed). +- **Files:** use a hard link when on the same volume, otherwise copy. +- Real symlinks are used when the probe shows they are allowed (Developer Mode or admin). +- A copy is reported in spawn output, because it does not track later edits to the source. + +**E2: Require Developer Mode.** +- Violates the locked-down-machine target. Rejected. + +### Area F: Interactive shell default + +On Windows the default for `shell.shell`, and the dashboard and `afx shell` fallback, is +PowerShell. We prefer `pwsh` (PowerShell 7) if installed and fall back to `powershell.exe` +(5.1, always present). Architect and builder commands are unchanged (`claude`), apart from shim +resolution. + +An existing `.codev/config.json` carrying `shell: "bash"` (today's scaffold default) is +interpreted on Windows as **Git Bash**, never as System32 `bash.exe`, so existing configs cannot +launch WSL. + +## Open Questions + +**Critical (blocks the plan)** +1. **Windows CI leg commitment** (ruling 7 is "recommended, owner confirms at the spec gate"). + Does the owner confirm `windows-latest` on unit, CLI e2e, and post-release e2e, introduced + non-required and promoted to required after stabilization? What is the promotion criterion? + We propose N consecutive green runs on main without a Windows-only flake. +2. **Process census provider (Area C).** Ruling 4 makes Git Bash "the only plumbing shell" for + scripts. Is an inline `powershell.exe -Command` CIM query, used only for the sweep census and + not as a script, acceptable under that ruling? Or does the owner prefer a bundled census + binary, or a degraded mode where Windows sweeps rely only on DB-registered PIDs plus tree + kill? The recommended C1 keeps this off the reconnect hot path either way. + +**Important (shapes the design)** +3. **Job breakaway.** If the documented launch paths show Tower running inside a kill-on-close + job (so shellpers die with Tower), is detect-and-warn enough, or must the project ship native + interop (a CREATE_BREAKAWAY_FROM_JOB launcher) to keep the parity guarantee there? +4. **PowerShell flavor.** Should we prefer `pwsh` 7 when present and fall back to Windows + PowerShell 5.1, or always use 5.1 for predictability? (Proposed: prefer `pwsh`.) +5. **Windows on ARM64.** Out of scope for this project (documented as untested), or a supported + target? +6. **Pipe access control.** Is the per-session HELLO secret (A1, option 2) acceptable as the + owner-only guarantee, or does the owner require OS-level pipe ACLs (native interop)? +7. **Owner-only files** (local key, cloud config). Is setting an explicit current-user-only ACL + at creation time required, or is reliance on the default per-user profile ACL (`%USERPROFILE%` + is already private to the user on default installs) acceptable, documented as such? + +**Nice-to-know** +8. Should doctor also report Developer Mode, long-path support, and `core.autocrlf`, so the user + knows ahead of time which link mode and line-ending behavior they will get? +9. Should `afx tower start` on Windows hide the daemon console window (`windowsHide`), which + matters for UX in the VS Code extension and the CLI? + +## Test Scenarios + +**Functional (on Windows unless noted)** +1. A fresh native install (Git for Windows, jq, Node 22, `npm install -g @cluesmith/codev`) + gives a `codev doctor` with all-green Windows prerequisites, and `codev init` creates a + project. +2. Doctor with jq missing, and doctor with only System32 `bash.exe` present: each gives a clear + failure naming the missing prerequisite. The WSL launcher is not accepted. +3. `afx tower start` reaches ready; the dashboard loads; the architect terminal is PowerShell + (or Claude, per config) and accepts input; resize reflows; `afx tower stop` exits cleanly and + releases the owner lock. +4. **Persistence:** + - Spawn a builder. + - Record the builder agent's PID. + - Run `afx tower stop`, then `afx tower start`. + - Expected: the builder terminal reconnects with replayed output, its PID is unchanged, and + input still works. +5. **PID reuse:** make a registered shellper's PID belong to a different process, or put a stale + DB row in place. Reconnect must refuse, and the row must be cleaned. +6. **Teardown:** kill a builder; `afx cleanup` a builder; stop `afx dev` on a dev server that + spawns children. In each case, no descendant processes survive. +7. **Orphan sweep:** an unregistered shellper (for example after a Tower crash) is detected and + reaped at the next start. +8. **Forge:** run every GitHub concept live, including ones passing `/repos/...`-style arguments. + Run GitLab/Gitea/Linear concepts through the same launcher in unit tests with stubbed CLIs. +9. **Spawn → porch:** a BUGFIX builder spawned on Windows runs `porch next`/`porch done` through + a phase transition and opens a PR via the forge. +10. **postSpawn** with a multi-command bash hook; also with a CRLF hook file, where doctor warns + and the hook either still runs or fails with a clear message. +11. **Runnable worktree:** + - Developer Mode off: a directory link becomes a junction and a file link becomes a hard + link or copy (reported). + - Developer Mode on: real symlinks. +12. **`afx send`:** + - to a busy Windows builder: held; + - at an idle prompt: delivered; + - while a human is typing in the composer: held. +13. **Consult:** a 3-way consult runs from a Windows builder; an unauthenticated agy skips + without blocking. +14. **Access control:** a second local non-admin user cannot connect to, or read from, a + shellper pipe. +15. **VS Code extension:** start Tower, spawn a builder, open its terminal, and reveal a file, + all on Windows. + +**Non-functional** +16. The macOS and Linux suites pass unchanged, so there is no POSIX regression. +17. Windows CI leg stability: track the flake rate over the stabilization window. A Windows-only + flaky test is annotated, not silently retried. +18. Render-gate captures: ConPTY capture fixtures for each validated harness are checked in and + replayed by unit tests, the same way existing gate captures are. + +## Risks and Mitigation + +| Risk | Probability | Impact | Mitigation | +|------|-------------|--------|------------| +| Shellpers die with Tower when Tower runs inside a kill-on-close job (terminal host or IDE) | Medium | High (breaks the persistence guarantee) | Prove survival on each documented launch path; detect the job at start and warn; native breakaway launcher held in reserve (OQ 3) | +| `taskkill /T` misses re-parented descendants, leaking processes | Medium | Medium | Orphan/husk sweep on Windows; teardown tests assert no survivors; Job Objects as fallback (Area B2) | +| ConPTY timing differs from POSIX PTYs, so the render gate misclassifies (lost or force-held mail) | High | High | Assume nothing (#1567 lesson): capture real ConPTY sessions per harness, replay them as fixtures, adjust gate profiles from the data | +| MSYS path conversion mangles forge arguments | High | Medium | Per-call opt-out of automatic conversion; explicit translation helper; concept-by-concept tests with slash-prefixed arguments | +| System32 `bash.exe` invoked by accident, launching WSL or failing on WSL-blocked machines | Medium | High | Single resolver; never bare `bash`; doctor rejects System32; config `shell: bash` maps to Git Bash on Windows | +| Locked-down policy (AppLocker, Constrained Language Mode) blocks the census provider or a bundled binary | Medium | Medium | Keep introspection off the reconnect hot path (C1); sweeps degrade to DB-registered PIDs if the provider fails | +| Pipe readable or connectable by other local users | Low | High | Per-session HELLO secret, kept off argv; server sends nothing pre-auth; UUID pipe names; client verifies WELCOME | +| Native deps lack Windows prebuilds for a Node version | Low | Medium | Verify in the audit phase; document the toolchain fallback; CI leg catches regressions | +| Windows CI leg is flaky or slow, so it gets ignored and support rots | High | High | Non-required during stabilization with an explicit promotion criterion; flaky tests annotated per protocol; required check after promotion | +| A platform branch changes POSIX behavior | Low | High | POSIX implementations stay today's code; full macOS/Linux suites gate every phase | +| CRLF line endings (Git `autocrlf`) break bash scripts or output parsing | Medium | Medium | `.gitattributes` `eol=lf` for shipped `.sh` scripts; CRLF-tolerant output parsing; doctor CRLF warning for user scripts | + +## References + +- Issue #1658, including the owner rulings of 2026-10-12 (two comments) +- Spec 1313 (mailbox-first `afx send`, render gate); #1365 (submit lock); #1473 (input settle); + #1567 (assume nothing about terminal timing); #1573, #1664 (gate profiles, composer-region + stability) +- #1645 (forge scripts are POSIX-clean, pipefail note); #1189 (server/client isolation) +- `codev/DEPENDENCIES.md` (current "use WSL2" guidance) +- `codev/resources/lessons-learned.md` [From 0005]: prefer Node built-ins over shell commands for + cross-platform safety +- Claude Code on Windows: Git Bash for its Bash tool, honoring `CLAUDE_CODE_GIT_BASH_PATH` diff --git a/codev/state/spir-1658_thread.md b/codev/state/spir-1658_thread.md new file mode 100644 index 0000000000..cf5c021866 --- /dev/null +++ b/codev/state/spir-1658_thread.md @@ -0,0 +1,18 @@ +# spir-1658 — native (non-WSL) Windows support + +## Context +Issue #1658. Started as a tracking map; owner rulings on 2026-10-12 (two issue comments) +turned it into a build: SPIR, one spec, phased, **full parity** with macOS/Linux. + +Owner rulings treated as fixed (Baked-Decision equivalent, though not under that heading): +- Git Bash (Git for Windows) is a hard prerequisite and the ONLY plumbing shell (forge + concepts, builder launch script, postSpawn). No PowerShell twins, no Node port of scripts. +- PowerShell is the default interactive shell for architect/shell terminals on Windows. +- Resolve Git Bash explicitly (install paths → PATH → override); never bare `bash` + (System32 bash.exe = WSL). Set/honor `CLAUDE_CODE_GIT_BASH_PATH`. +- Git for Windows + jq are hard prereqs checked by `codev doctor` (+ CRLF warning for + user postSpawn scripts). WSL 2 stays documented as a supported alternative. +- Windows CI leg: recommended non-required → required; owner confirms at spec gate. + +## Progress +- [in progress] Specify — auditing codebase (transport/lifecycle, launchers/residue) to ground the spec. From 2fae3e169082577e3b5be843bfe57bb7357fa49e Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Mon, 12 Oct 2026 08:21:56 +1100 Subject: [PATCH 003/245] chore(porch): 1658 specify build-complete --- .../projects/1658-windows-native-non-wsl-support/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/1658-windows-native-non-wsl-support/status.yaml b/codev/projects/1658-windows-native-non-wsl-support/status.yaml index f4f7273115..0ee280a9a9 100644 --- a/codev/projects/1658-windows-native-non-wsl-support/status.yaml +++ b/codev/projects/1658-windows-native-non-wsl-support/status.yaml @@ -14,7 +14,7 @@ gates: verify-approval: status: pending iteration: 1 -build_complete: false +build_complete: true history: [] started_at: '2026-10-11T21:15:51.801Z' -updated_at: '2026-10-11T21:15:51.801Z' +updated_at: '2026-10-11T21:21:56.378Z' From b988964f67fbbff9482a183a50ff3bdab959b472 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Mon, 12 Oct 2026 08:28:36 +1100 Subject: [PATCH 004/245] [Spec 1658] Specification with multi-agent review --- .../1658-windows-native-non-wsl-support.md | 175 +++++++++++++----- 1 file changed, 132 insertions(+), 43 deletions(-) diff --git a/codev/specs/1658-windows-native-non-wsl-support.md b/codev/specs/1658-windows-native-non-wsl-support.md index 94355e2e5a..143a7cb431 100644 --- a/codev/specs/1658-windows-native-non-wsl-support.md +++ b/codev/specs/1658-windows-native-non-wsl-support.md @@ -87,7 +87,8 @@ Linux user, under two prerequisites: Git for Windows and jq. - Forge concepts (all providers), porch, consult, `afx dev`, runnable worktrees and the VS Code extension work. - Shell roles are split: - - **PowerShell** is the interactive shell a human types into (architect and shell terminals). + - **PowerShell** is the interactive shell a human types into (shell terminals). Agent + terminals run their harness directly, as on POSIX. - **Git Bash** is the only plumbing shell: forge concepts, the builder launch script, `postSpawn`, and `devCommand`. - `codev doctor` on Windows checks the Windows prerequisites and explains anything missing. @@ -121,8 +122,14 @@ disabled, unless it says otherwise. - [ ] The root cause of the 2026-09-09 `afx tower start` hang is identified and recorded in the review. `afx tower start` reaches ready, and `afx tower stop` stops it. Both work without `lsof`/`pgrep`. -- [ ] Architect and shell terminals open in PowerShell by default. Typing, resize, scrollback, - and exit all behave correctly in the dashboard and the VS Code extension. +- [ ] Terminal roles are distinct: + - **Agent terminals** (architect, builder) run the configured harness command (`claude` by + default) directly, as on POSIX, with `.cmd`/`.exe` shims resolved. + - **Human shell terminals** (`afx shell`, the dashboard's "new shell", and any terminal that + needs a host shell) open in PowerShell by default. + + Typing, resize, scrollback, and exit all behave correctly in the dashboard and the VS Code + extension. - [ ] `afx send` delivers onto a Windows architect or builder: it is held while the recipient is mid-turn or a human is typing, and delivered at an empty prompt. This rests on render-gate validation captures from real ConPTY sessions of each supported harness (at minimum claude and @@ -136,10 +143,23 @@ disabled, unless it says otherwise. too. - [ ] Killing a builder, a dev PTY, or `afx cleanup` leaves **no descendant processes** of the killed session: the whole tree is gone, including `node`, `bash.exe` and harness children. -- [ ] Orphaned and husk shellpers are detected and reaped on Windows, as on POSIX. -- [ ] Shellper endpoints are not connectable or readable by other non-admin local users. + This holds even when an intermediate process exited before teardown and its children were + re-parented, and when the session root is already dead. +- [ ] `afx cleanup` actually removes the worktree directory on Windows despite transient open + handles (antivirus, indexer, a just-killed process's cwd), or fails with a clear error naming + the blocking path. Removal deletes junction/link entries without traversing into or deleting + their targets. +- [ ] Orphaned and husk shellpers are detected and reaped on Windows, as on POSIX, including the + process-age race guard the POSIX sweep uses. +- [ ] Other non-admin local users cannot **read session data from, or control,** a shellper. An + unauthenticated connection receives zero bytes, and every frame it sends is ignored before the + connection is dropped. Whether OS-level *non-connectability* is also required is Open + Question 6. **Launchers and forge** +- [ ] Paths flow correctly in both directions across the Git Bash boundary: Windows paths passed + into bash (`cwd`, arguments) and MSYS-form paths emitted by bash/git (`/c/Users/...`) parsed + back into native paths before Node uses them. - [ ] Every forge concept for every shipped provider runs on Windows through Git Bash (GitHub exercised live; others via the same launcher plus unit coverage). This includes concepts whose arguments are paths or slash-prefixed strings, which MSYS path conversion would otherwise @@ -150,12 +170,20 @@ disabled, unless it says otherwise. teardown leaves no orphaned dev-server process. - [ ] Runnable-worktree links work without Developer Mode, through a documented fallback for files and directories. When Developer Mode is on, real symlinks are used. +- [ ] A workspace at a typical depth (for example `C:\Users\\src\`) supports builder + worktrees and their dependency trees without the admin-only `LongPathsEnabled` setting. Doctor + reports when the workspace root is deep enough to risk MAX_PATH overruns, and whether Git's + per-user `core.longpaths` is set. **Breadth** - [ ] `consult` (all three reviewers, with non-blocking skip semantics intact), porch checks, `afx open`, and `afx attach` work on Windows. - [ ] The local auth key and other owner-only files are protected on Windows by an equivalent of the POSIX `0600` modes, not silently left world-readable. +- [ ] On a supported Node version (≥ 20, x64), `npm install -g @cluesmith/codev` succeeds on + Windows without a C/C++ toolchain. If a native dependency lacks a Windows prebuild for the + user's Node version, install or first run fails with a message naming the supported Node + versions. It never silently falls back to a source build. **Non-regression and CI** - [ ] The macOS and Linux unit, integration, CLI e2e and post-release e2e suites pass unchanged. @@ -237,13 +265,14 @@ The ruling also named known items the spec must address: ## Assumptions - node-pty ^1.1 and better-sqlite3 ^12 ship Windows x64 prebuilt binaries for the supported - Node versions. If they don't, the docs add Visual Studio Build Tools as a prerequisite. This is - verified in the audit phase. + Node versions. This is verified in the audit phase. The supported Windows Node versions are + defined as those with prebuilds; a compiler toolchain is never a prerequisite, because that + contradicts the locked-down target. - Node's `net` module supports Windows named pipes (`\\.\pipe\...`) for both server and client, and the shellper HELLO/WELCOME framing is transport-agnostic. - Git for Windows ships `bash`, `sed`, `grep`, `head`, `tail`, `sort`, `date`, `curl` and `git`, - which covers every external tool the forge scripts use except `jq`, `gh` and `glab`. `gh` and - `glab` are separate installs on every platform already. + which covers every external tool the forge scripts use except `jq` and the provider CLIs `gh`, + `glab` and `tea`. The provider CLIs are separate installs on every platform already. - Windows 10 1809+ (ConPTY) is the floor. Windows 10 22H2 and Windows 11 x64 are the tested targets. Windows on ARM is not a tested target in this project (see Open Questions). - The VS Code extension already branches for Windows where it touches the OS (for example @@ -252,6 +281,16 @@ The ruling also named known items the spec must address: Git Bash path users have. - Claude Code honors `CLAUDE_CODE_GIT_BASH_PATH` on Windows, so a builder's own Bash tool uses the same Git Bash Codev resolved. +- Windows' 260-character MAX_PATH limit can only be lifted machine-wide with admin rights + (`LongPathsEnabled`), so the target machines are assumed to have it **off**. Node's own `fs` + handles long paths internally. Git needs the per-user (no-admin) `core.longpaths=true`. Other + tools in the chain (npm installs inside a worktree, some harness CLIs) may not cope, so worktree + depth must stay within MAX_PATH by construction or be diagnosed. +- Ruling 4's "PowerShell is the default interactive shell for architect and shell terminals" is + read as follows. The `.codev/config.json` *shell* role defaults to PowerShell on Windows. Agent + terminals still run their configured harness command directly (`shell.architect: claude`), as + they do on POSIX. A terminal that needs a host shell uses PowerShell. This is confirmed at the + spec gate (Open Question 10). ## Solution Approaches @@ -271,6 +310,8 @@ fallback**. Each has competing approaches below. - "Wait for socket file" becomes "connect-probe until ready". - `isSocket()` checks become endpoint probes. - Stale cleanup is a no-op, because pipes vanish with their server. + - Every place that derives identity from the endpoint string, such as parsing the session id + out of the `shellper-.sock` basename, reads it from a platform-neutral source instead. - The attach fallback scan enumerates `\\.\pipe\` or relies on the DB registry, which is already the primary mechanism. - **Logs.** The per-session log moves to a real file path decoupled from the endpoint, since @@ -278,14 +319,17 @@ fallback**. Each has competing approaches below. - **Access control.** The pipe must not be readable or connectable by other users. Two options: 1. Restrict the pipe's DACL to the current user. This needs native interop, because Node does not expose pipe security attributes. - 2. Gate every connection on a per-session secret in HELLO. Tower generates the secret; it is - never on argv (argv is visible to other users) and is persisted in the user-private global - DB. The shellper sends nothing to an unauthenticated client. + 2. Gate every connection on a per-session secret in HELLO. Tower generates the secret and + passes it to the shellper through its environment or stdin, never on argv (argv is visible + to other users through process queries). It is persisted in the user-private global DB for + reconnects. The shellper sends nothing to an unauthenticated client. We recommend option 2. It needs no native code, and it also defends against an already-connected read-only client, because the server writes nothing before HELLO is verified. Name squatting is mitigated by unguessable UUID names and by the client verifying the - WELCOME (session id and pid). + WELCOME (session id and pid). Under the secret gate another user *can* open a handle to the + pipe but gets nothing from it. True non-connectability needs a restrictive DACL (Open + Question 6). - **Pros:** smallest conceptual change; one framing; reuses the existing persistence model. - **Cons:** each file-based assumption needs a platform branch. - **Risk:** medium. @@ -309,10 +353,17 @@ tree, at every process-group kill site (recommended for teardown).** - **Graceful paths.** Where a graceful step matters, Windows uses an explicit protocol message instead of a signal, because SIGTERM is a hard kill there. Two examples: - shellper shutdown (an existing wire message); - - Tower stop, which needs an HTTP shutdown request so the owner lock is released. + - Tower stop, through an HTTP shutdown request so the daemon can clean up. The owner lock + already self-clears on a dead PID at the next start, so the hard-kill fallback degrades + safely and needs no extra machinery. - **Pros:** no native code. - **Cons:** `taskkill /T` walks parent links, so a process whose parent already exited (a - re-parented grandchild) can escape. + re-parented grandchild) can escape. If the root itself is already dead, `taskkill /T` fails + outright and walks nothing. +- **Closing the gap.** Teardown takes a descendant snapshot from the census (pid/ppid/creation + time) *before* killing. It then kills each recorded descendant explicitly, using creation time + to avoid killing a reused PID. So "no descendants survive" does not rest on `taskkill`'s walk + alone. The orphan/husk sweep is the backstop for anything spawned after the snapshot. - **Risk:** medium. **B2: Job Objects per session.** @@ -321,20 +372,24 @@ tree, at every process-group kill site (recommended for teardown).** the no-toolchain constraint and adds a binary that AppLocker may block on locked-down machines. - **Risk:** high. -**Recommendation:** B1, with the escape case covered by the orphan/husk sweep (Area C). B2 is -the fallback if CI proves leaks that the sweep cannot catch. +**Recommendation:** B1 plus a census snapshot. B2 is the fallback if CI proves leaks the snapshot +and sweep cannot catch. **Detached survival.** Shellpers are spawned detached so they outlive Tower. On Windows, a detached child still outlives its parent, **unless** Tower itself runs inside a job with kill-on-close (some terminal hosts and IDEs do this). Then the shellpers die with it, because Node cannot request job breakaway. The spec requires the following: -- **Prove survival** under the documented launch paths: +- **Survival is required, not best-effort**, on every documented launch path: - `afx tower start` from PowerShell / Windows Terminal; - from the VS Code extension; - from CI. -- **Detect** the kill-on-close-job case at Tower start and warn in doctor. - -Solving breakaway outright needs native interop (see Open Questions). +- **If Tower detects it is inside a kill-on-close job**, that launch context cannot keep the + persistence guarantee. A warning alone is not parity. The two candidate behaviors are Open + Question 3: + 1. **Relaunch outside the job.** A process created through WMI's `Win32_Process.Create` or a + one-shot scheduled task is parented outside the caller's job, with no native code. + 2. **Refuse.** Refuse to start Tower in that context, with an actionable error naming a + supported launch path. ### Area C: Process introspection (census, start time, port owner) @@ -348,8 +403,15 @@ census (pid, ppid, cmdline), process start time, the port-to-PID owner, and live reconnect does not need process start time. This is stronger than a start-time comparison. - **Tower PID.** Tower stop and start learn Tower's PID from the owner lock and from the `/api/status` response, not from port-ownership lookups. -- **Sweeps.** The remaining census needs (orphan and husk sweeps, fleet RSS, doctor) use a - single Windows census provider. +- **Sweeps.** The remaining census needs (orphan and husk sweeps, the teardown snapshot in Area + B, fleet RSS, doctor) use a single Windows census provider. The provider must return **pid, + ppid, command line, and creation time**. The POSIX husk sweep and the registered-PID set both + *skip* any process whose start time cannot be read, so a provider without creation time makes + the Windows sweep a silent no-op. CIM's `Win32_Process` supplies all four fields. +- **No degraded mode at parity.** The census provider is load-bearing for the "no descendants" + and "orphans reaped" criteria. If it is unavailable (for example blocked by policy), that is a + reported failure: doctor flags it, and Tower logs that sweeps are disabled. It is not a + supported configuration. - **Pros:** the load-bearing guarantee (reconnect) has no external-tool dependency. - **Cons:** two identity mechanisms (start time on POSIX, handshake on Windows). - **Risk:** low to medium. @@ -361,7 +423,7 @@ census (pid, ppid, cmdline), process start time, the port-to-PID owner, and live - **Risk:** medium. **The census provider itself** has two options, and the choice is an open question for the -owner: +owner. Both must satisfy the field requirements above. 1. A `powershell.exe -NoProfile -NonInteractive -Command` CIM query. Inline commands are not governed by script execution policy, but Constrained Language Mode or AppLocker may still apply. @@ -388,7 +450,12 @@ Every plumbing launch site routes through one resolved Git Bash binary: variables, where arguments must pass through unmangled (for example slash-prefixed API paths or `/repos/...` strings given to `gh api`). -**Executable shims.** The `.cmd`/`.exe` shims that commands like `claude` and `afx` become on +**Output paths.** Paths that bash or git print in MSYS form (`/c/...`) are converted back to +native form before Node consumes them. Otherwise Node reads `/c/...` as a drive-relative +`C:\c\...`. + +**Executable shims.** ConPTY spawns through `CreateProcess`, which cannot execute a `.cmd`/`.bat` +file directly. The `.cmd`/`.exe` shims that commands like `claude` and `afx` become on Windows are resolved through `PATHEXT` before spawn, both for PTY commands and for the VS Code extension's `afx` calls. @@ -402,6 +469,10 @@ a shared resolver. That spreads the System32-WSL trap across every site. - **Files:** use a hard link when on the same volume, otherwise copy. - Real symlinks are used when the probe shows they are allowed (Developer Mode or admin). - A copy is reported in spawn output, because it does not track later edits to the source. +- Worktree removal deletes the link entries themselves and never recurses through a junction + into its target. +- Worktree removal on Windows retries with backoff on EBUSY/EPERM from transient handles after + the session tree is killed, and reports the blocking path if it still fails. **E2: Require Developer Mode.** - Violates the locked-down-machine target. Rejected. @@ -424,16 +495,19 @@ launch WSL. Does the owner confirm `windows-latest` on unit, CLI e2e, and post-release e2e, introduced non-required and promoted to required after stabilization? What is the promotion criterion? We propose N consecutive green runs on main without a Windows-only flake. -2. **Process census provider (Area C).** Ruling 4 makes Git Bash "the only plumbing shell" for - scripts. Is an inline `powershell.exe -Command` CIM query, used only for the sweep census and - not as a script, acceptable under that ruling? Or does the owner prefer a bundled census - binary, or a degraded mode where Windows sweeps rely only on DB-registered PIDs plus tree - kill? The recommended C1 keeps this off the reconnect hot path either way. +2. **Process census provider (Area C).** The provider must return pid, ppid, command line *and + creation time*, or the Windows husk sweep and teardown snapshot become no-ops (see Area C). + Ruling 4 makes Git Bash "the only plumbing shell" for scripts. Is an inline + `powershell.exe -Command` CIM query, used only for the census and not as a script, acceptable + under that ruling? Or does the owner prefer a bundled census binary? A third option, "DB-registered PIDs plus `taskkill` only", **forfeits** the husk sweep + and the re-parented-descendant guarantee, so it does not meet full parity. The recommended C1 + keeps the census off the reconnect hot path either way. **Important (shapes the design)** -3. **Job breakaway.** If the documented launch paths show Tower running inside a kill-on-close - job (so shellpers die with Tower), is detect-and-warn enough, or must the project ship native - interop (a CREATE_BREAKAWAY_FROM_JOB launcher) to keep the parity guarantee there? +3. **Kill-on-close job context.** If Tower is launched inside a kill-on-close job (so shellpers + would die with it), should Codev relaunch Tower outside the job (WMI `Win32_Process.Create` + or a one-shot scheduled task, both without native code but each subject to policy), or refuse + to start with an actionable error? Detect-and-warn alone does not meet parity. 4. **PowerShell flavor.** Should we prefer `pwsh` 7 when present and fall back to Windows PowerShell 5.1, or always use 5.1 for predictability? (Proposed: prefer `pwsh`.) 5. **Windows on ARM64.** Out of scope for this project (documented as untested), or a supported @@ -445,11 +519,16 @@ launch WSL. is already private to the user on default installs) acceptable, documented as such? **Nice-to-know** -8. Should doctor also report Developer Mode, long-path support, and `core.autocrlf`, so the user - knows ahead of time which link mode and line-ending behavior they will get? +8. Should doctor also report Developer Mode and `core.autocrlf`, so the user knows ahead of time + which link mode and line-ending behavior they will get? (Long paths are now a success + criterion.) 9. Should `afx tower start` on Windows hide the daemon console window (`windowsHide`), which matters for UX in the VS Code extension and the CLI? +**Important (confirm at the spec gate)** +10. **Ruling 4 interpretation.** Is the reading in Assumptions right? Agent terminals run the + harness directly; the *shell* role and any host-shell need default to PowerShell. + ## Test Scenarios **Functional (on Windows unless noted)** @@ -458,8 +537,8 @@ launch WSL. project. 2. Doctor with jq missing, and doctor with only System32 `bash.exe` present: each gives a clear failure naming the missing prerequisite. The WSL launcher is not accepted. -3. `afx tower start` reaches ready; the dashboard loads; the architect terminal is PowerShell - (or Claude, per config) and accepts input; resize reflows; `afx tower stop` exits cleanly and +3. `afx tower start` reaches ready; the dashboard loads; the architect terminal runs the + configured harness and accepts input; `afx shell` opens PowerShell; resize reflows; `afx tower stop` exits cleanly and releases the owner lock. 4. **Persistence:** - Spawn a builder. @@ -470,7 +549,14 @@ launch WSL. 5. **PID reuse:** make a registered shellper's PID belong to a different process, or put a stale DB row in place. Reconnect must refuse, and the row must be cleaned. 6. **Teardown:** kill a builder; `afx cleanup` a builder; stop `afx dev` on a dev server that - spawns children. In each case, no descendant processes survive. + spawns children. In each case, no descendant processes survive. Also: an intermediate + process exits first (re-parenting its children), and the session root is already dead when + teardown runs. No descendants survive in either case. +6a. **Worktree removal:** `afx cleanup` succeeds while a just-killed process briefly holds a + handle in the worktree. A worktree containing junction links is removed without touching the + junction targets. +6b. **Long paths:** a workspace under `C:\Users\\src\` with `LongPathsEnabled` off + spawns a builder whose worktree runs `npm install`. 7. **Orphan sweep:** an unregistered shellper (for example after a Tower crash) is detected and reaped at the next start. 8. **Forge:** run every GitHub concept live, including ones passing `/repos/...`-style arguments. @@ -489,8 +575,8 @@ launch WSL. - while a human is typing in the composer: held. 13. **Consult:** a 3-way consult runs from a Windows builder; an unauthenticated agy skips without blocking. -14. **Access control:** a second local non-admin user cannot connect to, or read from, a - shellper pipe. +14. **Access control:** a second local non-admin user who opens a shellper pipe receives zero + bytes, and frames it sends have no effect. 15. **VS Code extension:** start Tower, spawn a builder, open its terminal, and reveal a file, all on Windows. @@ -505,14 +591,17 @@ launch WSL. | Risk | Probability | Impact | Mitigation | |------|-------------|--------|------------| -| Shellpers die with Tower when Tower runs inside a kill-on-close job (terminal host or IDE) | Medium | High (breaks the persistence guarantee) | Prove survival on each documented launch path; detect the job at start and warn; native breakaway launcher held in reserve (OQ 3) | -| `taskkill /T` misses re-parented descendants, leaking processes | Medium | Medium | Orphan/husk sweep on Windows; teardown tests assert no survivors; Job Objects as fallback (Area B2) | +| Shellpers die with Tower when Tower runs inside a kill-on-close job (terminal host or IDE) | Medium | High (breaks the persistence guarantee) | Survival required on each documented launch path; detect the job at start and relaunch outside it or refuse (OQ 3); never a silent warning | +| `taskkill /T` misses re-parented descendants, or walks nothing when the root is already dead | Medium | Medium | Pre-kill census snapshot with explicit per-PID kills; orphan/husk sweep as backstop; teardown tests assert no survivors; Job Objects as fallback (Area B2) | | ConPTY timing differs from POSIX PTYs, so the render gate misclassifies (lost or force-held mail) | High | High | Assume nothing (#1567 lesson): capture real ConPTY sessions per harness, replay them as fixtures, adjust gate profiles from the data | | MSYS path conversion mangles forge arguments | High | Medium | Per-call opt-out of automatic conversion; explicit translation helper; concept-by-concept tests with slash-prefixed arguments | | System32 `bash.exe` invoked by accident, launching WSL or failing on WSL-blocked machines | Medium | High | Single resolver; never bare `bash`; doctor rejects System32; config `shell: bash` maps to Git Bash on Windows | | Locked-down policy (AppLocker, Constrained Language Mode) blocks the census provider or a bundled binary | Medium | Medium | Keep introspection off the reconnect hot path (C1); sweeps degrade to DB-registered PIDs if the provider fails | | Pipe readable or connectable by other local users | Low | High | Per-session HELLO secret, kept off argv; server sends nothing pre-auth; UUID pipe names; client verifies WELCOME | -| Native deps lack Windows prebuilds for a Node version | Low | Medium | Verify in the audit phase; document the toolchain fallback; CI leg catches regressions | +| Native deps lack Windows prebuilds for a Node version | Low | Medium | Verify in the audit phase; supported Node versions are defined as those with prebuilds; clear failure otherwise (no toolchain fallback); CI leg catches regressions | +| Worktree removal fails on open handles (antivirus, indexer, lingering cwd) | High | Medium | Kill the session tree first; retry with backoff; report the blocking path; never recurse through junctions | +| MAX_PATH overruns in nested `.builders//` plus dependency trees (raising the limit needs admin) | Medium | High | Doctor depth check and `core.longpaths` check; docs recommend a short workspace root; keep Codev-generated paths short | +| `.cmd` shims cannot be spawned directly under ConPTY | High | High | Resolve shims through `PATHEXT` and launch them through `%COMSPEC% /c` or the underlying Node entry point | | Windows CI leg is flaky or slow, so it gets ignored and support rots | High | High | Non-required during stabilization with an explicit promotion criterion; flaky tests annotated per protocol; required check after promotion | | A platform branch changes POSIX behavior | Low | High | POSIX implementations stay today's code; full macOS/Linux suites gate every phase | | CRLF line endings (Git `autocrlf`) break bash scripts or output parsing | Medium | Medium | `.gitattributes` `eol=lf` for shipped `.sh` scripts; CRLF-tolerant output parsing; doctor CRLF warning for user scripts | From 006e3d2139c25c544d2a839cab1bab9586152abc Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Mon, 12 Oct 2026 08:28:54 +1100 Subject: [PATCH 005/245] chore(porch): 1658 spec-approval gate-requested --- codev/projects/1658-windows-native-non-wsl-support/status.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codev/projects/1658-windows-native-non-wsl-support/status.yaml b/codev/projects/1658-windows-native-non-wsl-support/status.yaml index 0ee280a9a9..1407161d86 100644 --- a/codev/projects/1658-windows-native-non-wsl-support/status.yaml +++ b/codev/projects/1658-windows-native-non-wsl-support/status.yaml @@ -7,6 +7,7 @@ current_plan_phase: null gates: spec-approval: status: pending + requested_at: '2026-10-11T21:28:54.199Z' plan-approval: status: pending pr: @@ -17,4 +18,4 @@ iteration: 1 build_complete: true history: [] started_at: '2026-10-11T21:15:51.801Z' -updated_at: '2026-10-11T21:21:56.378Z' +updated_at: '2026-10-11T21:28:54.200Z' From 149d5ecb84a4beef189dc00056aa750c05a970c9 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Mon, 12 Oct 2026 08:29:02 +1100 Subject: [PATCH 006/245] [Spec 1658] Thread log + iter1 rebuttal --- .../1658-specify-iter1-rebuttals.md | 66 +++++++++++++++++++ codev/state/spir-1658_thread.md | 9 ++- 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 codev/projects/1658-windows-native-non-wsl-support/1658-specify-iter1-rebuttals.md diff --git a/codev/projects/1658-windows-native-non-wsl-support/1658-specify-iter1-rebuttals.md b/codev/projects/1658-windows-native-non-wsl-support/1658-specify-iter1-rebuttals.md new file mode 100644 index 0000000000..7032cf2d7c --- /dev/null +++ b/codev/projects/1658-windows-native-non-wsl-support/1658-specify-iter1-rebuttals.md @@ -0,0 +1,66 @@ +# Spec 1658 — Iteration 1 rebuttal + +All REQUEST_CHANGES points were accepted and applied (commit `[Spec 1658] Specification with +multi-agent review`). No disagreements. + +## Claude (REQUEST_CHANGES) + +1. **C1 left the husk sweep with no start time.** Accepted. Area C now requires the Windows + census provider to return pid, ppid, command line **and creation time**, and explains why: + the husk sweep and the registered-PID set skip any process whose start time is null. The + "DB PIDs + taskkill only" option is now stated to forfeit the sweep, so it fails parity. + Open Question 2 was rewritten so the owner sees that trade-off. +2. **Worktree removal and open handles.** Accepted. Added three things: + - a success criterion (removal succeeds despite transient handles, or fails clearly); + - retry-with-backoff and junction-safe deletion in Area E; + - test scenario 6a and a risk row. +3. **Long paths need admin.** Accepted. Long paths moved out of nice-to-know: + - an assumption (`LongPathsEnabled` assumed off; per-user `core.longpaths`); + - a success criterion (typical-depth workspace works; doctor checks depth and `core.longpaths`); + - test scenario 6b and a risk row. +4. **`tea` missing.** Fixed. Assumptions now list the provider CLIs as `gh`, `glab`, `tea`. +5. **Session id parsed from the `.sock` basename.** Added to Area A's list of endpoint-string + dependencies. +6. **Owner lock already self-clears.** Noted in Area B, so the plan won't over-engineer the + graceful Tower-stop path. + +## Codex (REQUEST_CHANGES) + +1. **HELLO secret vs "not connectable."** Accepted. The criterion now states testable + semantics: an unauthenticated connection receives zero bytes and its frames have no effect. + Area A states plainly that the secret gate does not prevent opening a handle, and that + OS-level non-connectability (a DACL) is Open Question 6 for the owner. +2. **Detect-and-warn isn't parity.** Accepted. Survival is now *required* on documented launch + paths. A kill-on-close job context must either relaunch Tower outside the job (WMI + `Win32_Process.Create` or a scheduled task, both without native code) or refuse with an + actionable error. Open Question 3 asks the owner to choose; warning alone is ruled out. +3. **`taskkill /T` plus a degraded sweep can't guarantee "no descendants."** Accepted. + - Teardown now takes a pre-kill census snapshot and kills recorded descendants explicitly, + using creation time as a PID-reuse guard. + - The sweep is the backstop. + - Census unavailability is a reported failure, not a supported mode ("No degraded mode at + parity"). +4. **Build Tools fallback contradicts the locked-down target.** Accepted. The supported Windows + Node versions are defined as those with prebuilds. A new criterion requires a clear failure, + never a silent source build. +5. **Agent vs shell terminal semantics.** Accepted. The success criteria, Desired State and test + scenario 3 now distinguish: + - agent terminals, which run the harness directly; + - human shell terminals, which run PowerShell. + + The reading of ruling 4 is recorded in Assumptions and raised as Open Question 10 for owner + confirmation, rather than decided unilaterally. +6. **Critical questions resolved at the gate.** Agreed. Open Questions 1 and 2 remain Critical, + explicitly for the spec-approval gate. Open Question 1 proposes a promotion criterion: N + consecutive green runs on main without a Windows-only flake. The owner sets N. + +## Gemini (COMMENT), suggestions also adopted + +- `.cmd` shims can't run under ConPTY's `CreateProcess`: added to Area D, with a risk row. +- MSYS output-path normalization (`/c/...` back to `C:\...`): added to Area D and a success + criterion. +- `taskkill /T` on an already-dead root walks nothing: covered by the pre-kill snapshot, and + added to test scenario 6. +- Junction deletion must not traverse targets: added to Area E, the success criteria, and + scenario 6a. +- Secret delivered through environment or stdin, never argv: made explicit in Area A. diff --git a/codev/state/spir-1658_thread.md b/codev/state/spir-1658_thread.md index cf5c021866..65b547078c 100644 --- a/codev/state/spir-1658_thread.md +++ b/codev/state/spir-1658_thread.md @@ -15,4 +15,11 @@ Owner rulings treated as fixed (Baked-Decision equivalent, though not under that - Windows CI leg: recommended non-required → required; owner confirms at spec gate. ## Progress -- [in progress] Specify — auditing codebase (transport/lifecycle, launchers/residue) to ground the spec. +- [done] Specify — codebase audit (~145 POSIX sites; key blockers: UDS shellper transport, + start-time PID-reuse guard null on win32 => every reconnect fails, process-group kills, + ps/pgrep/lsof, /bin/bash launchers). Spec drafted; CMAP iter1: claude+codex REQUEST_CHANGES, + gemini COMMENT — all accepted (census must carry creation time; pre-kill descendant snapshot; + kill-on-close job => relaunch-outside or refuse, not warn; worktree removal vs open handles; + MAX_PATH; agent vs human-shell terminal split; no toolchain fallback). +- [gate] spec-approval pending. Owner decisions at gate: OQ1 CI leg + promotion N, OQ2 census + provider (PowerShell CIM vs bundled binary), OQ3 kill-on-close behavior, OQ10 ruling-4 reading. From 0f83301211633d4816a994ac9277eab66ad10e21 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Wed, 21 Oct 2026 15:43:00 +1100 Subject: [PATCH 007/245] [Spec 1658] Specification with user feedback --- .../1658-windows-native-non-wsl-support.md | 169 ++++++++++-------- 1 file changed, 96 insertions(+), 73 deletions(-) diff --git a/codev/specs/1658-windows-native-non-wsl-support.md b/codev/specs/1658-windows-native-non-wsl-support.md index 143a7cb431..ee22d7ecc6 100644 --- a/codev/specs/1658-windows-native-non-wsl-support.md +++ b/codev/specs/1658-windows-native-non-wsl-support.md @@ -93,7 +93,8 @@ Linux user, under two prerequisites: Git for Windows and jq. `postSpawn`, and `devCommand`. - `codev doctor` on Windows checks the Windows prerequisites and explains anything missing. - Docs present two supported Windows paths: **native + Git Bash** and **WSL 2**. -- A Windows CI leg keeps the support from rotting. +- An on-demand Windows CI workflow lets maintainers check Windows before a release, or whenever + a change touches platform code. The macOS and Linux paths behave exactly as before. Every Windows change is a platform branch or an abstraction whose POSIX implementation is today's code. @@ -153,8 +154,7 @@ disabled, unless it says otherwise. process-age race guard the POSIX sweep uses. - [ ] Other non-admin local users cannot **read session data from, or control,** a shellper. An unauthenticated connection receives zero bytes, and every frame it sends is ignored before the - connection is dropped. Whether OS-level *non-connectability* is also required is Open - Question 6. + connection is dropped. OS-level non-connectability is not required (decision 7). **Launchers and forge** - [ ] Paths flow correctly in both directions across the Git Bash boundary: Windows paths passed @@ -187,11 +187,18 @@ disabled, unless it says otherwise. **Non-regression and CI** - [ ] The macOS and Linux unit, integration, CLI e2e and post-release e2e suites pass unchanged. -- [ ] `windows-latest` runs the unit, CLI e2e, and post-release e2e workflows. It is introduced - as non-required, *pending owner confirmation at the spec gate* (see Open Questions). +- [ ] A Windows CI workflow (`windows-latest`) runs the unit, CLI e2e, and post-release e2e + suites **on demand only** (manual dispatch). It never runs automatically on PRs or pushes and + is never a required check. +- [ ] Shellper connections on macOS/Linux are unchanged: the same Unix-socket endpoint, the same + handshake, and no secret. The Windows-only per-session secret never applies on POSIX. Upgrade + compatibility with sessions started by an older release is therefore a non-issue on POSIX, + and there is no existing Windows install base to stay compatible with. - [ ] The test suite no longer hardcodes `/tmp` or POSIX-only helpers in tests that are meant to run on Windows. Tests that are inherently POSIX-only are explicitly skipped on win32, with a - reason. + reason. Test helpers that use `/tmp` *deliberately* to keep Unix-socket paths under the + 104-byte macOS `sun_path` limit keep doing so on POSIX. macOS's `os.tmpdir()` (`/var/folders/...`) + is long enough to overflow that limit. **Docs** - [ ] The install docs present native Windows (Git for Windows + jq, plus Node ≥ 20, 22 @@ -228,7 +235,8 @@ Decisions. Quoted in substance:** 6. **Prerequisites and docs.** Git for Windows and jq are hard Windows prerequisites, checked by `codev doctor`, including a CRLF warning for user-authored `postSpawn` scripts. WSL 2 stays a documented, supported alternative; docs present native + Git Bash and WSL 2 as two paths. -7. **Windows CI leg.** Recommended, but the owner confirms it at the spec gate: add +7. **Windows CI leg.** *(Superseded at the spec gate, 2026-10-21. See "Decisions at the spec + gate" below: the workflow is on-demand only.)* Original text: add `windows-latest` to the unit, CLI e2e, and post-release e2e workflows. It starts as non-required during stabilization and is then promoted to a required check. @@ -274,11 +282,19 @@ The ruling also named known items the spec must address: which covers every external tool the forge scripts use except `jq` and the provider CLIs `gh`, `glab` and `tea`. The provider CLIs are separate installs on every platform already. - Windows 10 1809+ (ConPTY) is the floor. Windows 10 22H2 and Windows 11 x64 are the tested - targets. Windows on ARM is not a tested target in this project (see Open Questions). + targets. x64 is the supported architecture. Windows on ARM64 is supported only if node-pty and + better-sqlite3 ship win32-arm64 prebuilds, verified in the audit phase. Otherwise the docs + state ARM64 is unsupported, because a source build needs a compiler toolchain. - The VS Code extension already branches for Windows where it touches the OS (for example Explorer reveal). It needs only launcher fixes (`afx`/`.cmd` shims), not a redesign. -- GitHub-hosted `windows-latest` runners include Git for Windows, so CI exercises the same - Git Bash path users have. +- GitHub-hosted `windows-latest` runners include Git for Windows, so the on-demand CI + workflow exercises the same Git Bash path users have. +- Windows PowerShell 5.1 (`powershell.exe`) ships with every supported Windows version. Codev + uses it as needed for OS integration that Node does not expose: the process census, the + port-owner lookup, and relaunching Tower outside a kill-on-close job. These are inline + commands (`-NoProfile -NonInteractive -Command`), never `.ps1` scripts, so script execution + policy does not apply. They are not twins of forge scripts; ruling 4's Git-Bash-only rule + governs *scripts*. - Claude Code honors `CLAUDE_CODE_GIT_BASH_PATH` on Windows, so a builder's own Bash tool uses the same Git Bash Codev resolved. - Windows' 260-character MAX_PATH limit can only be lifted machine-wide with admin rights @@ -289,8 +305,8 @@ The ruling also named known items the spec must address: - Ruling 4's "PowerShell is the default interactive shell for architect and shell terminals" is read as follows. The `.codev/config.json` *shell* role defaults to PowerShell on Windows. Agent terminals still run their configured harness command directly (`shell.architect: claude`), as - they do on POSIX. A terminal that needs a host shell uses PowerShell. This is confirmed at the - spec gate (Open Question 10). + they do on POSIX. A terminal that needs a host shell uses PowerShell. Confirmed at the spec gate + (decision 4): the architect spawns builders with no user intervention. ## Solution Approaches @@ -328,8 +344,8 @@ fallback**. Each has competing approaches below. already-connected read-only client, because the server writes nothing before HELLO is verified. Name squatting is mitigated by unguessable UUID names and by the client verifying the WELCOME (session id and pid). Under the secret gate another user *can* open a handle to the - pipe but gets nothing from it. True non-connectability needs a restrictive DACL (Open - Question 6). + pipe but gets nothing from it. **Decided at the spec gate: option 2, Windows only.** POSIX + keeps its `0600` socket and today's handshake unchanged. - **Pros:** smallest conceptual change; one framing; reuses the existing persistence model. - **Cons:** each file-based assumption needs a platform branch. - **Risk:** medium. @@ -383,13 +399,16 @@ Node cannot request job breakaway. The spec requires the following: - `afx tower start` from PowerShell / Windows Terminal; - from the VS Code extension; - from CI. -- **If Tower detects it is inside a kill-on-close job**, that launch context cannot keep the - persistence guarantee. A warning alone is not parity. The two candidate behaviors are Open - Question 3: - 1. **Relaunch outside the job.** A process created through WMI's `Win32_Process.Create` or a - one-shot scheduled task is parented outside the caller's job, with no native code. - 2. **Refuse.** Refuse to start Tower in that context, with an actionable error naming a - supported launch path. +- **Measure first.** The audit phase determines which common launchers (Windows Terminal, + PowerShell console, VS Code integrated terminal and extension host, SSH sessions, CI runners) + actually place children in a kill-on-close job. +- **If Tower's launcher is a kill-on-close job**, `afx tower start` launches the daemon + **outside the job** through the built-in PowerShell. A process created via CIM/WMI + `Win32_Process.Create` is parented by the WMI service, not the caller, so it is not in the + caller's job. No native code is needed. A warning alone is not parity. +- **If that relaunch is itself blocked by policy**, `afx tower start` refuses with an + actionable error naming a supported launch path. It never starts a Tower whose builders would + silently die with the launcher. ### Area C: Process introspection (census, start time, port owner) @@ -422,12 +441,11 @@ census (pid, ppid, cmdline), process start time, the port-to-PID owner, and live binary on the hot path. - **Risk:** medium. -**The census provider itself** has two options, and the choice is an open question for the -owner. Both must satisfy the field requirements above. -1. A `powershell.exe -NoProfile -NonInteractive -Command` CIM query. Inline commands are not - governed by script execution policy, but Constrained Language Mode or AppLocker may still - apply. -2. A dependency that bundles a small native census binary. +**The census provider (decided at the spec gate)** is the built-in Windows PowerShell: an +inline `powershell.exe -NoProfile -NonInteractive -Command` CIM query against `Win32_Process` +(pid, ppid, command line, creation time). The same mechanism serves port-owner lookup +(`Get-NetTCPConnection`). Inline commands are not governed by script execution policy. No +bundled binary and no new dependency. Note that `wmic` is removed on current Windows 11, and Git Bash's MSYS `ps` cannot see native command lines. @@ -480,9 +498,14 @@ a shared resolver. That spreads the System32-WSL trap across every site. ### Area F: Interactive shell default On Windows the default for `shell.shell`, and the dashboard and `afx shell` fallback, is -PowerShell. We prefer `pwsh` (PowerShell 7) if installed and fall back to `powershell.exe` -(5.1, always present). Architect and builder commands are unchanged (`claude`), apart from shim -resolution. +PowerShell: the built-in Windows PowerShell 5.1 (`powershell.exe`), with no auto-detection of +PowerShell 7. A user who prefers PowerShell 7 sets `shell.shell: "pwsh"` in +`.codev/config.json`. Architect and builder commands are unchanged (`claude`), apart from shim +resolution. The architect spawns builders with no human intervention, exactly as on POSIX. + +**Console windows.** Every background process Codev starts on Windows (the Tower daemon, +shellpers, the IDE server, detached helpers) is spawned with its console window hidden. No +empty console window appears on the desktop, so closing one cannot kill Tower. An existing `.codev/config.json` carrying `shell: "bash"` (today's scaffold default) is interpreted on Windows as **Git Bash**, never as System32 `bash.exe`, so existing configs cannot @@ -490,44 +513,44 @@ launch WSL. ## Open Questions -**Critical (blocks the plan)** -1. **Windows CI leg commitment** (ruling 7 is "recommended, owner confirms at the spec gate"). - Does the owner confirm `windows-latest` on unit, CLI e2e, and post-release e2e, introduced - non-required and promoted to required after stabilization? What is the promotion criterion? - We propose N consecutive green runs on main without a Windows-only flake. -2. **Process census provider (Area C).** The provider must return pid, ppid, command line *and - creation time*, or the Windows husk sweep and teardown snapshot become no-ops (see Area C). - Ruling 4 makes Git Bash "the only plumbing shell" for scripts. Is an inline - `powershell.exe -Command` CIM query, used only for the census and not as a script, acceptable - under that ruling? Or does the owner prefer a bundled census binary? A third option, "DB-registered PIDs plus `taskkill` only", **forfeits** the husk sweep - and the re-parented-descendant guarantee, so it does not meet full parity. The recommended C1 - keeps the census off the reconnect hot path either way. - -**Important (shapes the design)** -3. **Kill-on-close job context.** If Tower is launched inside a kill-on-close job (so shellpers - would die with it), should Codev relaunch Tower outside the job (WMI `Win32_Process.Create` - or a one-shot scheduled task, both without native code but each subject to policy), or refuse - to start with an actionable error? Detect-and-warn alone does not meet parity. -4. **PowerShell flavor.** Should we prefer `pwsh` 7 when present and fall back to Windows - PowerShell 5.1, or always use 5.1 for predictability? (Proposed: prefer `pwsh`.) -5. **Windows on ARM64.** Out of scope for this project (documented as untested), or a supported - target? -6. **Pipe access control.** Is the per-session HELLO secret (A1, option 2) acceptable as the - owner-only guarantee, or does the owner require OS-level pipe ACLs (native interop)? -7. **Owner-only files** (local key, cloud config). Is setting an explicit current-user-only ACL - at creation time required, or is reliance on the default per-user profile ACL (`%USERPROFILE%` - is already private to the user on default installs) acceptable, documented as such? - -**Nice-to-know** -8. Should doctor also report Developer Mode and `core.autocrlf`, so the user knows ahead of time - which link mode and line-ending behavior they will get? (Long paths are now a success - criterion.) -9. Should `afx tower start` on Windows hide the daemon console window (`windowsHide`), which - matters for UX in the VS Code extension and the CLI? - -**Important (confirm at the spec gate)** -10. **Ruling 4 interpretation.** Is the reading in Assumptions right? Agent terminals run the - harness directly; the *shell* role and any host-shell need default to PowerShell. +### Decisions at the spec gate (user feedback, 2026-10-21) + +These were open questions in the reviewed draft. They are now settled. + +1. **Windows CI:** create the Windows CI workflow, but run it **on demand only** (manual + dispatch). It never runs automatically and is never a required check. This supersedes ruling + 7's non-required-then-required plan, so no promotion criterion is needed. +2. **Process census and other OS integration:** use the built-in Windows PowerShell as needed + ("don't reinvent the wheel"). Inline CIM queries serve the census (with creation time), + port-owner lookup, and relaunching outside a kill-on-close job. No bundled binary, no native + addon. +3. **Kill-on-close job:** measure which launchers do this. Where one does, relaunch Tower outside + the job through built-in PowerShell (CIM `Win32_Process.Create`). Refuse with an actionable + error only if that relaunch is blocked (see Area B). +4. **Agent terminals:** the architect and builder terminals launch their harness directly, and + the architect spawns builders with **no user intervention**, exactly as on POSIX. PowerShell + is the default only for plain human shell terminals (the `shell` role). This resolves the + ruling-4 reading. +5. **PowerShell version:** the built-in Windows PowerShell 5.1, everywhere. There is no + PowerShell 7 dependency or auto-detection; users may opt in via `shell.shell: "pwsh"`. +6. **CPU architecture:** x64 is supported. ARM64 is supported only if the native dependencies + ship win32-arm64 prebuilds; otherwise it is documented as unsupported. +7. **Pipe access control:** a per-session secret, **Windows only**. Other users can open the + pipe but receive nothing and control nothing. No OS-level pipe ACL and no native code. POSIX + connections are unchanged. +8. **Owner-only files:** rely on Windows user folders (`%USERPROFILE%`) being private to their + owner by default. No explicit ACLs. This is documented in the install docs. +9. **Doctor scope:** doctor does **not** report Developer Mode or `core.autocrlf`. Link fallback + is reported by `afx spawn` when it happens, shipped scripts are pinned to LF, and doctor keeps + only ruling 6's CRLF warning for user-authored `postSpawn`/`devCommand` scripts. The long-path + diagnosis in the success criteria stays. +10. **Console windows:** always hidden for background processes. +11. **Upgrade compatibility:** not required. POSIX shellper connections are unchanged (decision + 7 is Windows-only), and there are no existing Windows sessions to stay compatible with. + +### Remaining + +None blocking. The owner confirms the above at the spec-approval gate. ## Test Scenarios @@ -582,8 +605,8 @@ launch WSL. **Non-functional** 16. The macOS and Linux suites pass unchanged, so there is no POSIX regression. -17. Windows CI leg stability: track the flake rate over the stabilization window. A Windows-only - flaky test is annotated, not silently retried. +17. The on-demand Windows CI workflow runs green from a manual dispatch. A Windows-only flaky + test is annotated, not silently retried. 18. Render-gate captures: ConPTY capture fixtures for each validated harness are checked in and replayed by unit tests, the same way existing gate captures are. @@ -591,18 +614,18 @@ launch WSL. | Risk | Probability | Impact | Mitigation | |------|-------------|--------|------------| -| Shellpers die with Tower when Tower runs inside a kill-on-close job (terminal host or IDE) | Medium | High (breaks the persistence guarantee) | Survival required on each documented launch path; detect the job at start and relaunch outside it or refuse (OQ 3); never a silent warning | +| Shellpers die with Tower when Tower runs inside a kill-on-close job (terminal host or IDE) | Medium | High (breaks the persistence guarantee) | Measure the launchers; relaunch Tower outside the job via built-in PowerShell CIM; refuse only if that is blocked; never a silent warning | | `taskkill /T` misses re-parented descendants, or walks nothing when the root is already dead | Medium | Medium | Pre-kill census snapshot with explicit per-PID kills; orphan/husk sweep as backstop; teardown tests assert no survivors; Job Objects as fallback (Area B2) | | ConPTY timing differs from POSIX PTYs, so the render gate misclassifies (lost or force-held mail) | High | High | Assume nothing (#1567 lesson): capture real ConPTY sessions per harness, replay them as fixtures, adjust gate profiles from the data | | MSYS path conversion mangles forge arguments | High | Medium | Per-call opt-out of automatic conversion; explicit translation helper; concept-by-concept tests with slash-prefixed arguments | | System32 `bash.exe` invoked by accident, launching WSL or failing on WSL-blocked machines | Medium | High | Single resolver; never bare `bash`; doctor rejects System32; config `shell: bash` maps to Git Bash on Windows | -| Locked-down policy (AppLocker, Constrained Language Mode) blocks the census provider or a bundled binary | Medium | Medium | Keep introspection off the reconnect hot path (C1); sweeps degrade to DB-registered PIDs if the provider fails | +| Locked-down policy (AppLocker, Constrained Language Mode) blocks the inline PowerShell CIM queries | Medium | Medium | Keep introspection off the reconnect hot path (C1); a blocked census is a reported failure (doctor + Tower log), not a silent degrade | | Pipe readable or connectable by other local users | Low | High | Per-session HELLO secret, kept off argv; server sends nothing pre-auth; UUID pipe names; client verifies WELCOME | | Native deps lack Windows prebuilds for a Node version | Low | Medium | Verify in the audit phase; supported Node versions are defined as those with prebuilds; clear failure otherwise (no toolchain fallback); CI leg catches regressions | | Worktree removal fails on open handles (antivirus, indexer, lingering cwd) | High | Medium | Kill the session tree first; retry with backoff; report the blocking path; never recurse through junctions | | MAX_PATH overruns in nested `.builders//` plus dependency trees (raising the limit needs admin) | Medium | High | Doctor depth check and `core.longpaths` check; docs recommend a short workspace root; keep Codev-generated paths short | | `.cmd` shims cannot be spawned directly under ConPTY | High | High | Resolve shims through `PATHEXT` and launch them through `%COMSPEC% /c` or the underlying Node entry point | -| Windows CI leg is flaky or slow, so it gets ignored and support rots | High | High | Non-required during stabilization with an explicit promotion criterion; flaky tests annotated per protocol; required check after promotion | +| On-demand-only Windows CI means a Windows regression can merge unnoticed until someone dispatches the workflow | High | Medium | Dispatch before every release and on any PR touching platform code (documented in the release protocol and the PR checklist); flaky tests annotated per protocol | | A platform branch changes POSIX behavior | Low | High | POSIX implementations stay today's code; full macOS/Linux suites gate every phase | | CRLF line endings (Git `autocrlf`) break bash scripts or output parsing | Medium | Medium | `.gitattributes` `eol=lf` for shipped `.sh` scripts; CRLF-tolerant output parsing; doctor CRLF warning for user scripts | From d06ed2dabeb034f324f04bbe8408ff3cde74c718 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Wed, 21 Oct 2026 15:43:14 +1100 Subject: [PATCH 008/245] [Spec 1658] Thread log: gate feedback --- codev/state/spir-1658_thread.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/codev/state/spir-1658_thread.md b/codev/state/spir-1658_thread.md index 65b547078c..db55358c7f 100644 --- a/codev/state/spir-1658_thread.md +++ b/codev/state/spir-1658_thread.md @@ -23,3 +23,13 @@ Owner rulings treated as fixed (Baked-Decision equivalent, though not under that MAX_PATH; agent vs human-shell terminal split; no toolchain fallback). - [gate] spec-approval pending. Owner decisions at gate: OQ1 CI leg + promotion N, OQ2 census provider (PowerShell CIM vs bundled binary), OQ3 kill-on-close behavior, OQ10 ruling-4 reading. +- [gate feedback 2026-10-21] User decisions folded into the spec ("Decisions at the spec gate"): + - Windows CI is on-demand only (supersedes ruling 7). + - Built-in PowerShell 5.1 is used as needed for OS integration: census, port owner, and + relaunch outside a kill-on-close job. + - Kill-on-close jobs: measure first, relaunch via CIM, refuse only if blocked. + - Agent terminals launch the harness directly with no user intervention. + - x64 is supported; arm64 only if prebuilds exist. + - Pipe secret is Windows-only, so POSIX is unchanged and no upgrade compatibility is needed. + - Owner-only files rely on user-folder privacy; no extra doctor checks; console windows hidden. + - Still at the spec-approval gate, waiting for the owner. From 279f4edab579573bf852d91257f39f6257105525 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Wed, 21 Oct 2026 15:54:49 +1100 Subject: [PATCH 009/245] [Spec 1658] Specification with user feedback: sync facts with current main --- codev/specs/1658-windows-native-non-wsl-support.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/codev/specs/1658-windows-native-non-wsl-support.md b/codev/specs/1658-windows-native-non-wsl-support.md index ee22d7ecc6..e2c921544c 100644 --- a/codev/specs/1658-windows-native-non-wsl-support.md +++ b/codev/specs/1658-windows-native-non-wsl-support.md @@ -46,7 +46,8 @@ approximate; they size the work rather than enumerate it. only darwin and linux branches. On Windows it returns null, so **every reconnect after a Tower restart is treated as PID reuse and fails**. The "detached shellpers survive a Tower restart" guarantee is unproven on Windows. -3. **Launchers.** All 56 forge concept scripts are POSIX `#!/bin/sh`. The scripts are fine; the +3. **Launchers.** All 64 forge concept scripts (five providers: GitHub, GitLab, Gitea, Linear, + Jira) are POSIX `#!/bin/sh`. The owner ruling's count of 56 predates the Jira provider. The scripts are fine; the **launch sites** are not. Five places assume a POSIX shell: - Forge concepts run through `exec`/`execSync` with the default shell, which on Windows is `cmd.exe` and cannot run a shebang script. @@ -280,7 +281,8 @@ The ruling also named known items the spec must address: and the shellper HELLO/WELCOME framing is transport-agnostic. - Git for Windows ships `bash`, `sed`, `grep`, `head`, `tail`, `sort`, `date`, `curl` and `git`, which covers every external tool the forge scripts use except `jq` and the provider CLIs `gh`, - `glab` and `tea`. The provider CLIs are separate installs on every platform already. + `glab`, `tea` and Atlassian's `acli` (Jira). The provider CLIs are separate installs on every + platform already. - Windows 10 1809+ (ConPTY) is the floor. Windows 10 22H2 and Windows 11 x64 are the tested targets. x64 is the supported architecture. Windows on ARM64 is supported only if node-pty and better-sqlite3 ship win32-arm64 prebuilds, verified in the audit phase. Otherwise the docs @@ -475,7 +477,9 @@ native form before Node consumes them. Otherwise Node reads `/c/...` as a drive- **Executable shims.** ConPTY spawns through `CreateProcess`, which cannot execute a `.cmd`/`.bat` file directly. The `.cmd`/`.exe` shims that commands like `claude` and `afx` become on Windows are resolved through `PATHEXT` before spawn, both for PTY commands and for the VS Code -extension's `afx` calls. +extension's `afx` calls. A Windows-aware `PATH`/`PATHEXT` executable lookup already exists in +the codebase (the consult-availability probe in `codev bootstrap-docs`). That lookup is the +starting point: it is promoted to a shared helper rather than written again. **Considered and rejected:** running each launch site with `shell: ` piecemeal, without a shared resolver. That spreads the System32-WSL trap across every site. @@ -583,7 +587,7 @@ None blocking. The owner confirms the above at the spec-approval gate. 7. **Orphan sweep:** an unregistered shellper (for example after a Tower crash) is detected and reaped at the next start. 8. **Forge:** run every GitHub concept live, including ones passing `/repos/...`-style arguments. - Run GitLab/Gitea/Linear concepts through the same launcher in unit tests with stubbed CLIs. + Run GitLab/Gitea/Linear/Jira concepts through the same launcher in unit tests with stubbed CLIs. 9. **Spawn → porch:** a BUGFIX builder spawned on Windows runs `porch next`/`porch done` through a phase transition and opens a PR via the forge. 10. **postSpawn** with a multi-command bash hook; also with a CRLF hook file, where doctor warns From 1c6b396027176de5970beed68194f06f2a598fa9 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Wed, 21 Oct 2026 16:18:06 +1100 Subject: [PATCH 010/245] [Spec 1658] Specification with user feedback: Codev IDE server in scope on Windows --- .../1658-windows-native-non-wsl-support.md | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/codev/specs/1658-windows-native-non-wsl-support.md b/codev/specs/1658-windows-native-non-wsl-support.md index e2c921544c..5637a1152a 100644 --- a/codev/specs/1658-windows-native-non-wsl-support.md +++ b/codev/specs/1658-windows-native-non-wsl-support.md @@ -87,6 +87,8 @@ Linux user, under two prerequisites: Git for Windows and jq. to them. - Forge concepts (all providers), porch, consult, `afx dev`, runnable worktrees and the VS Code extension work. +- The Tower-managed **Codev IDE server** (`afx ide`, #1789) works on native Windows. This covers + discovery, version preflight, spawn, the `/ide/` forward, and sidebar extension install. - Shell roles are split: - **PowerShell** is the interactive shell a human types into (shell terminals). Agent terminals run their harness directly, as on POSIX. @@ -176,6 +178,25 @@ disabled, unless it says otherwise. reports when the workspace root is deep enough to risk MAX_PATH overruns, and whether Git's per-user `core.longpaths` is set. +**Codev IDE server (#1789) on native Windows** +- [ ] `afx ide start` without `--server-path` finds `codev-ide-server` on `PATH`. On Windows that + is an npm `.cmd` shim, resolved through `PATHEXT` by the same shared shim resolver as every + other command (Area D). +- [ ] The version preflight reads line 1 of `codev-ide-server --version` as a bare + `major.minor.patch` (the VS Code base) and compares it against the declared floor (1.128.0). + It never compares the npm package version. A server below the floor is refused with a message + naming the installed version and the floor. +- [ ] Tower spawns the server headless: the console window is hidden, under the same rule as + every other background process. +- [ ] `/ide/?folder=` is served through Tower, with the folder given as a native Windows + path. +- [ ] The embedded sidebar extension is installed with `--install-extension` into a Tower-owned + Windows extensions directory, and the server is spawned with `--extensions-dir`. Both flags + receive native Windows paths, including paths longer than 260 characters. +- [ ] `afx ide stop` tears down the whole process tree. No server or extension-host process + survives. +- [ ] Nothing the IDE integration writes lands outside the user profile. + **Breadth** - [ ] `consult` (all three reviewers, with non-blocking skip semantics intact), porch checks, `afx open`, and `afx attach` work on Windows. @@ -310,6 +331,26 @@ The ruling also named known items the spec must address: they do on POSIX. A terminal that needs a host shell uses PowerShell. Confirmed at the spec gate (decision 4): the architect spawns builders with no user intervention. +**Dependencies on other work** +- **#1789 (Tower-managed Codev IDE server) merges first.** This project builds on its code + (discovery, version handshake, extensions handover) and adds the Windows behavior. It does not + re-implement any of it. +- **The Codev IDE server package**, published by the codev-ide lane: + - Meta-package `@cluesmith/codev-ide`, with bin `codev-ide-server`. + - Windows platform package `@cluesmith/codev-ide-server-win32-x64`. + - Both ship in the first publish (1.0.0), are built against this spec's assumptions, and are + versioned independently of `@cluesmith/codev`. + - Tower compares the VS Code base on `--version` line 1 against the 1.128.0 floor, never the + npm version. +- **Facts agreed with the codev-ide architect**, which this project relies on and does not + re-derive: + - On Windows, `codev-ide-server` on `PATH` is an npm `.cmd` shim. + - Line 1 of `--version` stays a bare `major.minor.patch`. + - The server runs headless. + - It exits cleanly on process-tree teardown. + - `--extensions-dir` and `--install-extension` accept native and long Windows paths. + - It writes only under the user profile. + ## Solution Approaches The owner rulings already settle the forge-layer fork (Git Bash) and the shell roles. The open @@ -551,6 +592,12 @@ These were open questions in the reviewed draft. They are now settled. 10. **Console windows:** always hidden for background processes. 11. **Upgrade compatibility:** not required. POSIX shellper connections are unchanged (decision 7 is Windows-only), and there are no existing Windows sessions to stay compatible with. +12. **Codev IDE server in scope on native Windows** (owner direction, 2026-10-21, relayed by the + main architect from the codev-ide architect). Tower's IDE integration from #1789 (discovery, + version preflight, spawn, `/ide/` forward, extension install) must work on native Windows. + It is not a follow-up or an unsupported platform. The codev-ide lane ships + `@cluesmith/codev-ide-server-win32-x64` in its first publish (1.0.0). #1789 merges first, + and this project builds on its code (see "Dependencies on other work"). ### Remaining @@ -606,6 +653,17 @@ None blocking. The owner confirms the above at the spec-approval gate. bytes, and frames it sends have no effect. 15. **VS Code extension:** start Tower, spawn a builder, open its terminal, and reveal a file, all on Windows. +15a. **Codev IDE server:** + - `afx ide start` on Windows finds the `codev-ide-server` `.cmd` shim, passes the version + floor, and serves `/ide/?folder=` through Tower. + - It installs the sidebar extension into a Windows extensions directory. + - `afx ide stop` leaves no server or extension-host process. + - Negative case: a server whose `--version` line 1 is below 1.128.0 is refused with the + versioned message. +15b. **Joint check with the codev-ide lane:** the codev-ide architect brings the win32-x64 + package from its CI run, and the owner's Windows machine is the test machine. The plan + decides whether this runs at dev-approval or at verify. Scenario 15a is executed + end-to-end there. **Non-functional** 16. The macOS and Linux suites pass unchanged, so there is no POSIX regression. From 5b46ca0fd17324c29e2de25733f329947f6bc006 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Wed, 21 Oct 2026 16:18:18 +1100 Subject: [PATCH 011/245] [Spec 1658] Thread log: IDE server direction --- codev/state/spir-1658_thread.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codev/state/spir-1658_thread.md b/codev/state/spir-1658_thread.md index db55358c7f..de138dfa14 100644 --- a/codev/state/spir-1658_thread.md +++ b/codev/state/spir-1658_thread.md @@ -33,3 +33,5 @@ Owner rulings treated as fixed (Baked-Decision equivalent, though not under that - Pipe secret is Windows-only, so POSIX is unchanged and no upgrade compatibility is needed. - Owner-only files rely on user-folder privacy; no extra doctor checks; console windows hidden. - Still at the spec-approval gate, waiting for the owner. +- [gate feedback 2026-10-21] Merged origin/main (389 commits; clean). Spec synced: 64 forge scripts (Jira/acli added), reuse bootstrap-docs PATHEXT lookup. +- [gate feedback 2026-10-21] Owner direction via main: Codev IDE server (#1789) IN SCOPE on native Windows. Added SC block, Dependencies (1789 merges first; @cluesmith/codev-ide + win32-x64 pkg 1.0.0; floor 1.128.0 on --version line 1), scenarios 15a/15b (joint check on owner's Windows box), decision 12. Spec head 1c6b39602. Still holding at spec-approval. From a227b10ca6a8cf231e3d1c1430b43938398f0db0 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Wed, 21 Oct 2026 16:23:41 +1100 Subject: [PATCH 012/245] chore(porch): 1658 spec-approval gate-approved --- .../projects/1658-windows-native-non-wsl-support/status.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codev/projects/1658-windows-native-non-wsl-support/status.yaml b/codev/projects/1658-windows-native-non-wsl-support/status.yaml index 1407161d86..3429c5b8f4 100644 --- a/codev/projects/1658-windows-native-non-wsl-support/status.yaml +++ b/codev/projects/1658-windows-native-non-wsl-support/status.yaml @@ -6,8 +6,9 @@ plan_phases: [] current_plan_phase: null gates: spec-approval: - status: pending + status: approved requested_at: '2026-10-11T21:28:54.199Z' + approved_at: '2026-10-21T05:23:41.122Z' plan-approval: status: pending pr: @@ -18,4 +19,4 @@ iteration: 1 build_complete: true history: [] started_at: '2026-10-11T21:15:51.801Z' -updated_at: '2026-10-11T21:28:54.200Z' +updated_at: '2026-10-21T05:23:41.123Z' From a9d8e24406b6261a5d1f2c415e210854d02c7b1e Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Wed, 21 Oct 2026 16:23:43 +1100 Subject: [PATCH 013/245] chore(porch): 1658 plan phase-transition --- .../1658-windows-native-non-wsl-support/status.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/codev/projects/1658-windows-native-non-wsl-support/status.yaml b/codev/projects/1658-windows-native-non-wsl-support/status.yaml index 3429c5b8f4..3f5561f1b3 100644 --- a/codev/projects/1658-windows-native-non-wsl-support/status.yaml +++ b/codev/projects/1658-windows-native-non-wsl-support/status.yaml @@ -1,7 +1,7 @@ id: '1658' title: windows-native-non-wsl-support protocol: spir -phase: specify +phase: plan plan_phases: [] current_plan_phase: null gates: @@ -16,7 +16,10 @@ gates: verify-approval: status: pending iteration: 1 -build_complete: true +build_complete: false history: [] started_at: '2026-10-11T21:15:51.801Z' -updated_at: '2026-10-21T05:23:41.123Z' +updated_at: '2026-10-21T05:23:43.085Z' +context_refreshes: + - boundary: enter:plan + at: '2026-10-21T05:23:43.084Z' From dc9674bd9e14a40fa7f55843af876dcdf9ff85d4 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Wed, 21 Oct 2026 16:24:28 +1100 Subject: [PATCH 014/245] chore(porch): 1658 acknowledge context refresh --- codev/projects/1658-windows-native-non-wsl-support/status.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codev/projects/1658-windows-native-non-wsl-support/status.yaml b/codev/projects/1658-windows-native-non-wsl-support/status.yaml index 3f5561f1b3..7545ba7baa 100644 --- a/codev/projects/1658-windows-native-non-wsl-support/status.yaml +++ b/codev/projects/1658-windows-native-non-wsl-support/status.yaml @@ -19,7 +19,8 @@ iteration: 1 build_complete: false history: [] started_at: '2026-10-11T21:15:51.801Z' -updated_at: '2026-10-21T05:23:43.085Z' +updated_at: '2026-10-21T05:24:28.777Z' context_refreshes: - boundary: enter:plan at: '2026-10-21T05:23:43.084Z' + acknowledged_at: '2026-10-21T05:24:28.776Z' From 3bd35fbad40059802ff31c4f4a841064633ae9c5 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Wed, 21 Oct 2026 16:29:07 +1100 Subject: [PATCH 015/245] [Spec 1658] Initial implementation plan --- .../1658-windows-native-non-wsl-support.md | 677 ++++++++++++++++++ codev/state/spir-1658_thread.md | 6 + 2 files changed, 683 insertions(+) create mode 100644 codev/plans/1658-windows-native-non-wsl-support.md diff --git a/codev/plans/1658-windows-native-non-wsl-support.md b/codev/plans/1658-windows-native-non-wsl-support.md new file mode 100644 index 0000000000..c637148932 --- /dev/null +++ b/codev/plans/1658-windows-native-non-wsl-support.md @@ -0,0 +1,677 @@ +# Plan: Native (non-WSL) Windows Support + +**Specification**: [codev/specs/1658-windows-native-non-wsl-support.md](../specs/1658-windows-native-non-wsl-support.md) + +## Executive Summary + +The spec selects, per area: + +- **A1**: a platform endpoint abstraction. Shellpers use named pipes on Windows, gated by a + Windows-only per-session HELLO secret. POSIX keeps its Unix socket and today's handshake. +- **B1 + census snapshot**: a single "kill session tree" primitive (POSIX group signals, Windows + `taskkill /T /F` plus explicit per-PID kills from a pre-kill census snapshot). Tower is + relaunched outside a kill-on-close job through built-in PowerShell CIM when needed. +- **C1**: on Windows, reconnect identity is the authenticated WELCOME, not process start time. + Sweeps use one Windows census provider (inline PowerShell CIM: pid, ppid, cmdline, creation + time). Tower stop/start learn Tower's PID from the owner lock and `/api/status`, not `lsof`. +- **D**: one Git Bash resolver, one PATHEXT shim resolver (promoted from `codev bootstrap-docs`), + one MSYS path translator. Every plumbing launch site routes through them. +- **E1**: junction for directories, hard link or copy for files, real symlinks when allowed. +- **F**: PowerShell 5.1 for human shell terminals; agent terminals run their harness directly. + +Owner ruling 1 fixes the phase order: audit → transport + lifecycle → forge/launcher routing → +POSIX residue sweep → CI leg. This plan keeps that order and splits the large buckets into +phases that each commit and verify on their own: + +| Ruling-1 bucket | Plan phases | +|---|---| +| Audit | 1 | +| Transport + lifecycle | 2, 3 | +| Forge/launcher routing | 4, 5, 6 | +| (ConPTY gate and IDE server, both of which need working launchers) | 7, 8 | +| POSIX residue sweep | 9 | +| CI leg | 10 | + +Every Windows change enters through the platform seam built in Phase 1, so every Windows branch +is unit-testable on macOS/Linux by forcing the seam to `win32`. Real-Windows behavior is +verified by the on-demand Windows workflow (also started in Phase 1) and by the owner's Windows +machine where CI cannot reach (interactive launchers, authenticated harness captures, the VS +Code extension, the joint IDE check). + +### How Windows verification works across phases + +The builder works on macOS. Windows facts come from three places: + +1. **Seam-forced unit tests** on macOS/Linux, run in every phase. +2. **`windows.yml` dispatches** (`workflow_dispatch` only, never automatic, never required). + Phase 1 creates it with a `probe` job and a `targeted` job that runs a vitest filter given as + a dispatch input. Each later phase adds its Windows-only tests (`describe.runIf(win32)`) and + dispatches the `targeted` job against the branch. Phase 10 adds the full suites. +3. **The owner's Windows machine** for what CI cannot do. These are listed per phase and + collected into one manual test pass at the PR gate. + +**Prerequisite for item 2 (needs an architect decision at plan approval).** GitHub only +dispatches a workflow whose file exists on the default branch. A branch's version of the file +then runs via `--ref`. So the dispatch-only `windows.yml` skeleton needs to land on `main` early, +as a tiny separate PR. It is inert on `main` (dispatch only). The alternative is that every +Windows check before the final PR runs on the owner's machine, which makes the per-phase +verification much slower. **This plan assumes the early landing**; if it is declined, Windows +checks for Phases 2 to 9 move to the owner's machine and the PR-gate pass. + +### Standing constraints applied to every phase + +- POSIX behavior does not change. POSIX implementations stay today's code. Each phase runs the + macOS/Linux unit suite plus `check-types` (`pnpm build` first) before its commit. +- Framework content is mirrored in `codev/` and `codev-skeleton/`. Shipped skill edits touch all + four copies (`.claude`/`.codex` × root/skeleton). +- The mailbox invariant (Spec 1313) and server/client isolation (#1189) hold. No Windows code + enters `codev-sdk` outside its `/node` adapter. +- Commits: `[Spec 1658][Phase: ] ...`, files staged explicitly. One PR at the end. + +## Phases (Machine Readable) + +```json +{ + "phases": [ + {"id": "phase_1", "title": "Windows audit, probe harness, and platform seam"}, + {"id": "phase_2", "title": "Shellper named-pipe transport with per-session secret"}, + {"id": "phase_3", "title": "Process lifecycle and introspection on Windows"}, + {"id": "phase_4", "title": "Git Bash, executable shim, and MSYS path resolution with doctor prerequisites"}, + {"id": "phase_5", "title": "Launcher routing and shell roles"}, + {"id": "phase_6", "title": "Runnable-worktree links and worktree removal on Windows"}, + {"id": "phase_7", "title": "ConPTY render-gate validation captures"}, + {"id": "phase_8", "title": "Codev IDE server on native Windows"}, + {"id": "phase_9", "title": "POSIX residue sweep"}, + {"id": "phase_10", "title": "Windows CI leg and install docs"} + ] +} +``` + +## Phase Breakdown + +### Phase 1: Windows audit, probe harness, and platform seam + +**Dependencies**: None + +#### Objective + +Turn the spec's assumptions into measured facts before any surgery, and give every later phase +a testable platform seam and a way to run code on Windows. + +#### Files to Create / Modify + +- `codev/projects/1658-windows-native-non-wsl-support/audit.md` (new): the measured facts and + the site inventory, each site tagged with the phase that fixes it. +- `scripts/windows-probe.mjs` (new, repo root, not shipped in the package). +- `.github/workflows/windows.yml` (new): `workflow_dispatch` only; jobs `probe` and `targeted` + (input: vitest filter). Node 20/22 × x64. +- `packages/codev/src/lib/platform.ts` (new): `hostPlatform()`, `isWindows()`, and a test-only + override, so Windows branches can be exercised on any host. +- `packages/codev/src/lib/__tests__/platform.test.ts` (new). + +#### Deliverables + +- [ ] **Site inventory**, re-audited against current `main`, covering every site class in the + spec's Current State. It includes the code that landed after the spec audit: the IDE server + spawn/stop (`agent-farm/servers/ide-server.ts`, `ide-forward.ts`, from #1668) and, if merged by + then, #1789 (`commands/ide.ts`, `lib/ide-version.ts`, `lib/ide-extensions.ts`), #1595 hooks, and + #1672 lane card. Re-run at the start of Phases 5, 8 and 9 for anything merged since. +- [ ] **Probe script** measuring, on the host it runs on: + - node-pty and better-sqlite3 prebuild availability for Node 20/22/24, win32-x64 and + win32-arm64 (install from the registry with no toolchain, record pass/fail); + - a named-pipe listen/connect round trip carrying a HELLO/WELCOME frame; + - detached-child survival: spawn a detached child, end the launching process, check the child + survives (an empirical kill-on-close job test that needs no native API); + - PATHEXT resolution of `claude`, `codex`, `afx`, `codev-ide-server`; + - Git Bash candidate locations and the System32 `bash.exe` trap; + - the `afx tower start` hang: start the daemon with its stderr/log captured, record the first + failure. +- [ ] **Launcher survival matrix**: CI runner (from the `probe` job); Windows Terminal, + PowerShell console, VS Code integrated terminal and extension host, SSH (owner runs + `windows-probe.mjs --survival` on their machine). +- [ ] **Root cause of the 2026-09-09 hang**, recorded in `audit.md` (carried to the review). +- [ ] Platform seam plus tests. + +#### Acceptance Criteria + +- [ ] `audit.md` lists every site class with its owning phase, the prebuild matrix (which + defines the supported Windows Node versions and the ARM64 verdict), the launcher survival + matrix, and the hang root cause. +- [ ] `windows.yml` `probe` job runs green from a dispatch (once the file is on `main`). +- [ ] Platform seam tests pass; POSIX unit suite and `check-types` green. + +#### Test Plan + +- Unit: platform seam defaults to `process.platform`; override and reset behave. +- Windows: dispatch `probe`; owner runs the survival mode on their launchers. +- If the audit contradicts a spec assumption (for example no prebuild for a supported Node + version, or every launcher kills detached children), stop and report to the architect before + Phase 2. Those facts change scope. + +--- + +### Phase 2: Shellper named-pipe transport with per-session secret + +**Dependencies**: Phase 1 + +#### Objective + +Shellpers listen and reconnect on Windows over named pipes, and other local users can neither +read from nor control them. POSIX connections stay byte-identical. + +#### Files to Create / Modify + +- `packages/codev/src/terminal/shellper-endpoint.ts` (new): resolve endpoint (POSIX + `/shellper-.sock`; Windows `\\.\pipe\codev-shellper--`), readiness + probe, stale-endpoint cleanup (no-op on Windows), log path decoupled from the endpoint. +- `packages/codev/src/terminal/session-manager.ts`: socket-file wait → endpoint readiness probe; + `isSocket`/`lstat` checks → endpoint probe; session id from the DB row, not the + `shellper-.sock` basename; attach fallback scan uses the DB registry on Windows; + `.sock`→`.log` string replace → `shellper-endpoint` log path; `socketDir` scope marker uses + `path.sep`. +- `packages/codev/src/terminal/shellper-protocol.ts`: optional HELLO `secret` field (sent only on + Windows). +- `packages/codev/src/terminal/shellper-main.ts`, `shellper-process.ts`: read the secret from the + environment, delete it from the env the PTY child inherits, write nothing to a connection + until a valid HELLO, drop on mismatch. +- `packages/codev/src/terminal/shellper-client.ts`: send the secret; verify WELCOME (session id, + pid). +- `packages/codev/src/agent-farm/db/migrations.ts`: nullable secret column on the session row in + `global.db` (always null on POSIX). +- `packages/codev/src/agent-farm/servers/tower-terminals.ts`, `agent-farm/commands/attach.ts`: + pass endpoint and secret through reconcile/attach. + +#### Deliverables + +- [ ] Endpoint abstraction; POSIX endpoint, log path and handshake unchanged. +- [ ] Windows secret: generated by Tower per session (crypto random), passed via environment, + never argv; persisted for reconnect; stripped before the PTY child starts. +- [ ] Unauthenticated connection: zero bytes written, frames ignored, connection dropped. +- [ ] Tests. + +#### Acceptance Criteria + +- [ ] Golden test: POSIX endpoint path, log path and HELLO/WELCOME bytes are identical to today. +- [ ] With the seam forced to `win32` (over a POSIX socket in unit tests): a client without or + with a wrong secret receives zero bytes, its frames have no effect, and it is dropped; the + correct secret attaches. +- [ ] The secret never appears in argv or in the PTY child's environment. +- [ ] Windows (`targeted` dispatch): a real `\\.\pipe\` shellper is spawned, attached, detached + and re-attached with replay. +- [ ] Windows (`targeted` dispatch, scenario 14): a second non-admin local user created on the + runner opens the pipe and receives zero bytes; its frames have no effect. +- [ ] POSIX suite and `check-types` green. + +#### Test Plan + +- Unit: endpoint resolution per platform; log path; secret gating; WELCOME verification + (wrong session id or pid → refuse); migration up on an existing DB. +- Windows CI: pipe round trip and reconnect; second-user access test. + +--- + +### Phase 3: Process lifecycle and introspection on Windows + +**Dependencies**: Phase 2 + +#### Objective + +The load-bearing guarantee holds on Windows: builders survive a Tower stop/start, reconnect +refuses reused PIDs, teardown leaves no descendants, orphans and husks are reaped, and Tower +start/stop work without `lsof`/`pgrep`. + +#### Files to Create / Modify + +- `packages/codev/src/lib/process-tree.ts` (new): `killSessionTree(rootPid, opts)`. POSIX: today's + group SIGTERM→SIGKILL. Windows: census snapshot of descendants (pid, ppid, creation time) → + `taskkill /T /F` → explicit kill of each recorded descendant whose creation time still matches + → tolerant of an already-dead root. +- `packages/codev/src/agent-farm/servers/process-census.ts`: provider interface; the POSIX + provider is today's `ps` code; a Windows provider running an inline + `powershell.exe -NoProfile -NonInteractive -Command` CIM `Win32_Process` query (JSON out: + pid, ppid, cmdline, creation time, working set). Stays async. A failure is reported, never an + empty census. +- Group-kill sites → `killSessionTree`: `terminal/pty-session.ts`, `terminal/session-manager.ts` + (orphan sweep, reaping), `agent-farm/servers/architect-session-holder.ts`, + `agent-farm/commands/cleanup.ts`, `commands/porch/checks.ts`. +- Start-time guard: `terminal/session-manager.ts`, `shellper-main.ts`, `shellper-process.ts`, + `agent-farm/servers/tower-terminals.ts`, `tower-instances.ts`, `tower-routes.ts`, + `analytics.ts`. On Windows, reconnect identity is the authenticated WELCOME from Phase 2; + sweeps get creation time from the census. +- `agent-farm/servers/shellper-husk-sweep.ts`: Windows census, same age race guard. +- `agent-farm/commands/tower.ts`, `agent-farm/utils/port.ts`, `agent-farm/db/tower-owner.ts`, + `agent-farm/servers/tower-routes.ts`: stop via a local-only HTTP shutdown request, then tree + kill as fallback; Tower PID from the owner lock and `/api/status`; port owner via + `Get-NetTCPConnection` only where still needed. +- `agent-farm/commands/tower.ts`: job escape. If Phase 1 found the launcher kills detached + children, start the daemon through CIM `Win32_Process.Create`; if that is blocked, refuse with + an error naming a supported launch path. +- Every detached spawn (Tower daemon, shellpers, helpers): `windowsHide: true`. + +#### Deliverables + +- [ ] `killSessionTree` and Windows census provider, with all group-kill sites migrated. +- [ ] Windows reconnect identity via WELCOME; PID-reuse refusal on Windows. +- [ ] Tower stop/start without `lsof`/`pgrep`; graceful shutdown path; job escape; hidden + consoles. +- [ ] Tests. + +#### Acceptance Criteria + +- [ ] POSIX: existing lifecycle tests pass unchanged; `killSessionTree` on POSIX issues exactly + today's signals (asserted). +- [ ] Unit (seam `win32`, injected exec): snapshot taken before kill; re-parented descendant + killed; dead root still clears snapshot descendants; creation-time mismatch skips the PID; + census failure surfaces as an error and a Tower log line. +- [ ] Windows (`targeted`): scenario 4 (builder survives `afx tower stop`/`start`, same agent + PID, replay, input works), scenario 5 (PID reuse refused, row cleaned), scenario 6 (no + descendants after kill, including re-parented and dead-root variants), scenario 7 (orphan + reaped on next start), scenario 3's stop/start half (owner lock released). +- [ ] POSIX suite and `check-types` green. + +#### Test Plan + +- Unit: CIM JSON parser (fixtures, including CRLF and empty output); kill-tree decision logic; + shutdown route is loopback-only. +- Windows CI: real node→bash→node trees; Tower restart survival. +- Owner machine: survival from Windows Terminal, PowerShell console, VS Code terminal (feeds the + PR-gate pass). + +--- + +### Phase 4: Git Bash, executable shim, and MSYS path resolution with doctor prerequisites + +**Dependencies**: Phase 1 + +#### Objective + +One resolver each for Git Bash, executable shims and MSYS paths, and a `codev doctor` that +reports the Windows prerequisites. This phase routes nothing yet; it builds and exposes the +pieces, so it is observable through doctor alone. + +#### Files to Create / Modify + +- `packages/codev/src/lib/git-bash.ts` (new): resolution order: `CLAUDE_CODE_GIT_BASH_PATH` / + config override is honored as the explicit override, standard install locations + (`%ProgramFiles%\Git\bin\bash.exe`, `%LocalAppData%\Programs\Git\bin\bash.exe`), then `PATH`. + `%SystemRoot%\System32\bash.exe` is always rejected. Returns path and Git version. +- `packages/codev/src/lib/executable.ts` (new): `which`-style PATH/PATHEXT lookup promoted from + `commands/bootstrap-docs.ts`, plus `resolveSpawnable(cmd)` that turns a `.cmd`/`.bat` shim into + `%COMSPEC% /d /s /c` form or its underlying `node `. `bootstrap-docs.ts` imports it. +- `packages/codev/src/lib/msys-path.ts` (new): native→POSIX (`C:\x` → `/c/x`), POSIX→native + (`/c/x` → `C:\x`), and the per-call opt-out env (`MSYS_NO_PATHCONV=1`, + `MSYS2_ARG_CONV_EXCL=*`). +- `packages/codev/src/commands/doctor.ts`: Windows section: Git for Windows (found, version, + bash path; never System32), jq (found or install hint), CRLF warning for user-authored + `postSpawn`/`devCommand` script files. +- `.gitattributes` (root): `eol=lf` for shipped `.sh` (forge concepts in `codev-skeleton/` and + `codev/`, templates). + +#### Deliverables + +- [ ] Three resolvers with unit tests; `bootstrap-docs` uses the shared lookup. +- [ ] Doctor Windows checks; missing Git Bash or jq is a named, actionable failure. +- [ ] LF pinning for shipped scripts. + +#### Acceptance Criteria + +- [ ] Resolver precedence tests (fake fs/env): override wins; install path before `PATH`; + System32 never returned even when first on `PATH`; nothing found → error naming Git for + Windows. +- [ ] Shim tests: `claude` → `claude.cmd` via PATHEXT; spawnable form is executable by + `CreateProcess`. +- [ ] MSYS round-trip table, including UNC and spaces. +- [ ] Doctor (seam `win32`): scenario 2 outputs (jq missing; only System32 bash) name the missing + prerequisite. On POSIX the doctor output is unchanged (snapshot). +- [ ] Windows (`targeted`): doctor reports the runner's Git Bash and jq. +- [ ] POSIX suite and `check-types` green. + +#### Test Plan + +Unit tests as above; one Windows dispatch running doctor and the resolver tests for real. + +--- + +### Phase 5: Launcher routing and shell roles + +**Dependencies**: Phases 3, 4 + +#### Objective + +Every plumbing launch site runs through the resolved Git Bash with correct path translation, +agent terminals spawn their harness through the shim resolver, and human shell terminals open +PowerShell. After this phase, `afx tower start`, spawn, forge, porch and `afx dev` work on +Windows. + +#### Files to Create / Modify + +- `packages/codev/src/lib/forge.ts`: concepts execute via ` -c` / `