feat: Windows support (full parity) via build-tag platform seams#83
Closed
nachiket0310 wants to merge 4 commits into
Closed
feat: Windows support (full parity) via build-tag platform seams#83nachiket0310 wants to merge 4 commits into
nachiket0310 wants to merge 4 commits into
Conversation
flow was macOS-only by design — no build tags, no runtime.GOOS
branching, and an entirely AppleScript-based session-spawn layer. This
brings it to full parity on macOS, Windows, and Linux with Claude Code,
isolating every OS-specific behavior behind //go:build seams so the
Unix code paths are byte-identical to before.
Platform seams (Unix files = original logic relocated; Windows files
compile only under GOOS=windows):
- internal/app/proc_{unix,windows}.go — setDetached() + processAlive()
(Setsid + signal-0 on Unix; CREATE_NEW_PROCESS_GROUP|DETACHED_PROCESS
+ OpenProcess/GetExitCodeProcess on Windows).
- internal/harness/claude/ps_{unix,windows}.go — runPS() feeds
LiveSessionIDs (ps -axo vs. Get-CimInstance Win32_Process).
- internal/spawner/shellquote_{unix,windows}.go — POSIX vs. PowerShell
quoting for the launch command.
- internal/winterm — Windows Terminal (wt.exe) backend; passes the
command via PowerShell -EncodedCommand (base64/UTF-16LE) to avoid
wt/PowerShell quoting and newline pitfalls. spawner.Detect() defaults
to it on Windows (never iTerm).
- EncodeCwd: Windows path chars (\, :) mapped to '-', gated on
runtime.GOOS so Unix encoding is unchanged (':' is a legal Linux
filename char — mapping it everywhere would break transcript
resolution for existing Unix users).
- edit.go: $EDITOR falls back to notepad on Windows, vi elsewhere.
Distribution, CI, docs:
- CI: windows-latest test job + a 5-target GOOS cross-compile gate.
- Release: build flow-windows-{amd64,arm64}.exe + checksums.
- Makefile build-windows target; golang.org/x/sys promoted to direct.
- Skill: Windows Task Scheduler guidance for the owner scheduler.
- Scope statements updated in README, CONTRIBUTING, CLAUDE.md;
docs/windows-support-plan.md added.
hookCommand is UNCHANGED ("flow hook session-start") — no existing
installs are orphaned.
Verified: go test ./..., go vet, and all 5 cross-compile targets green;
the full headless + interactive surface exercised on Windows 10 Pro
(19045, amd64) hardware with zero defects.
Refs #81
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AUX (like CON, PRN, NUL, COM1-9, LPT1-9) is a reserved device name on Windows, so git cannot check out a file named aux.go onto a Windows filesystem — `git clone` and the windows-latest CI job both fail at checkout with "invalid path". A Mac-side GOOS=windows cross-compile can't catch this (it only reads the file); only a real Windows checkout does, which the new windows-latest job did immediately. Renamed internal/app/aux.go -> auxfiles.go and aux_test.go -> auxfiles_test.go. Transparent to Go (package app, no filename references); build, vet, and tests unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The windows-latest CI job surfaced three internal/app test failures that are test-harness assumptions, not flow bugs (the Windows binary passed the same paths on real hardware): - TestCmdDoAutoLaunchesDetached asserted the auto-run log path contains 'tasks/auto-task/auto-runs/' with forward slashes; filepath.Join yields backslashes on Windows. Now normalizes separators — still runs on Windows. - TestCmdDoPropagatesFlowRootEnv and TestCmdDoWithFile pin the iTerm backend and inspect its osascript spawn string. iTerm's AppleScript escaping doubles the backslashes in Windows temp paths, so the assertions miss. That backend is never selected on Windows (Detect -> winterm); these now skip on Windows. FLOW_ROOT propagation on Windows is covered by internal/winterm TestSpawnTabArgvAndScript; --with-file injection is backend-agnostic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The windows-latest job exposed ~25 test-harness assumptions (the Windows binary itself passed these paths on real hardware). Root causes: - HOME isolation: tests set $HOME for a temp home, but os.UserHomeDir() reads %USERPROFILE% on Windows (ignoring $HOME), so skill/hook/ transcript reads and writes escaped to the real profile. Added a shared setTestHome() that sets both; routed withTempHome, initTempFlowRoot, and the e2e/transcript sites through it. Fixes the skill/init/version/transcript/e2e batch (~20 tests). - spawner: TestDetectFromEnv, TestDetectFlowTermInvalidFallsThrough, and TestShellQuoteParity assert macOS detection / POSIX quote parity; both are GOOS-specific now. Skipped on Windows and added TestDetectDefaultsToWinTermOnWindows for positive Windows coverage. - EncodeCwd: the Unix colon-preservation case is asserted off-Windows only (on Windows ':' maps to '-'). - edit: TestCmdEditPlaybook used /usr/bin/true; now a cmd no-op on Windows. Also fixed one genuine latent bug: TestMigrationHarnessSurvivesSession- InvariantRebuild left a *sql.Rows open, which on Windows kept flow.db locked and failed t.TempDir cleanup. Now closes the rows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
flow was macOS-only by design — no build tags, no
runtime.GOOSbranching, and an entirely AppleScript-based session-spawn layer. This PR brings it to full parity on macOS, Windows, and Linux with Claude Code, isolating every OS-specific behavior behind//go:buildseams so the Unix code paths are byte-identical to before.📊 Interactive report (port design, Mac/Linux-intact proof, full Windows test results): https://claude.ai/code/artifact/74c7c8f6-89ae-4e5b-b1c5-de85150c4c78 (Claude Code artifact)
Closes #81.
Why
We want flow usable on Windows. The architecture was already portable (pure-Go SQLite/no CGO,
filepath.Join+os.UserHomeDir()throughout, a cleanharness.Harnessinterface, andFLOW_TERM=bgbackground mode that bypasses terminals). What blocked Windows was two hard compile errors (syscall.Setsid) plus a handful of Unix runtime assumptions (ps, AppleScript backends, the iTerm fallback, POSIX shell quoting,/-only path encoding).How — platform seams
Standard Go build-tag pattern. Unix files contain the original logic, just relocated; the Windows siblings compile only under
GOOS=windows. The overridable test seams (processAlive,claude.PSRunner, backendRunnervars) are preserved, so the existing test suite is untouched.!windows)proc_unix.go—Setsid, signal-0proc_windows.go—CREATE_NEW_PROCESS_GROUP|DETACHED_PROCESS,OpenProcess/GetExitCodeProcessLiveSessionIDs)ps_unix.go—ps -axops_windows.go—Get-CimInstance Win32_Processshellquote_unix.go— POSIXshellquote_windows.go— PowerShellinternal/winterm—wt.exe+ PowerShell-EncodedCommandEncodeCwd/ . _→-(unchanged)\ :→-(runtime.GOOS-gated)$EDITORfallbackvinotepadspawner.Detect()defaults to Windows Terminal on Windows (never iTerm);wintermpasses the command via base64/UTF-16LE-EncodedCommandto sidestepwt.exe/PowerShell quoting and newline pitfalls.hookCommandis UNCHANGED (flow hook session-start) — no existing installs are orphaned (per CONTRIBUTING's callout).Mac & Linux unchanged
The Unix paths are the same logic, only relocated. One latent regression was caught and fixed: the first cut of
EncodeCwdmapped:/\→-unconditionally, which would have re-encoded Linux cwds containing a:(a legal filename char) and silently brokenflow transcript/flow do --herefor existing Linux users. It's now gated to Windows, with a test pinning that:is preserved on Unix.!windowsfiles as macOSTest plan
make test,go vet ./..., and all 5 cross-compile targets (darwin/linux/windows × arches) green locally; CI now gates the same matrix (windows-latestjob + cross-compile job).flow init~/.flowEncodeCwd~/.claude/projectsnamingC:\…→C--Users-…), confirmed viaflow transcriptflow hook session-starthookSpecificOutputJSONflow do --autoDETACHED_PROCESS,OpenProcesslivenessflow done→ completedflow doneflow do(interactive)winterm.SpawnTab,-EncodedCommandwt.exetab +claude --session-idlaunchedflow dops_windows.goCIM scan--force")flow do --hereKnown follow-ups (not blockers)
wintermtab focus returns(false, nil)—wt.exeexposes no per-tab process query, so a duplicateflow dosurfaces "running elsewhere" instead of focusing the existing tab. Tracked for a follow-up..jsonluntil a graceful/exit, soflow transcripton a live interactive session returns nothing (the--autotranscript works because that session has exited). This is Claude Code flush behavior, not flow.See
docs/windows-support-plan.mdfor the full phased plan and open questions.🤖 Generated with Claude Code