feat(windows): enable MCP mode and telemetry on Windows#172
feat(windows): enable MCP mode and telemetry on Windows#172JF10R wants to merge 8 commits intojustrach:mainfrom
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3fa8ff4089
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
src/telemetry.zig
Outdated
| const cmd = std.fmt.bufPrint(&cmd_buf, "curl -sf -X POST {s} -H 'Content-Type: application/json' --data-binary @{s} >/dev/null 2>&1 && : > {s}", .{ CLOUD_URL, path, path }) catch return; | ||
| var child = std.process.Child.init(&.{ "/bin/sh", "-c", cmd }, std.heap.page_allocator); |
There was a problem hiding this comment.
Avoid shell interpolation for telemetry upload path
Building a /bin/sh -c command with {s}-interpolated path reintroduces command injection and command-breakage risks that the previous argv-based curl call avoided. path is derived from environment/root-derived values (HOME/USERPROFILE and project location), so shell metacharacters or spaces in those paths can execute unintended commands or make telemetry sync fail in production; this should pass arguments directly via Child.init argv (without a shell) as before.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Enables Windows support for codedb’s MCP mode and telemetry by reducing stack pressure, adding Windows-specific environment/path handling, and gating unsupported HTTP server mode on Windows.
Changes:
- Introduce
compat.path_buf_sizeand apply it to stack-allocated path buffers across watcher/MCP/telemetry/main. - Heap-allocate large data structures to avoid Windows stack overflows (watcher
EventQueue, index frequency-table buffers) and addnoinline/never_inlinebarriers for MCP handlers. - Add
HOME→USERPROFILEfallback in multiple places and blockserveon Windows with a guided error.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/compat.zig | Adds path_buf_size to cap stack path buffers on Windows. |
| src/watcher.zig | Switches FsEvent buffers to path_buf_size; heap-allocates EventQueue with init/deinit. |
| src/tests.zig | Updates watcher queue tests to use EventQueue.init/deinit. |
| src/index.zig | Heap-allocates frequency-table buffers; refactors finishFrequencyTable signature. |
| src/main.zig | Adds is_windows, blocks serve on Windows, uses path_buf_size, updates watcher queue ownership. |
| src/mcp.zig | Uses path_buf_size, adds HOME/USERPROFILE fallback, adds inlining controls, sets cwd for indexing child process. |
| src/telemetry.zig | Uses path_buf_size; changes cloud sync execution strategy for Windows/POSIX. |
| src/snapshot.zig | Adds HOME → USERPROFILE fallback for secondary snapshot path. |
| build.zig | Sets Windows executable stack size to 8MB. |
src/telemetry.zig
Outdated
| if (comptime @import("builtin").os.tag == .windows) { | ||
| // On Windows, use cmd.exe to run curl and truncate the file | ||
| var cmd_buf: [2048]u8 = undefined; | ||
| const cmd = std.fmt.bufPrint(&cmd_buf, "curl -sf -X POST {s} -H \"Content-Type: application/json\" --data-binary @{s} >NUL 2>&1 && type nul > {s}", .{ CLOUD_URL, path, path }) catch return; | ||
| var child = std.process.Child.init(&.{ "cmd.exe", "/C", cmd }, std.heap.page_allocator); | ||
| child.stdin_behavior = .Ignore; | ||
| child.stdout_behavior = .Ignore; | ||
| child.stderr_behavior = .Ignore; | ||
| _ = child.spawn() catch return; | ||
| } else { | ||
| var cmd_buf: [2048]u8 = undefined; | ||
| const cmd = std.fmt.bufPrint(&cmd_buf, "curl -sf -X POST {s} -H 'Content-Type: application/json' --data-binary @{s} >/dev/null 2>&1 && : > {s}", .{ CLOUD_URL, path, path }) catch return; | ||
| var child = std.process.Child.init(&.{ "/bin/sh", "-c", cmd }, std.heap.page_allocator); | ||
| child.stdin_behavior = .Ignore; | ||
| child.stdout_behavior = .Ignore; | ||
| child.stderr_behavior = .Ignore; | ||
| _ = child.spawn() catch return; | ||
| } |
There was a problem hiding this comment.
syncToCloud now constructs a shell command string (both cmd.exe /C on Windows and /bin/sh -c elsewhere) that interpolates path directly without robust quoting/escaping. This reintroduces command-injection risk (regression from the previous argv-based Child.init(&.{"curl", ...})) and will also break for valid telemetry paths containing spaces or shell metacharacters. Prefer spawning curl with an argv array (no shell) and truncating the file via Zig after a successful exit, or otherwise ensure path is correctly quoted/escaped for the target shell.
| if (comptime @import("builtin").os.tag == .windows) { | |
| // On Windows, use cmd.exe to run curl and truncate the file | |
| var cmd_buf: [2048]u8 = undefined; | |
| const cmd = std.fmt.bufPrint(&cmd_buf, "curl -sf -X POST {s} -H \"Content-Type: application/json\" --data-binary @{s} >NUL 2>&1 && type nul > {s}", .{ CLOUD_URL, path, path }) catch return; | |
| var child = std.process.Child.init(&.{ "cmd.exe", "/C", cmd }, std.heap.page_allocator); | |
| child.stdin_behavior = .Ignore; | |
| child.stdout_behavior = .Ignore; | |
| child.stderr_behavior = .Ignore; | |
| _ = child.spawn() catch return; | |
| } else { | |
| var cmd_buf: [2048]u8 = undefined; | |
| const cmd = std.fmt.bufPrint(&cmd_buf, "curl -sf -X POST {s} -H 'Content-Type: application/json' --data-binary @{s} >/dev/null 2>&1 && : > {s}", .{ CLOUD_URL, path, path }) catch return; | |
| var child = std.process.Child.init(&.{ "/bin/sh", "-c", cmd }, std.heap.page_allocator); | |
| child.stdin_behavior = .Ignore; | |
| child.stdout_behavior = .Ignore; | |
| child.stderr_behavior = .Ignore; | |
| _ = child.spawn() catch return; | |
| } | |
| var data_arg_buf: [1024]u8 = undefined; | |
| const data_arg = std.fmt.bufPrint(&data_arg_buf, "@{s}", .{path}) catch return; | |
| var child = std.process.Child.init(&.{ | |
| "curl", | |
| "-sf", | |
| "-X", | |
| "POST", | |
| CLOUD_URL, | |
| "-H", | |
| "Content-Type: application/json", | |
| "--data-binary", | |
| data_arg, | |
| }, std.heap.page_allocator); | |
| child.stdin_behavior = .Ignore; | |
| child.stdout_behavior = .Ignore; | |
| child.stderr_behavior = .Ignore; | |
| child.spawn() catch return; | |
| const term = child.wait() catch return; | |
| switch (term) { | |
| .Exited => |code| { | |
| if (code != 0) return; | |
| }, | |
| else => return, | |
| } | |
| var file = std.fs.cwd().openFile(path, .{ .mode = .write_only }) catch return; | |
| defer file.close(); | |
| file.setEndPos(0) catch return; |
src/telemetry.zig
Outdated
| var child = std.process.Child.init(&.{ "cmd.exe", "/C", cmd }, std.heap.page_allocator); | ||
| child.stdin_behavior = .Ignore; | ||
| child.stdout_behavior = .Ignore; | ||
| child.stderr_behavior = .Ignore; | ||
| _ = child.spawn() catch return; | ||
| } else { | ||
| var cmd_buf: [2048]u8 = undefined; | ||
| const cmd = std.fmt.bufPrint(&cmd_buf, "curl -sf -X POST {s} -H 'Content-Type: application/json' --data-binary @{s} >/dev/null 2>&1 && : > {s}", .{ CLOUD_URL, path, path }) catch return; | ||
| var child = std.process.Child.init(&.{ "/bin/sh", "-c", cmd }, std.heap.page_allocator); | ||
| child.stdin_behavior = .Ignore; | ||
| child.stdout_behavior = .Ignore; | ||
| child.stderr_behavior = .Ignore; | ||
| _ = child.spawn() catch return; |
There was a problem hiding this comment.
syncToCloud uses child.spawn() and then drops the Child without waiting. On POSIX this will leave zombie processes until codedb exits, and with syncToCloud() called every 10 events this can exhaust the process table over long sessions. Consider using spawnAndWait(), or spawning in a background thread and still calling wait() to reap, or otherwise retaining/reaping child processes.
d094955 to
a81c763
Compare
Addressing reviewer feedback✅ Fixed:
|
|
Hey @JF10R — thanks for the Windows work! Before we can review and merge, could you please:
We'll get Codex to do a review pass once rebased. Thanks! |
justrach
left a comment
There was a problem hiding this comment.
Requesting rebase onto current main and CONTRIBUTING.md compliance before review. See comment above.
Add Windows platform support foundations that all subsequent Windows PRs depend on. No behavioral change on Linux/macOS. - compat.zig: add path_buf_size (1024 on Windows vs max_path_bytes) - build.zig: set 8MB stack for Windows (matches Linux default) - main.zig: replace thread trampoline with noinline mainImpl to avoid LLVM frame merging (~128MB) that exceeds Windows guard page stride - main.zig: fix double scan_thread.join() (UB on all platforms) - watcher.zig: heap-allocate EventQueue (avoids ~4MB stack array) - watcher.zig: use compat.path_buf_size in FsEvent - index.zig: heap-allocate frequency table buffers (~640KB each) - tests.zig: update EventQueue construction to use init()/deinit()
Upstream issue-148 added stdin HUP detection via std.posix.poll() and pipe tests. These use POSIX APIs unavailable on Windows. Guard the poll block with comptime os check (Windows uses idle timeout only) and skip pipe tests on Windows.
Upstream's mmap trigram index (justrach#167) uses std.posix.mmap/munmap which are unavailable on Windows. Guard initFromDisk to return null on Windows (falling back to heap-based index) and skip mmap-dependent tests.
Platform-specific I/O changes for Windows file system operations. - store.zig: use NtLockFile/NtUnlockFile on Windows (POSIX flock on others) - watcher.zig: add isSep() to handle both / and \ path separators - watcher.zig: backslash-aware shouldSkip() for Windows paths - watcher.zig: use %TEMP%/%TMP% for codedb-notify path on Windows - watcher.zig: use setEndPos() instead of ftruncate (cross-platform) - watcher.zig: trim both / and \ when making paths relative to root
- store.zig: check NtLockFile return status; only unlock if lock succeeded; skip write-under-lock if lock fails (matches upstream's advisory lock pattern) - watcher.zig: normalize backslash paths to forward slashes in drainNotifyFile to avoid duplicate entries in the explorer (walker uses / convention, notify file may contain \ on Windows)
Remove barriers preventing MCP and telemetry from working on Windows. - main.zig: add is_windows const; block HTTP serve with error message directing users to mcp mode; HOME→USERPROFILE fallback in getDataDir - mcp.zig: import compat; noinline on run/dispatch to prevent LLVM frame merging; never_inline for heavy handlers (handleRemote, handleIndex, handleProjects); HOME→USERPROFILE fallback; path_buf_size; pass cwd to Child.run in handleIndex - snapshot.zig: HOME→USERPROFILE fallback in writeSnapshotDual - telemetry.zig: use cmd.exe /C for cloud sync on Windows; use compat.path_buf_size for buffer declarations
Revert from shell command string interpolation (cmd.exe /C, /bin/sh -c) to argv-based curl execution. This: - Eliminates command injection risk from path interpolation - Avoids zombie processes (spawnAndWait instead of spawn) - Works on both Windows (curl ships with Win10+) and POSIX - Truncates telemetry file after successful sync via createFile
a81c763 to
bde3bf7
Compare
|
Rebased onto current
Resolved rebase conflict in
|
Closes #183
Depends on #170, #171
Summary
Enables MCP mode and telemetry on Windows: HOME/USERPROFILE fallback, LLVM frame merging prevention, cross-platform telemetry sync, and blocking the unsupported HTTP
servecommand.Changes
src/main.zigis_windowsconst; blockservecommand with error directing tomcpmodesrc/mcp.zigHOME→USERPROFILEfallback ingetDataDir,handleProjects,ProjectCache;noinline/never_inlinefor heavy MCP handlers;compat.path_buf_sizefor path buffers; passcwdtoChild.runinhandleIndexsrc/telemetry.zigcurlexecution (no shell interpolation — fixes command injection);compat.path_buf_size;spawnAndWaitinstead of orphanedspawnsrc/snapshot.zigHOME→USERPROFILEfallback for secondary snapshot pathTests
Before/After
HOMEenv var doesn't exist on Windows. LLVM inlines all handler frames into ~128MB. Telemetry uses/bin/sh.servecommand hits POSIX-onlystd.net.curlargv.serveblocked with clear error message.Compliance
main(d7e3bfb), branched from feat(windows): core I/O — file locking and path separators #171