Repository navigation
Run kindex on Windows - #89
VelitRicardo wants to merge 23 commits into
Conversation
os.O_NOFOLLOW does not exist on Windows, so every repo-local store open raised AttributeError there, and the getattr(os, "O_NOFOLLOW", 0) call sites silently dropped the symlink refusal. platform_io.open_nofollow keeps O_NOFOLLOW on POSIX; on Windows it refuses a symlink or reparse point at the final component before opening and again against the opened handle, raising the same ELOOP, and opens in binary mode so bytes match POSIX. On Windows (Python 3.13) this fixes 236 tests that failed on 23490c5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dream, subscription_review and supervisor imported fcntl at module load, so they (and everything importing them) failed on Windows; repo_memory and the degraded-ledger cap failed on first use; attention, the cron run lock and the scheduler state lock silently ran unlocked. All now use platform_io.lock/unlock: flock on POSIX, a one-byte msvcrt lock past the data on Windows, exclusive per descriptor and released on close. Non-blocking contention still raises BlockingIOError. The dream lock no longer leaks its descriptor when contended. budget.py keeps its own existing Windows path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
subscription_review fsyncs the parent directory after os.replace by opening it with os.open, which raises PermissionError on Windows, so every allowance write reported review_accounting_unavailable there. platform_io.fsync_directory keeps the fsync on POSIX and is a no-op on Windows, where NTFS journals the rename. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4eb584f switched dream's lock to platform_io without importing it, so acquiring the lock raised NameError. test_cycle_skips_when_locked took the lock with fcntl directly and could not run on Windows; it now uses platform_io and fails on the missing import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows Python ignores HOME and reads USERPROFILE, so Path.home() and expanduser disagreed with Git Bash/MSYS shells and with anything that isolates a run by setting HOME; a subprocess given only HOME could not even import kindex.config. platform_io.home() and platform_io.expanduser() honor HOME first on Windows (an MSYS-style /c/Users/me is read as C:/Users/me) and are exactly Path.home() and os.path.expanduser on POSIX. Every call site now goes through them, and test_kindex_resolves_home_only_through_platform_io keeps it that way. Tests that isolated home by patching Path.home patch platform_io.home instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests, hooks and scripts pass --config /dev/null for "no config". A shell such as Git Bash rewrites that argument to nul, but a program launching kin directly does not, and on Windows the path resolved to a missing C:\dev\null. platform_io.null_device maps /dev/null to os.devnull on Windows and leaves every path unchanged on POSIX. On Windows this fixes 55 tests in the transfer, capture and scope suites. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_run_process multiplexed the child's pipes with selectors, which on Windows only accept sockets (WinError 10038), and killed its process group with os.killpg, which Windows lacks. On Windows it now drains the pipes and feeds the prompt from threads and kills the process tree with taskkill /T; the POSIX path is unchanged. platform_io.kill_tree is killpg(SIGKILL) on POSIX and replaces the other killpg call too. A child given a hand-built environment without SYSTEMROOT cannot start its socket layer on Windows (WinError 10106), so the subscription review worker could not run there. platform_io.essential_environment() returns the system variables a closed environment must keep on Windows (never per-user ones) and nothing on POSIX; the worker environment and the closed-environment contract tests include it. test_run_process_portable.py states the runner contract (tree killed on timeout, background child does not hold the run, large prompt, max_bytes, undecodable output) with the interpreter instead of POSIX sh, so it runs on every platform. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_relative_to_root compared Windows-absolute paths lexically, so on a Windows host a symlink inside the repo that points outside it was exported as a repo file. The lexical branch now applies only when a POSIX host reads Windows paths; on Windows the path is resolved like on POSIX and the escape is refused. Found by test_export_understand_anything_rejects_symlink_outside_repo, which used to skip on Windows without symlink privilege. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The acceptance sandbox isolates PATH with symlinks to git, kin, sh, bash, env and python. Windows looks a program's DLLs up beside the path it was started from, so a symlinked native tool exits with STATUS_DLL_NOT_FOUND, and a symlinked venv launcher loses its pyvenv.cfg. On Windows the sandbox now puts each tool's own directory on PATH and keeps the variables a Windows child needs. POSIX is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Configured commands (sim.command, the advocate command, reminder actions) are POSIX shell: sim.py splits them with shlex to check the executable exists, then ran them with shell=True, which on Windows is cmd.exe. A Windows path failed one way or the other: quoted, cmd.exe could not read it; unquoted, shlex dropped its backslashes. _run_process now runs a shell command line under Git for Windows' sh.exe, found beside the git on PATH or from its install record, never a bare sh/bash from PATH (which can be WSL's). Without Git it keeps cmd.exe. The line reaches sh through the environment, because MSYS re-parses its own command line: a quoted \ becomes \, and globs and @files expand. The reminder test that touches a marker now quotes the path it puts in the command line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The code adapter named modules with str(path.relative_to(root)), which on Windows is Assets\Player.prefab. That path is the module's title, its node id hash and the key later lookups use, and it lands in .kin/code-map.json, which is shared through git: a map built on Windows did not match one built on Linux. Every relative module path is now as_posix(), as the file walk already was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Store._activity_bound reads a naive time as local with astimezone(). Windows' localtime() rejects instants before the epoch, so a bound such as since=1970-01-01 west of UTC raised OSError (Errno 22) from activity_since, changelog and the MCP tools built on them. Such a bound now takes the epoch's local offset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_infer_audience looks for /work/, /code/ and /personal/ in the project path, but read it with str(), which on Windows separates with backslashes: every project came out private. It now reads the path in POSIX form. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cap kept a handle on the ledger and os.replace'd the trimmed copy over it, then read appends that landed in between from that handle. Windows cannot replace a file that is open: the replace failed, the error was swallowed, and the ledger grew without bound. On Windows the cap now moves the ledger aside first. The move fails while an appender holds it (the cap is declined, as when its lock is busy); once it succeeds no append can reach the moved file, so it is read whole. The trimmed ledger is renamed back without replacing, and a ledger appenders recreated meanwhile is folded in after it. A run that cannot finish appends what it holds, and a moved-aside file a crash left is read first next time: nothing is dropped. POSIX keeps its replace-and-recover path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows a pipe takes the ANSI code page (cp1252), which has no ✓/✗ and none of most non-Latin node text: kin doctor died with UnicodeEncodeError after its first line when its output was captured. The CLI now prints a replacement character instead. The encoding is unchanged, so a reader decoding in the code page still agrees. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
git runs merge.kindex.driver through sh, but the driver line held the kin path unquoted: sh split a path with spaces, and on Windows dropped every backslash, so the merge failed with "command not found". The program is now shlex-quoted. The driver test's own line is quoted the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
setup-cron, the scheduler's interval repack and setup-cron --uninstall
ran crontab directly; where it does not exist (Windows) each raised
FileNotFoundError and the CLI printed a traceback, even for --dry-run.
They now go through run_crontab, which answers a missing crontab with
exit 127 and the reason on stderr, the failure every caller already
reports ("no crontab", "Failed to add crontab: ...").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before running sim.command or the advocate command, the supervisor and sim check that its program exists with shutil.which. On Windows which refuses a file without a PATHEXT extension, so a provider script given by path (#!/bin/sh) was reported command_unavailable although Git's sh, which now runs these command lines, starts it. platform_io.shell_can_run keeps which's verdict and, on Windows with Git's sh, also accepts an existing file named by path. The simulacrum test quotes the script path it puts in the command line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reload-helper and plugin-manifest tests started /bin/sh, which Windows lacks; they now use platform_io.posix_shell(), still /bin/sh on POSIX, and the helper's PATH is joined with os.pathsep. The clone-config tests put marker paths into shell command lines unquoted, so on Windows sh dropped their backslashes: the positive control could never see its marker and the negative ones passed vacuously; the paths are quoted. The pasted-remedy test ran a bare "bash", which Windows resolves to WSL's in System32 before PATH; it uses Git's bash there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two contention tests imported fcntl, so neither module could even be collected on Windows, and an fcntl lock would not exclude the product there anyway: it locks with platform_io.lock. They now hold the lock the product's way, non-blocking and exclusive as before. Their closed child environments keep platform_io.essential_environment(); without SYSTEMROOT a Windows child cannot start its socket layer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- sessions: the v12 migration stores normalize_project_path("/project"),
c:\project on Windows; the store-level tests now seed and filter with
that identity instead of the POSIX literal.
- release_metadata, graph_transfer: read and write UTF-8 files as UTF-8
(Windows defaults to cp1252); the console-script test looks for
kin.exe beside python.exe on Windows.
- v8_config_seam: match a path literally (re.escape); a Windows path
is not a valid regex.
- mcp_lite: the no-network guard lets loopback connects through; Windows'
asyncio builds its self-pipe with one.
- implicit_scope: on Windows the closed environment's POSIX PATH named no
git, so the product could not confirm the repository; it gets git's
directory and the essential variables there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Five assertions held POSIX spellings of what the product records: - snapshots: the changelog's snapshot path is compared to its own details field, not searched for in the dict's repr, where Windows backslashes are doubled; migrated session paths are compared with normalize_project_path(), which the migration stores (c:\project\a on Windows). - project_config: data_dir /tmp/... has no drive on Windows, so it is the project drive's \tmp; expected as project / "/tmp/...", which is /tmp/... itself on POSIX. - integrations_review: the session scope is embedded as JSON, so the project path is looked for in its JSON-escaped form. - prime_surfaces: a leaked descriptor is detected by the lowest free descriptor rising, not by listing /dev/fd, which Windows lacks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Node takes a .ts file's module type from the nearest package.json up the tree. The plugin's hooks shipped none, so a package.json above the checkout or the installed plugin declaring "type": "commonjs" made Node load kindex.ts as CommonJS, and it failed on its first import. hooks/package.json now declares "type": "module"; the installer copies it with the rest of the plugin and the wheel includes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Confidence Score: 2/5
Summary
Windows port via a new platform_io seam; design is sound and POSIX paths are largely unchanged, but the Linux suite (Python 3.12) now has 7 PR-caused failures, and the Windows ledger cap breaks on Python <3.13.
Important Files Changed
| File | Overview |
|---|---|
| src/kindex/platform_io.py | New seam: nofollow open, msvcrt lock, home/expanduser, kill_tree, Git sh discovery |
| src/kindex/actions.py | Windows _run_process variant with threads, taskkill, Git-sh runner via env var |
| src/kindex/config.py | platform_io home/expanduser; Windows degraded-ledger cap via move-aside (unguarded os.fchmod) |
| src/kindex/cli.py | errors=replace on stdout/stderr; run_crontab; platform_io expanduser |
| src/kindex/setup.py | run_crontab fallback; merge driver quoted with shlex.join (duplicates _find_kin_path) |
| src/kindex/code_map.py | Resolve Windows paths on Windows instead of lexical relative_to |
| src/kindex/adapters/code.py | Module keys use as_posix() for cross-platform code-map ids |
| src/kindex/store.py | open_nofollow for migration lock; pre-epoch naive bound fallback |
| src/kindex/sim.py | shell_can_run and platform_io.expanduser for sim/advocate commands |
| src/kindex/supervisor.py | platform_io lock/open_nofollow; shell_can_run in preflight |
| src/kindex/subscription_review.py | platform_io lock, fsync_directory, kill_tree, essential_environment |
| src/kindex/scheduling.py | Blocking platform_io lock; run_crontab |
| src/kindex/dream.py | platform_io lock; closes fd on contention |
| src/kindex/attention.py | platform_io non-blocking lock replaces fcntl fallback |
| src/kindex/daemon.py | Cron lock through platform_io |
| src/kindex/repo_memory.py | open_nofollow + platform_io lock for artifact lock |
| src/kindex/project_store.py | open_nofollow for .gitignore append; platform_io home |
| src/kindex/ingest.py | Audience inference reads POSIX form of project path |
| src/kindex/sessions.py | normalize_project_path via platform_io.expanduser |
| src/kindex/supervisor_health.py | open_nofollow and platform_io home |
| src/kindex/supervisor_health_activity.py | platform_io.home for agent state roots |
| src/kindex/supervisor_display.py | platform_io.home for Claude root and ~ shortening |
| src/kindex/mcp_lite.py | platform_io.expanduser in repo scope |
| src/kindex/mcp_server.py | platform_io.expanduser for profile data_dir compares |
| src/kindex/kinbase.py | platform_io.expanduser for repo roots |
| src/kindex/routing.py | platform_io.expanduser for cwd/profile roots |
| src/kindex/reminders.py | platform_io.expanduser for codex home and cwd |
| src/kindex/secret_mask.py | Default resolver uses platform_io.expanduser |
| src/kindex/snapshots.py | platform_io.home for XDG state fallback |
| src/kindex/analytics.py | platform_io.home for archive db candidate |
| src/kindex/vectors.py | platform_io.expanduser in project path normalization |
| src/kindex/adapters/claude_web.py | DEFAULT_DIR via platform_io.home |
| src/kindex/adapters/codex_sessions.py | platform_io.expanduser availability check |
| src/kindex/adapters/conversations.py | platform_io.expanduser for --directory |
| src/kindex/adapters/sessions.py | platform_io.expanduser availability check |
| src/kindex/claude_modern/hooks/package.json | Declares hook modules ESM; copied by copytree install |
| tests/test_platform_io.py | New contract tests; six fail on Linux because os.name is monkeypatched |
| tests/test_run_process_portable.py | Interpreter-based _run_process contract tests; pass on Linux |
| tests/test_cli_output_encoding.py | ASCII-strict stdout must not crash CLI |
| tests/test_hook_scope_fail_open.py | Three tests for the Windows ledger cap, run on every platform |
| tests/test_mcp_lite.py | Loopback connects allowed; ip_address() raises on hostnames |
| tests/test_graph_transfer.py | kin executable suffix derived from sys.executable (breaks for python3.12) |
| tests/test_kin_merge.py | Quoted driver; new quoting test |
| tests/test_scheduling.py | posix_shell harness; redundant inner platform_io import |
| tests/test_setup.py | run_crontab missing-binary test; platform_io.home patch |
| tests/test_cron_reliability.py | Patch platform_io.home instead of Path.home |
| tests/test_clone_config_authority.py | Quoted markers; Git bash on Windows |
| tests/test_claude_install.py | posix_shell harness; package.json type assertion |
| tests/test_sessions.py | normalize_project_path for expected identities |
| tests/test_snapshots.py | Compare details.path field; normalized project paths |
| tests/test_prime_surfaces.py | Descriptor leak via lowest free fd |
| tests/test_project_config.py | data_dir expectation via project / '/tmp/...' |
| tests/test_integrations_review.py | Scope path matched in JSON-escaped form |
| tests/test_implicit_scope.py | Windows PATH keeps git dir only |
| tests/test_ollama_review_contract.py | platform_io lock; essential env in child |
| tests/test_v8_ledger_race.py | platform_io lock replaces fcntl |
| tests/test_v8_config_seam.py | re.escape on path match |
| tests/test_dream.py | platform_io lock in contention test |
| tests/test_reminder_action_safety.py | Quoted marker path |
| tests/test_sim.py | Quoted sim command path |
| tests/test_release_metadata.py | UTF-8 reads |
| tests/test_admission_notification_contract.py | essential_environment in closed env |
| tests/test_historical_native_contract.py | essential_environment in closed env |
| tests/test_sim_queue_contract.py | essential_environment in closed env |
| tests/supervisor_acceptance/test_supervisor_acceptance.py | Windows PATH uses tool dirs instead of symlinks |
Findings
- CHANGELOG [Unreleased] has no entry for Windows support or the two cross-platform behavior changes (merge-driver quoting, CLI errors='replace').
Reviewed 23490c5...98254f6 · ↻ Re-run review · View in Adapt
| @pytest.mark.parametrize("git", ["cmd/git.exe", "mingw64/bin/git.exe", "ucrt64/bin/git.exe"]) | ||
| def test_posix_shell_is_the_sh_of_the_git_on_path(tmp_path, monkeypatch, git): | ||
| install = _git_tree(tmp_path / "Git", git, "usr/bin/sh.exe") | ||
| monkeypatch.setattr(platform_io.os, "name", "nt") |
There was a problem hiding this comment.
Patching os.name to 'nt' makes pathlib build WindowsPath, which cannot be instantiated on POSIX: six tests here fail on Linux 3.12; gate Windows branches on a patchable module flag instead.
| import shutil | ||
| import signal | ||
| import stat | ||
| import subprocess |
There was a problem hiding this comment.
Top-level import subprocess reaches config.py's import chain; test_budget_concurrency fakes msvcrt so subprocess imports _winapi and fails on Linux. Import subprocess lazily in kill_tree.
| tmp = Path(tmp_name) | ||
| try: | ||
| try: | ||
| os.fchmod(fd, 0o600) |
There was a problem hiding this comment.
os.fchmod does not exist on Windows before Python 3.13 (requires-python >=3.10); it raises after the ledger was moved aside, leaving it stranded. Guard with hasattr like store.py.
| view = view[os.write(fd, view):] | ||
|
|
||
|
|
||
| def _cap_degraded_ledger_windows(path: Path) -> None: |
There was a problem hiding this comment.
Any failure after the move-aside leaves entries only in .capping, invisible to readers until the ledger regrows past the cap; restore aside to path on the error path.
| _RETRY_SECONDS = 0.01 | ||
|
|
||
|
|
||
| def _is_link(info: os.stat_result) -> bool: |
There was a problem hiding this comment.
Refusing every reparse point also refuses OneDrive/cloud placeholder files; restrict to st_reparse_tag symlink and mount-point tags.
| fcntl = None | ||
| import msvcrt | ||
|
|
||
| __all__ = ["open_nofollow", "lock", "unlock", "fsync_directory", "home", "expanduser", "null_device", "kill_tree", "essential_environment", "posix_shell"] |
There was a problem hiding this comment.
all omits shell_can_run, which sim.py and supervisor.py call; add it.
| # Windows' asyncio builds its self-pipe with a loopback connect; that is | ||
| # no provider. Every other address is. | ||
| host = address[0] if isinstance(address, tuple) else None | ||
| if host is not None and ipaddress.ip_address(host).is_loopback: |
There was a problem hiding this comment.
ipaddress.ip_address raises ValueError for a hostname before forbidden() records the attempt, so a swallowed hostname connect now passes; catch ValueError and treat as forbidden.
|
|
||
| def test_installed_console_script_transfers_and_reopens_for_recall(tmp_path): | ||
| executable = Path(sys.executable).with_name("kin") | ||
| executable = Path(sys.executable).with_name("kin" + Path(sys.executable).suffix) # kin.exe on Windows |
There was a problem hiding this comment.
Path(sys.executable).suffix is '.12' for /usr/bin/python3.12, so the test looks for kin.12; use '.exe' if os.name == 'nt' else ''.
| driver_cmd = f"{_find_kin_path()} merge-kin %O %A %B %P" | ||
| # git runs the driver through sh: quote the program, whose path may hold | ||
| # spaces or (on Windows) backslashes that sh would drop. | ||
| executable = shutil.which("kin") |
There was a problem hiding this comment.
Reimplements _find_kin_path; use shlex.join([*_kin_command_parts(_find_kin_path()), 'merge-kin']) so the two cannot drift.
| plist.write_text("300\n") | ||
| lock = lock or tmp_path / "reload.lock" | ||
| env = {**os.environ, "PATH": f"{bin_dir}:{os.environ['PATH']}"} | ||
| from kindex import platform_io |
There was a problem hiding this comment.
Redundant inner import; platform_io is already imported at module top.
Summary
This PR makes kindex run on Windows. It covers the repo-local store,
kindex-lite --repo, the CLI, the scheduler and the supervisor. Linux and macOS behavior is unchanged, apart from two small fixes that apply everywhere (see Notes for review). Every Windows-specific path is either confined toplatform_ioor guarded byos.name == "nt".Measured on Windows 11, Python 3.13, with a fresh
HOMEandTEMPper run:main(23490c5)On
main, whole test modules that importfcntlfail to collect, so its totals cover fewer tests. This PR on its own leaves two extra failures, the JSONL import of U+2028/U+2029 content, which needs the UTF-8 change. A third run-to-run difference is a test that times out under-n 8and passes when run alone. Every remaining failure is environmental or a macOS-only feature (listed below), not a port defect. No test is skipped or marked xfail by this PR.What changes
platform_io: one place for the platform differencesopen_nofollowO_NOFOLLOWlstat. Raises the sameELOOPerror.lock/unlockflockmsvcrt.lockingon one byte far past the data. Exclusive, per descriptor, non-blocking when asked.fsync_directoryfsyncof the directoryhome/expanduserHOMEwins overUSERPROFILE, as in git. An MSYSHOME=/c/...is read asC:/....null_device/dev/nullmaps tonulwhen a program (not a shell) passes it, e.g.--config /dev/null.kill_treekillpg(SIGKILL)taskkill /T /Fessential_environment{}SYSTEMROOT, …).posix_shell/bin/shsh.exe: the one beside thegitonPATH, or the one in its install record. Never a barebashfromPATH, which can be WSL's.shell_can_runshutil.whichshcan run.Behavior fixes, each its own commit
actions._run_process). Windows cannotselect()on pipes and has no process groups, so threads drain the pipes and a timeout kills the whole tree. The contract (exit code, output bounds, grace period, a timed-out run returnsNone) is the same on both platforms.sim.command, the advocate command, reminder actions) are POSIX shell: kindex already splits them withshlex. On Windows they used to go tocmd.exe, so a quoted Windows path failed one way and an unquoted one failed the other. They now run under Git'ssh. The command reachesshthrough an environment variable: MSYS re-parses its own command line, turning a quoted\\into\and expanding globs and@files. Without Git they fall back tocmd.exe.os.replaces over a file that is still open, which Windows refuses (the error was swallowed and the ledger grew without bound). On Windows the ledger is first moved aside. If an appender holds it, the cap is declined, the same as when the cap lock is busy. Appends that recreate the ledger in the meantime are folded back in. R4.1 (no silent loss) holds, and three new tests cover the Windows path on every platform..kin/code-map.json, and a map built on Windows did not match one built on Linux.kinprogram. git runs it throughsh, which split a path with spaces (on any OS) and dropped backslashes (on Windows).crontabreads as a failed crontab run instead of a traceback,--dry-runincluded.errors="replace". A Windows pipe's code page cannot encode✓/✗, andkin doctordied after its first line. The encoding itself is unchanged.since=1970-01-01west of UTC raisedErrno 22fromlocaltime()on Windows. Such a bound now uses the epoch's offset./work/,/code/) reads the project path in POSIX form.package.jsonwith"type": "module". Node takes a.tsfile's module type from the nearestpackage.json, so one above the plugin declaringcommonjsbrokekindex.ts.Tests
test_platform_io.py,test_run_process_portable.py) state the helpers' contracts and the_run_processcontract using the running interpreter instead ofsh, so Windows exercises them too.posix_shell()instead of a literal/bin/sh.platform_ioinstead offcntl.essential_environment()in closed child environments.kin.exebesidepython.exeon Windows.normalize_project_path;data_dirasproject / "/tmp/...";/dev/fd.What still fails on Windows, and why
supervisor_acceptance/test_health*,test_notifications*(30),test_desktop_session_identity(8)osascript,sendmail. The fakes are shebang scripts, which Windows cannot execute. The popup title walk is POSIXdir_fdcode.test_kinbase_*,test_admission_notification_contract(27)test_kinbase_submissionsoversized-payload cases (2 errors)PYTEST_CURRENT_TEST; this parametrize id is longer than Windows' 32,767-character limit for an environment variable.test_reminder_action_safety(4)$$,$!), probe withos.kill(pid, 0)(a CTRL_C on Windows) or patchselectors.test_run_process_portable.pystates the same contract portably.test_mcp_litescope replacement (4)chmod(0)directory,0600mode (3):or*in names, and no POSIX permission bits./bin/bash -lc …. Installing hooks for Windows hosts is a separate feature.Notes for review
os.name == "nt". Two fixes deliberately change POSIX behavior too:kinpaths with spaces on any OS;errors="replace"on CLI output, which affects any non-UTF-8 locale.%TEMP%sits inside a git repository (for example, a home directory under version control) gives false "dubious ownership" failures, because the repo-local store walks up to that repository. PointTEMPoutside it.Test plan
testworkflow on this PR).🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.