Skip to content

Run kindex on Windows - #89

Open
VelitRicardo wants to merge 23 commits into
wandercom:mainfrom
VelitRicardo:windows-port
Open

VelitRicardo wants to merge 23 commits into
wandercom:mainfrom
VelitRicardo:windows-port

Conversation

@VelitRicardo

@VelitRicardo VelitRicardo commented Oct 20, 2026 •

Copy link
Copy Markdown

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 to platform_io or guarded by os.name == "nt".

Measured on Windows 11, Python 3.13, with a fresh HOME and TEMP per run:

passed failed errors
main (23490c5) 3040 609 120
this PR 3843 80 2
this PR + #88 (UTF-8 text I/O) 3847 77 2

On main, whole test modules that import fcntl fail 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 8 and 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 differences

Helper POSIX Windows
open_nofollow O_NOFOLLOW Refuses a symlink or junction both before and after opening, by comparing the opened handle with lstat. Raises the same ELOOP error.
lock / unlock flock msvcrt.locking on one byte far past the data. Exclusive, per descriptor, non-blocking when asked.
fsync_directory fsync of the directory No-op (NTFS journals the rename).
home / expanduser unchanged HOME wins over USERPROFILE, as in git. An MSYS HOME=/c/... is read as C:/....
null_device unchanged /dev/null maps to nul when a program (not a shell) passes it, e.g. --config /dev/null.
kill_tree killpg(SIGKILL) taskkill /T /F
essential_environment {} The system variables a closed child environment still needs (SYSTEMROOT, …).
posix_shell /bin/sh Git for Windows' sh.exe: the one beside the git on PATH, or the one in its install record. Never a bare bash from PATH, which can be WSL's.
shell_can_run shutil.which Also accepts a script given by path that Git's sh can run.

Behavior fixes, each its own commit

  • Child processes (actions._run_process). Windows cannot select() 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 returns None) is the same on both platforms.
  • Shell command lines (sim.command, the advocate command, reminder actions) are POSIX shell: kindex already splits them with shlex. On Windows they used to go to cmd.exe, so a quoted Windows path failed one way and an unquoted one failed the other. They now run under Git's sh. The command reaches sh through an environment variable: MSYS re-parses its own command line, turning a quoted \\ into \ and expanding globs and @files. Without Git they fall back to cmd.exe.
  • The degraded-event ledger cap no longer 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.
  • Code-map module keys use the POSIX relative path. The key feeds the node id hash and .kin/code-map.json, and a map built on Windows did not match one built on Linux.
  • The registered merge driver quotes the kin program. git runs it through sh, which split a path with spaces (on any OS) and dropped backslashes (on Windows).
  • A missing crontab reads as a failed crontab run instead of a traceback, --dry-run included.
  • The CLI's stdout/stderr use errors="replace". A Windows pipe's code page cannot encode ✓/✗, and kin doctor died after its first line. The encoding itself is unchanged.
  • Naive pre-epoch time bounds. since=1970-01-01 west of UTC raised Errno 22 from localtime() on Windows. Such a bound now uses the epoch's offset.
  • Audience inference (/work/, /code/) reads the project path in POSIX form.
  • The modern Claude hooks ship a package.json with "type": "module". Node takes a .ts file's module type from the nearest package.json, so one above the plugin declaring commonjs broke kindex.ts.

Tests

  • Two new modules (test_platform_io.py, test_run_process_portable.py) state the helpers' contracts and the _run_process contract using the running interpreter instead of sh, so Windows exercises them too.
  • Harness fixes that leave assertions unchanged:
    • Start posix_shell() instead of a literal /bin/sh.
    • Quote paths put into shell command lines. Unquoted, some negative tests passed vacuously on Windows.
    • Hold the product's locks with platform_io instead of fcntl.
    • Keep essential_environment() in closed child environments.
    • Read and write UTF-8 fixtures as UTF-8.
    • Look for kin.exe beside python.exe on Windows.
  • Five assertions are restated in a form both platforms meet. On POSIX each still checks the same value:
    • the snapshot path compared to its own field instead of a dict's repr;
    • session paths through normalize_project_path;
    • data_dir as project / "/tmp/...";
    • the scope path looked for in JSON-escaped form;
    • a descriptor leak detected by the lowest free descriptor instead of listing /dev/fd.

What still fails on Windows, and why

Tests Cause
supervisor_acceptance/test_health*, test_notifications* (30), test_desktop_session_identity (8) macOS-only feature: launchd, osascript, sendmail. The fakes are shebang scripts, which Windows cannot execute. The popup title walk is POSIX dir_fd code.
test_kinbase_*, test_admission_notification_contract (27) No Kinbase native binary on this machine.
test_kinbase_submissions oversized-payload cases (2 errors) pytest copies the test id into PYTEST_CURRENT_TEST; this parametrize id is longer than Windows' 32,767-character limit for an environment variable.
test_reminder_action_safety (4) They read MSYS PIDs ($$, $!), probe with os.kill(pid, 0) (a CTRL_C on Windows) or patch selectors. test_run_process_portable.py states the same contract portably.
test_mcp_lite scope replacement (4) They rename the open SQLite database, which Windows refuses outright, so the attack cannot be staged.
pathspec-magic directory, chmod(0) directory, 0600 mode (3) NTFS: no : or * in names, and no POSIX permission bits.
Cursor installer (1) The installed hooks are /bin/bash -lc …. Installing hooks for Windows hosts is a separate feature.

Notes for review

  • The commits are kept separate on purpose, one root cause each, with the test that covers it.
  • Windows-only branches are guarded by os.name == "nt". Two fixes deliberately change POSIX behavior too:
    • the merge-driver quoting, which fixes kin paths with spaces on any OS;
    • errors="replace" on CLI output, which affects any non-UTF-8 locale.
  • Running the suite on a Windows machine where %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. Point TEMP outside it.

Test plan

  • Full suite on Windows 11 / Python 3.13 (figures above).
  • New contract tests pass on Windows. They also run under POSIX semantics via monkeypatching where the behavior is Windows-only.
  • Linux CI (the test workflow on this PR).

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

VelitRicardo and others added 23 commits October 20, 2026 10:07
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>

@adaptcom adaptcom Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread tests/test_platform_io.py
@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")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/kindex/platform_io.py
import shutil
import signal
import stat
import subprocess

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/kindex/config.py
tmp = Path(tmp_name)
try:
try:
os.fchmod(fd, 0o600)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/kindex/config.py
view = view[os.write(fd, view):]


def _cap_degraded_ledger_windows(path: Path) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/kindex/platform_io.py
_RETRY_SECONDS = 0.01


def _is_link(info: os.stat_result) -> bool:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refusing every reparse point also refuses OneDrive/cloud placeholder files; restrict to st_reparse_tag symlink and mount-point tags.

Comment thread src/kindex/platform_io.py
fcntl = None
import msvcrt

__all__ = ["open_nofollow", "lock", "unlock", "fsync_directory", "home", "expanduser", "null_device", "kill_tree", "essential_environment", "posix_shell"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all omits shell_can_run, which sim.py and supervisor.py call; add it.

Comment thread tests/test_mcp_lite.py
# 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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ''.

Comment thread src/kindex/setup.py
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")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reimplements _find_kin_path; use shlex.join([*_kin_command_parts(_find_kin_path()), 'merge-kin']) so the two cannot drift.

Comment thread tests/test_scheduling.py
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant inner import; platform_io is already imported at module top.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant