Windows native port: pipe-transport CLI daemon, JSON tool surface, cleanup pass - #5
Windows native port: pipe-transport CLI daemon, JSON tool surface, cleanup pass#5nsxdavid wants to merge 145 commits into
Conversation
…oot indexing Three fixes, each with a failing->passing test (per CLAUDE.md): justrach#537 snapshot-restored files were in neither trigram_index nor skip_trigram_files, so codedb_search missed them once the trigram index was non-empty (Tier-5 full scan ruled out). Register restored files in skip_trigram_files in insertRestoredFile, mirroring the justrach#507 outline-only path. Test: issue-537 (src/test_snapshot.zig). #537b insertRestoredFile never rebuilt symbol_index, and resolveCallees reads it with no fallback (assumed "rebuilt on every commit"), so call-graph edges into restored files vanished after a load. Make rebuildSymbolIndexFor pub + call it on restore. Test: issue-537b (src/test_snapshot.zig). justrach#538 index refused temp roots (/tmp, /private/tmp) with no override, blocking SWE-bench/CI retrieval harnesses. Add an opt-in: CODEDB_ALLOW_TEMP=1 env or --allow-temp flag (root_policy.tempIndexingAllowed, cio.posixSetenv). Default guard preserved. Test: issue-538 (src/test_mcp.zig) + verified end-to-end against the issue's exact CLI repro. Full `zig build test` green. Also carries pre-existing uncommitted WIP already in the working tree, entangled in shared files so it could not be cleanly separated: the justrach#531 call-graph / codedb_callpath feature (codegraph.zig, explore.zig, mcp.zig, main.zig, test_explore/parser/query) and justrach#528 CLI hardening. Generated artifacts (codedb.snapshot, .zigrep_archive) left unstaged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ce in search justrach#539 ("search returns 2 results where word finds 2658") is the same root cause as justrach#537: snapshot-restored files were in no search tier, so codedb_search never returned them. The Bug #1 fix (register restored files in skip_trigram_files) already resolves it. This adds an isolated recall regression guard: a term that lives ONLY in restored files, with a hot file lacking it and the word index still empty post-load, so the hit can only come from the Tier-3 skip_trigram scan. Asserts search recall matches word recall for restored files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ecall searchContent never triggered the lazy word-index rebuild that searchWord does, so after a snapshot fast-load Tier 0 (the inverted-index recall source) was empty and recall collapsed to the trigram + skip_trigram tiers. A relevant restored file could then be crowded out of a small max_results by less-relevant hot files (the justrach#539 quota residual). Rebuild the word index at searchContent's start when incomplete — at most once per load — so every file the inverted index knows about becomes a ranked Tier-0 candidate and competes on relevance. Only triggers on the incomplete-index path (post-load); live-indexed Explorers already have a complete word index, so the ranking tests are unaffected. Test: issue-539b (src/test_snapshot.zig) — a 3-hit cold file must survive a 2-slot result set against three 1-hit hot files (red before, green after). Full `zig build test` green: 23/23 steps, 721/721 tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…test to Tier 3 Addresses a Sonnet 4.6 review of the recall fix: - searchContent read word_index_complete OUTSIDE the lock (a data race vs the watcher's markWordIndexIncomplete; on AArch64 the bool could be cached and the rebuild silently skipped). Now read under mu.lockShared, matching searchWord. - issue-537 used a whole-token query, which the justrach#539 word-index rebuild now satisfies via Tier 0 — so it no longer exercised the skip_trigram (justrach#537) fix. Switched to a mid-token substring ("RESHNESS") that only the Tier 3 scan can surface, restoring isolated coverage. Fixed the now-inaccurate issue-539 comment. Full zig build test green: 23/23 steps, 721/721. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…) — bump + changelog draft Version bump to 0.2.5825 and a CHANGELOG entry for the search-recall / call-graph / temp-root fixes on this branch. NOT published: no tag, no GitHub Release. The CHANGELOG has a TODO to document the bundled in-progress justrach#531 call-path feature + justrach#528 CLI hardening before tagging. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parseTsLine only extracted an import's `from "..."` specifier when it appeared on the same line as `import `. Multi-line ES imports place the `from "..."` clause on a line beginning with `}`, so those dependency paths were dropped (only single-line imports were captured). Add a continuation branch that extracts the specifier from `} from "..."` lines; this also captures `export ... from` re-export dependencies. Adds a red-to-green parser test.
rebuildDepsFor (and its twin on the snapshot-restore path,
rebuildDepsFromOutline) dropped any import specifier containing "..",
so intra-repo relative imports never appeared in deps/imported_by; only
bare specifiers (node:*, package names) survived.
Add one shared resolveDependencyKey() used by both dep-build paths:
relative specifiers resolve against the importing file into repo-rooted
paths. Extensionless imports ("../foo") take the importer's extension so
they line up with the indexed file ("dir/foo.ts"). Resolved keys are
interned in a dep-graph-owned arena with a dedup map, so re-indexing a
file reuses one allocation per path instead of growing the arena. The
snapshot path dedups like the fresh path. outline.imports stays raw, so
issue-114 and the other import tests are unchanged. Also fixes a
pre-existing success-path leak in normalizePath.
Tests cover depends-on/imported_by for both explicit and extensionless
relative imports, and that interned keys are reused.
parseTsLine matched 'import ' anywhere on a line, so a plain string such as `export const ERROR = 'failed to import the config module'` was captured as a dependency edge. Fails on the current tip (the bogus string is in outline.imports). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parseTsLine matched 'import ' anywhere on a line via containsAny, so a string literal that merely contained 'import ' (e.g. an error message) was extracted and recorded as a bogus dependency. Match startsWith(line, 'import ') instead (the line is already trimmed when parseTsLine runs); keep require( detection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-substring fix(justrach#548): TS/JS parser no longer captures "import " inside strings as a dependency
…ms surface searchContent's Tier 0 recall is the word inverted index, but the CLI `search` path only loaded the trigram index after a snapshot load (`word`/`mcp` load the word index; `search` did not). With Tier 0 dead, `search` fell back to trigram only and missed low-frequency identifier terms that `word` finds. Load the word index for `search` too — a cheap mmap load mirroring the trigram one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rd-blend fix(justrach#547): load the word index for `search` so identifier terms surface
…ps/search/CLI + @nsxdavid credit Documents the work that landed in 0.2.5825 beyond the original justrach#537/justrach#538/justrach#539 cut (codedb_callpath + PageRank justrach#531, symbol filters, JSON/paths_only output, TS/JS dep-graph fixes justrach#540/justrach#541/justrach#542/justrach#543/justrach#548, search word-index loading justrach#547, CLI hardening justrach#528) and credits @nsxdavid. Removes the pre-publish TODO. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…m port) LexFreqPenalty ports engram's learned negative-lexical signal (LEARNED_W lexical = -2) into codedb's in-process rerank: down-weight files the query saturates (dispatcher / registry / re-export / changelog) so the eponymous implementation file surfaces. Multiplier is 1.0 for the least-matched file, 1-amp for the most-matched, linear in normalized match-line count; no-op when files tie (max_count<=1). Applied in rerankAndFinalize after rerankSignalScore. Default-on at amp 0.8 (tuned on the swe-lite retrieval sweep: 0.8 lifts a buried gold with no regressions; 0.95 over-penalizes legitimately-saturated symbol-owner files). Disable with CODEDB_LEX_FREQ_PENALTY=0/false/off; tune with CODEDB_LEX_FREQ_AMP. Retrieval: MRR 0.833 -> 1.000 on the swe-lite subset (one buried gold flips 2 -> 1), zero regressions. Suite: 722/722 green (verified prior session). Default-on routes the Tier-0 fast path through rerank, costing ~+14us worst-case on high-frequency queries (word/symbol controls flat); negligible for interactive/MCP use. Also lands RvsmSizePrior as opt-in, default-OFF scaffolding (CODEDB_RVSM_SIZE_PRIOR, amp/k via CODEDB_RVSM_AMP/_K): the rVSM file-size prior (BugLocator, ICSE 2012) flat-lined as a standalone reranker (see experiments/ "4th negative"); retained as a feature hook for future learned fusion (P4). No effect unless explicitly enabled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… additions Preserve the uncommitted research trail and bench artifacts from the working tree; no production code changes. experiments/ranking/ (follows the experiments/reader-md/ convention): - failed.md: 4 single-signal reranking tweaks that flat-lined (query-graph- distance justrach#550, global + normalized centrality, rVSM size prior), why each failed, and the lexfreq penalty (justrach#554) that worked, for contrast. - research.md: IR-fault-localization synthesis + method/reference table. - todo.md: prioritized P0-P4 plan (next: git fix-frequency, learned fusion). - scratch/: raw warm-daemon timing output. bench/swe-lite/validators/: 7 new functional validators (bandit, cattrs, httpx, kombu, narwhals, sqliteutils, vulture) + result JSONs (codedb vs codegraph/graphify/leanctx/baseline). Suite extension committed in place. .gitignore: ignore zig-pkg/ (zig dep cache) and __pycache__/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g the full index `codedb [root] status` was grouped with the heavy query commands: it loaded the full index (snapshot mmap, or a complete re-index + multi-GB snapshot rewrite when none existed) and — being in cliIsQueryCmd — also auto-spawned a warm cli-daemon, all before reading three cheap counts. Backgrounded from a Claude Code SessionStart hook (`codedb . status &`), each call reparented to launchd and left a multi-GB resident orphan; they stacked across sessions until RAM was exhausted (jetsam + WindowServer watchdog freeze). Reported by @lekt9. Add an early `status` branch before the load/daemon path that reports from the on-disk trigram header alone (readStatusMeta -> TrigramIndex.readDiskHeader: file_count + git_head, header-only) and exits. No Explorer load, no snapshot mmap, no re-index, no daemon spawn. Prints "not indexed" instead of triggering a scan when no index is persisted. Measured: `codedb . status` now ~72us. The codedb_status MCP tool is unaffected (runs inside the already-loaded server). Test references readStatusMeta, so it cannot pass without this fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(justrach#553): cheap metadata-only CLI status — stop materializing the full index
…rank src implementation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…/website/install) in rerank Single-word searchContent queries rank by rerankSignalScore, which penalized tests/examples/vendor/docs but had no prior for first-party tooling directories. Mention-heavy files under bench/, scripts/, website/, and install/ tied or beat the implementing src file and won on the path-asc tiebreaker (live repo: 'snapshot' ranked install/install.sh above src/snapshot.zig, gold at rank 6; engram codedb-insights MRR 0.30 vs 0.60). Apply a 0.5 multiplier for bench/benchmarks/scripts/website/install path segments — milder than vendor (0.4), stronger than tests (0.6 on a usually-lower base). Post-fix 'snapshot' ranks src/snapshot.zig first on this repo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ry file through Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…issing params; pipeline output reflects transforms filter now reads glob orelse pattern, auto-promotes bare patterns to '**/<pattern>' (same rule as codedb_search path_glob), and errors via finishQueryWithFailure when no recognized param is present instead of silently passing every file through. find defers its listing unless it is the pipeline's last step, so the rendered output is the post-filter/limit set (previously the eager 50-file dump made downstream transforms invisible). To preserve the issue-356 partial-results contract, finishQueryWithFailure now prints the file set accumulated before the failing step, and the unknown-op branch joins that contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… out-of-glob files Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uence, not the global rank window When path_glob is set, escalate the fetch window (x4, capped at the existing 100000 bound) until the in-glob set fills the requested page or the index is exhausted, then page over the glob-filtered view so offset/max_results address in-glob results. Previously the handler fetched offset+max_results+1 globally-ranked rows and filtered at render time, so an in-glob match ranking below the window produced '0 results' alongside a 'more results' hint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… mentions as call sites Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both handleCallers loops now skip lines isCommentOrBlank reports as comments (same helper compact-mode search uses), so documentation mentions no longer appear as call sites or inflate the header count. Trailing comments on code lines are unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-phase + maxrss load profiling Pass C eagerly rebuilt the global symbol index for every restored file even though plain content search never reads it. Mark it incomplete before Pass C, no-op per-file rebuilds while deferred, and build it from outlines on first use via ensureSymbolIndex (mirrors the lazy word-index rebuild) at every reader entry: findSymbol, findAllSymbols, searchSymbols, renderSymbols, resolveCallees, findCallPath, buildCallCentrality. ensureCallGraph refuses to build from a deferred index so an empty call graph is never cached. Also extends the gated CODEDB_LOAD_PROFILE with insert sub-phases (content/deps/symidx/store) and per-phase maxrss attribution, which is how this was found. openclaw (13,654 files, ReleaseFast, warm): load 60ms -> 40ms; Pass C heap +62.5MB -> +20.5MB; one-shot search phys footprint 132.7MB -> 89.2MB (-33%), max RSS 244MB -> 200MB. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-rank fix(justrach#546): down-rank first-party tooling paths in searchContent rerank
fix(justrach#558): codedb_query filter op no longer silently no-ops; output reflects pipeline transforms
perf(justrach#564): defer the symbol index on snapshot fast-load (−33% load time, −43MB footprint on openclaw)
…+ 560) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…budget feat(justrach#531): max_tokens two-step packing for codedb_context
Teach the 0.16 compat shim to back every primitive with the native API on Windows while POSIX keeps its existing libc/pthread paths: - argv: windowsArgv flattens WTF-16 command lines into the posix-shaped C-string argv the rest of the program uses - raw io: writeRaw/readRaw dispatch to _write/_read (msvcrt) vs write/read - locks: Mutex/RwLock select SRWLOCK on Windows (zero-init, real shared/exclusive semantics) and pthread on POSIX - time: GetSystemTimeAsFileTime wall clock; QueryPerformanceCounter monotonic clock with the constant frequency queried once and cached - env: getenv/_putenv_s with a HOME -> USERPROFILE fallback, plus a shared userHome() helper for the per-user cache root - subprocess: std.process.run capture path and a CreateProcessW DETACHED_PROCESS + CREATE_NO_WINDOW detached spawn for the cli-daemon - mapFileRead/unmapFileRead: mmap on POSIX, aligned heap read on Windows
main() converts the WTF-16 command line once via cio.windowsArgv; everything downstream keeps consuming posix-shaped argv. The warm CLI daemon gets a Windows transport equivalent to the per-project Unix socket: a per-project, per-user named pipe. One CliConn type (HANDLE on Windows, fd on POSIX) backs shared framing, serve, respond, and proxy paths, so the wire protocol lives in exactly one place; only connect/accept/close differ per platform. Pipe endpoint hardening, matching the socket path's chmod 0600 + stale-node handling: - owner-only protected DACL (SDDL D:P(A;;GA;;;OW)), fail closed - FILE_FLAG_FIRST_PIPE_INSTANCE so a daemon that lost the creation race exits instead of serving alongside the winner - clients pass SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION so a pipe server can identify but never impersonate them - username hashed into the pipe name (parity with uid in the socket path) The MCP idle watchdog skips the POSIX stdin POLLHUP probe on Windows and relies on the timed loop; socket unlink-on-exit is POSIX-only since a named pipe is a kernel object with no filesystem node.
…tcher - Replace direct std.posix.mmap/munmap with cio.mapFileRead/unmapFileRead (mmap on POSIX, aligned heap read on Windows); snapshot content sections allocate with the Explorer allocator that owns the adopted section and unmap with that same allocator on the rejection path - Open the hot-log/notify files with read access in one createFile call: Windows requires read access on the handle for length(), and .truncate = false opens-or-creates without the close/reopen dance - Honor native path separators where realPathFile returns them, keeping '/' in indexed relative paths for cross-platform codedb output - Route remaining timestamps and env access through cio
Project data dirs and the central snapshot cache derive their root from cio.userHome() (HOME, with the USERPROFILE fallback on native Windows) instead of reading HOME directly, so the per-user cache works from stock Windows shells that never set HOME. Central-cache tests share one setTestHome helper to point HOME inside the tmp dir.
- Skip getGitHead and symlink-cycle walker tests on native Windows: the former shells out to git (recurses through this checkout's tool plumbing and exhausts the DebugAllocator under the Windows test runner), the latter needs a reparse-point-aware walker; POSIX keeps both covered - Heap-allocate the large watcher EventQueue in tests so the smaller default Windows test-thread stack exercises the same queue behavior - test_mcp subprocess builds pin --global-cache-dir .zig-global-cache and use platform-correct exe paths/line endings; the unicode-root test exercises the named-pipe daemon end to end
Rollback inserted snapshot entries before releasing adopted backing buffers and invalidate cached call graph state when outlines mutate.
Keep plain search on the fast trigram path when the word index is incomplete, while reserving space for restored skip-trigram files. Also block native Windows temp roots unless temp indexing is explicitly allowed.
processId() returned a stub 0 on Windows, silently breaking nuke's self-pid guard and randU64's entropy mix; use GetCurrentProcessId. Move the HOME->USERPROFILE fallback out of posixGetenv into userHome() and switch all HOME readers (linter_pref, nuke, root_policy, update, mcp projects, main getDataDir) to it. Dedupe env-var NUL-termination into a zTerm helper.
format=json callers of codedb_search/codedb_symbol got plain-text
errors (and a stray arg-keys diagnostic in the JSON path); emit the
ok=false/tool/error{code,message} envelope instead. Consolidate the
per-type appendJsonKey{Usize,U8,F32} writers into appendJsonKeyNum,
extract searchResultVisible, and add round-trip schema tests that run
the real dispatch path and parse the responses.
Extract rejectExtraArgs for the arity-zero commands (tree/hot/status), fold the per-platform read/write bodies of cliReadFull/cliWriteFull into connRead/connWrite, and drop the hand-rolled SECURITY_ATTRIBUTES, CloseHandle, GetLastError, and ERROR_PIPE_CONNECTED declarations in favor of std.os.windows equivalents.
Replace the manual free-everything-on-each-failure ladder in the call graph build with an errdefer-based inner function, hoist the symbol max_results cap checks to loop tops, and reuse std.fs.path.extension / resolveRelativeImportPath in pathExtension and resolveDartImport.
The issue-537/537b/539/539b load-path tests each hand-rolled the same tmp-dir snapshot write + load sequence; share one helper.
Extract the CreateProcessW command-line builder from spawnDetachedWindows into pub cio.windowsCommandLine so it is testable, and add a FAILING test: an arg ending in a backslash (any drive-root project path like D:\) is emitted as "D:\" whose trailing \" escapes the closing quote under CommandLineToArgvW rules, fusing the arg with whatever follows in the spawned child's argv. Known-failing on Windows until the quoting bug is fixed; rename issue-XX once the issue is filed.
This reverts commit 5ab20bd.
This reverts commit cb455e0.
…CommandLine Double backslashes before a closing quote (2N) and before embedded quotes (2N+1) so args ending in '\' (e.g. drive-root paths like D:\) no longer fuse with the following argument. Extend the round-trip test with embedded quotes, internal double backslashes, and trailing backslash runs, and promote it from issue-XX to a named windows test.
… envelopes
- Add toolOutputIsError so telemetry, bundle fail counts, and CLI exit
codes recognize the {"ok":false,...} envelope, not just "error:" text
- Emit writeJsonToolError envelopes on regex and plain-search failures
when format=json was requested
- Skip the renderPlainSearch fast path under format=json so results
come back as JSON instead of plain text
- Include the symbol body in codedb_symbol JSON output when body=true
…geRank edge weights - searchSymbols: collect all candidates before sorting instead of capping during hash-map iteration, so the cap keeps the best-scored results; free the entries trimmed after the sort - findCallPath: dupe step path/name into the caller's allocator since the call graph's backing strings can be invalidated once the shared lock is released; free them at the callpath handler - pageRank: count out_weight only for edges with valid destinations, matching the propagation loop, so valid targets aren't under-weighted
Returning null on a malformed word/trigram index skipped the errdefer that unmaps the file, leaking the mapping; return error.MalformedIndex instead. Also move loadSnapshotFast's call-graph invalidation past header/section validation, so an early-rejected snapshot leaves the existing graph intact while the first real mutation still invalidates.
… --allow-temp Cold `search` (no snapshot) now enables the word index during the initial scan and marks it complete, so Tier-0 BM25 recall works on the first run instead of only after `index`. Add the missing `callpath` subcommand and `--allow-temp` option to the usage text.
…help tests Add EnvVarGuard/TestHomeGuard so tests that override HOME, USERPROFILE, CODEDB_ALLOW_TEMP, or CODEDB_CLI_DAEMON_IDLE_MS restore the original values instead of unsetting them for the rest of the test process. Use builtCodedbExe() in the --help/-h tests so they run on Windows. Reword a CHANGELOG line to avoid a bare trailing `import ` in backticks.
9a62483 to
185f8c3
Compare
There was a problem hiding this comment.
Actionable comments posted: 19
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/mcp.zig (1)
1593-1600:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReturn JSON errors for invalid
max_resultswhenformat=jsonis requested.
codedb_searchstill emits plain-text (error: ...) formax_results <= 0, which breaks theformat=jsonresponse contract for structured clients.💡 Minimal fix
@@ - if (getInt(args, "max_results")) |n| { + const json_fmt = wantsJsonFormat(args); + if (getInt(args, "max_results")) |n| { if (n <= 0) { - const w_err = cio.listWriter(out, alloc); - w_err.print("error: max_results ({d}) must be >= 1", .{n}) catch {}; + if (json_fmt) { + var msg_buf: [96]u8 = undefined; + const msg = std.fmt.bufPrint(&msg_buf, "max_results ({d}) must be >= 1", .{n}) catch "max_results must be >= 1"; + writeJsonToolError(out, alloc, "codedb_search", "invalid_max_results", msg); + } else { + const w_err = cio.listWriter(out, alloc); + w_err.print("error: max_results ({d}) must be >= 1", .{n}) catch {}; + } return; } } @@ - const json_fmt = wantsJsonFormat(args);Also applies to: 1623-1623
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/mcp.zig` around lines 1593 - 1600, The plain-text error emitted when getInt(args, "max_results") returns n <= 0 should instead return a JSON error when the client requested JSON output: detect the requested format (check args for "format" == "json" or use the same format-detection logic elsewhere in codedb_search), and when true write a JSON error object (e.g. {"error":"max_results must be >= 1","details":n}) to out using the same JSON writer used by codedb_search; otherwise keep the existing plain-text cio.listWriter path. Apply the identical change for the second occurrence noted around the other max_results check (the block near line ~1623) so both places respect the format=json contract.src/explore.zig (1)
2212-2223:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winGuard intermediate dupes until ownership transfers into the array.
Both allocation sequences build owned strings before the final
append, but the cleanup only covers elements that are already in the list. If a laterdupeor theappenditself fails, the earlier temporary allocations leak. Add localerrdefercleanup for each newly-duped string until the append succeeds.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/explore.zig` around lines 2212 - 2223, The code dupes multiple strings (path, sym.name, sym.detail) before calling list_ptr.append, but if any subsequent dup or the append fails those earlier allocations leak; wrap each alloc.dupe call with an errdefer that frees that duplicated buffer (using the same allocator) so the temp is cleaned up on error, and cancel the corresponding errdefer(s) after list_ptr.append succeeds; specifically, when creating the temporary duped values used to build the appended element (the duped path, the duped name, and the optional duped detail), add an errdefer cleanup for each and call the defer cancel/disable right after append returns successfully to transfer ownership into list_ptr.append (refer to Dedup.contains, alloc.dupe calls and list_ptr.append and the symbol fields sym.name/sym.detail).src/index.zig (1)
2455-2463:⚠️ Potential issue | 🟠 Major | ⚡ Quick winTreat a failed base/overlay candidate build as
null, not a partial result.
mergeOverlayCandidatescurrently returns whichever side succeeded when the other side returnednull. In overlay mode,nullfromMmapTrigramIndex.candidates*orTrigramIndex.candidates*is the allocation-failure signal, so this converts OOM into a partial candidate set and can drop valid matches instead of forcing the caller back to the full scan.💡 Minimal fix
fn mergeOverlayCandidates( mo: *const MmapOverlay, base: ?[]const []const u8, over: ?[]const []const u8, allocator: std.mem.Allocator, ) ?[]const []const u8 { - if (base == null and over == null) return null; - if (base == null) return over; + _ = mo; + if (base == null or over == null) { + if (base) |b| allocator.free(b); + if (over) |o| allocator.free(o); + return null; + } var merged = std.StringHashMap(void).init(allocator); defer merged.deinit(); var failed = false; for (base.?) |p| { - if (mo.masked.contains(p)) continue; + if (mo.masked.contains(p)) continue; merged.put(p, {}) catch { failed = true; break; }; }Also applies to: 2467-2507, 2509-2517
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/index.zig` around lines 2455 - 2463, When building candidates in overlay mode (AnyTrigramIndex.candidates -> .mmap_overlay) do not treat a null return from mo.base.candidates or mo.overlay.candidates as a partial success; instead, if either base or over is null (allocation failure), return null immediately and only call mergeOverlayCandidates when both succeeded. Update the .mmap_overlay branch and the similar blocks handled around lines 2467-2507 and 2509-2517 so they check both results and propagate null on any failure before attempting to merge.
🧹 Nitpick comments (1)
src/test_mcp.zig (1)
2071-2073: ⚡ Quick winUse an arena allocator for new Explorer-based tests.
These new
Explorer.init(...)test blocks usetesting.allocator; insrc/test_*.zig, Explorer tests should usestd.heap.ArenaAllocatoranddefer arena.deinit().As per coding guidelines:
src/test_*.zig: "Usestd.heap.ArenaAllocatorfor Explorer tests in Zig".Also applies to: 2105-2108, 2148-2151, 2190-2193, 2229-2232, 2355-2361
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test_mcp.zig` around lines 2071 - 2073, Replace uses of testing.allocator when initializing Explorer with a std.heap.ArenaAllocator: create an arena via std.heap.ArenaAllocator.init(&testing.allocator) (or similar), pass arena.allocator() into Explorer.init instead of testing.allocator, and add defer arena.deinit() before deferring explorer.deinit(); update the Explorer.init calls referenced (e.g., Explorer.init(...)) and corresponding defer lines so each test uses the arena allocator and deinitializes the arena.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@bench/swe-lite/validators/validate_bandit.py`:
- Around line 33-46: The temp directory created by tempfile.mkdtemp() (variable
d) is never removed, causing temp file leaks; change the _scan() flow to ensure
the directory is cleaned up after use (e.g., wrap the
write/discover/run/get_issue sequence in a try/finally or switch to
tempfile.TemporaryDirectory) and call shutil.rmtree(d) (or rely on the context
manager) after mgr.get_issue_list()/metrics access; reference variables and
functions: d, p, mgr.discover_files(...), mgr.run_tests(), mgr.get_issue_list(),
and totals = mgr.metrics.data["_totals"] so the cleanup always runs even if
run_tests() or get_issue_list() raises.
In `@bench/swe-lite/validators/validate_httpx.py`:
- Around line 241-244: The test currently passes a generator to httpx.Response
via content= which causes HTTPX to buffer the body at Response initialization;
change the constructor to use stream= with an httpx.ByteStream wrapper so the
chunks from _streaming_body are consumed only when iterating (e.g.,
httpx.Response(..., stream=httpx.ByteStream(_streaming_body([...]))), ensuring
iter_json() exercises streaming and that a subsequent iteration raises
httpx.StreamConsumed as expected; update the instantiation that currently calls
httpx.Response(200, headers={...}, content=stream) to use stream= instead.
In `@bench/swe-lite/validators/validate_kombu.py`:
- Around line 412-413: The code is calling Kombu private internals
ch._inplace_augment_message and _size and mutating
msg["properties"]["delivery_info"]["queue"]; replace those with public APIs:
construct the test message as a kombu.message.Message (or use
kombu.message.Message(body, delivery_info=..., properties=...)) and set the
queue name in its public delivery_info/properties instead of mutating internals,
and compute message byte-size using a public serializer (e.g.,
kombu.serialization.dumps followed by len(...) of the serialized payload) rather
than calling _size; update usages around _inplace_augment_message,
msg["properties"]["delivery_info"]["queue"], and _size to use these public
alternatives.
In `@bench/swe-lite/validators/validate_sqliteutils.py`:
- Around line 272-276: The validator currently treats any non-dict return from
db.import_csv(..., safe_mode=True, strict=False) and db.import_json(...,
safe_mode=True, strict=False) as success; change the logic so you require the
documented safe-mode envelope: for the db.import_csv result (variable ok_dict)
only set ok_dict = True when isinstance(res, dict) and res.get("success") is
True, otherwise False; apply the same strict check to the db.import_json result
(variable ok_json) so legacy/non-compliant non-dict returns do not pass
validation.
- Around line 47-56: The current get_exc() silently falls back to Exception
which hides missing API types; change get_exc to fail loudly when none of the
requested names exist (e.g., raise ImportError or RuntimeError with a clear
message referencing the requested names) so that assigning
SafeImportNotEnabledError, CheckpointNotActiveError, and CheckpointNotFoundError
will error if the implementation doesn't provide them; update references to
get_exc("SafeImportNotEnabledError"), get_exc("CheckpointNotActiveError"), and
get_exc("CheckpointNotFoundError") to rely on the new behavior (or perform an
explicit None check and raise) so the validator fails instead of silently using
Exception.
- Around line 327-337: The test currently only checks exit code and presence of
generic words; update it to also assert the failing invariant's identifier is
printed by capturing the id returned from d.add_import_invariant (call to
d.add_import_invariant("t", "SELECT COUNT(*) = 999") — store its return value,
e.g. inv_id) and then after running runner.invoke(cli_mod.cli,
["validate-import-invariants", dbpath, "t"]) check that (r.output or "").lower()
contains that inv_id (or a formatted form like f"{inv_id}" or f"{inv_id}"
prefixed by table if CLI prints "t:{id}"), and incorporate that boolean into
results["checks"]["cli_validate_invariants_exit0"] so the test fails if the CLI
omits the failing-invariant identifier.
In `@bench/swe-lite/validators/validate_vulture.py`:
- Around line 177-200: The tests only assert "at least one" scanned/reused path
but must require exact path coverage; update the checks around Vulture.scavenge
and _cache_stats so that stats1["scanned"] == set(paths) and stats1["reused"] ==
set() for the first run, and for the second run assert stats2["reused"] ==
set(paths) and stats2["scanned"] == set() (apply the same exact-set assertions
to the later analogous block at lines ~299-308); keep using getattr(vN,
"_cache_stats", None), ensure ok_stats_shape/ok2 still validate types before
comparing equality, and set C["first_run_scans_files"] and
C["second_run_reuses_unchanged"] based on those exact-set comparisons.
- Around line 255-256: The validator currently sets C["corrupt_cache_warns"] by
searching a combined variable (combined = (r.stderr or "") + (r.stdout or "")),
which can hide whether the message was actually written to stderr; change the
check to look only at r.stderr (e.g. test "cache is corrupted or unreadable" in
r.stderr or (r.stderr or "")) when assigning C["corrupt_cache_warns"]; apply the
same change to the other occurrence that sets C["corrupt_cache_warns"] on lines
~279-280 so both checks validate stderr only.
In `@src/explore.zig`:
- Around line 1580-1602: ensureSymbolIndex currently flips symbol_index_complete
back to true and calls rebuildSymbolIndexFor(..., false) which only appends,
leaving old entries and causing duplicates/stale results; before iterating
outlines in ensureSymbolIndex, acquire the same exclusive lock and clear the
existing symbol index so the rebuild starts fresh (e.g., reset or call the
symbol_index clear/reset routine) and then call rebuildSymbolIndexFor for each
outline; reference functions/fields: ensureSymbolIndex,
markSymbolIndexIncomplete, symbol_index, symbol_index_complete, and
rebuildSymbolIndexFor.
- Around line 818-823: setRoot currently overwrites self.root_dir and self.io
without closing any existing Dir, leaking the previous directory handle; modify
setRoot (in Explorer) to, before assigning the new root_dir/io, check if
self.root_dir != null and call close on that Dir handle (handle or ignore the
error), set it to null, then open the new dir and update self.io; keep the
existing logic that frees/dupes self.root_path and ensure deinit still closes
whatever root_dir remains.
- Around line 7059-7074: The function appendPythonModuleDep currently silently
truncates long mod_path into the fixed 512-byte stack buffer and still appends
".py", corrupting dependency keys; instead, detect when the input path
(converted with '.'->'/') plus the ".py" suffix would exceed the buffer and fail
explicitly (return an error) or allocate a sufficiently-sized buffer before
writing. Update appendPythonModuleDep to check if (mod_path.len + 3) > buf.len
(or compute required length after replacing '.' with '/') and if so return a
descriptive error instead of truncating; keep the call to
appendImportPath(outline, buf[0..pos]) unchanged when the path fits. Ensure the
error propagates from appendPythonModuleDep (it already returns !void) so
callers can handle the failure.
In `@src/main.zig`:
- Around line 1321-1350: The early "status" fast path bypasses the argument
validation in runQuery and rejectExtraArgs, letting "codedb status typo"
succeed; ensure the zero-arg contract by invoking the same validation before
taking the fast return: call rejectExtraArgs (the same function used by
runQuery) for the "status" branch (or otherwise validate there that no extra
args exist) before calling getDataDir/index_mod.readStatusMeta and exiting, so
the shortcut enforces the same no-arg rule as the normal status handler.
- Around line 2224-2228: The command parser misclassifies "bench-engine" because
isCommand() builds its commands list from cli_query_cmds plus an explicit array
that currently omits "bench-engine", so parsePositional() never treats args[1]
as a command; fix by adding "bench-engine" to the explicit array in isCommand()
(or ensure cli_query_cmds includes it) so that isCommand("bench-engine") returns
true and parsePositional() dispatches to the bench-engine handler.
- Around line 798-805: daemonLockTryAcquire currently constructs an ANSI byte
path and calls CreateFileA on Windows which fails for non-ASCII user paths;
change the Windows branch to convert the UTF-8 path (built from data_dir) to a
null-terminated UTF-16 (wide) buffer and call CreateFileW with that wide pointer
(then check for INVALID_HANDLE_VALUE and return the handle as before), or
explicitly document and perform an ANSI code-page conversion if you
intentionally want CreateFileA; refer to the function daemonLockTryAcquire and
the Windows calls CreateFileA/CreateFileW when making the change.
In `@src/snapshot.zig`:
- Around line 913-921: The rollback defer is calling explorer.removeFile(path)
where path is a borrowed slice that can alias the heap_section/CMS CONTENT
buffer which is freed earlier by the heap_section defer; fix by ensuring
inserted_paths stores owned copies instead of borrowed slices (or move the
rollback defer to run before heap_section defer). Specifically, change the code
paths that append into inserted_paths to duplicate the path (e.g.,
allocate/duplicate the string into the snapshot/explorer allocator) so that
entries in inserted_paths outlive heap_section/CONTENT, and keep the existing
rollback defer that calls explorer.removeFile(path) and the symbols load_ok,
inserted_paths, heap_section, explorer.removeFile, releaseOutlineSectionsFrom
and releaseContentSectionsFrom intact.
- Around line 1331-1373: isSensitivePath currently only splits on '/' and only
searches for UNIX-style directory tokens, which lets Windows paths (backslashes)
bypass checks; update isSensitivePath to treat both '/' and '\\' as path
separators when computing basename (e.g., use lastIndexOf for both or a helper
that finds the last of '/' or '\\'), and update directory-pattern checks
(std.mem.indexOf(u8, path, ".ssh/") etc.) to also look for the backslash
variants (".ssh\\", ".gnupg\\", ".aws\\") or check for the token followed by
either separator; keep the existing basename exact-name and extension checks but
run them on the normalized basename extracted using the new separator logic so
Windows paths are correctly flagged.
In `@src/store.zig`:
- Around line 99-117: appendVersion treats data_log as a multi-process file
(uses log.lock and re-stat) but compactDataLog currently rewrites/truncates
using only this process's self.files and can therefore drop bytes other
processes referenced; fix compactDataLog to acquire the same advisory lock on
data_log (same lock(...) path used by appendVersion) and while holding that
exclusive lock re-stat the file, scan all live version records across self.files
to compute the true highest referenced data_offset/length (not just this
process's view), rebuild the compacted log into a temporary file and atomically
replace/truncate the original only after verifying offsets against the
locked+re-statted end_pos, and update self.data_log_pos and any in-process
metadata consistently while still holding the lock so no concurrent append can
make offsets stale; ensure symbols touched include compactDataLog,
appendVersion, data_log, self.files, data_log_pos and log.lock/log.unlock.
In `@src/test_search.zig`:
- Around line 1815-1816: Replace direct use of testing.allocator when
initializing Explorer with a std.heap.ArenaAllocator instance: create an
ArenaAllocator (e.g. via std.heap.ArenaAllocator.init(testing.allocator)), pass
the arena's allocator to Explorer.init (instead of testing.allocator) for the
Explorer.DEFAULT_CONTENT_CACHE_CAPACITY calls, and ensure you deinit the arena
after the Explorer (mirror Explorer.deinit with arena.deinit/destroy as
appropriate). Update the three sites that call Explorer.init to use the arena
allocator and add the corresponding arena lifetime management adjacent to those
tests.
- Around line 1115-1132: Before you call
cio.posixSetenv("CODEDB_LEX_FREQ_PENALTY", "0") save the current environment
value (e.g., via cio.posixGetenv or std.os.getenv) into a local optional string,
and then in a defer restore it: if an original value existed call
cio.posixSetenv with that value, otherwise call cio.posixUnsetenv; do the same
pattern for the later block that calls cio.posixUnsetenv so the test always
restores the pre-test state of CODEDB_LEX_FREQ_PENALTY rather than permanently
unsetting it.
---
Outside diff comments:
In `@src/explore.zig`:
- Around line 2212-2223: The code dupes multiple strings (path, sym.name,
sym.detail) before calling list_ptr.append, but if any subsequent dup or the
append fails those earlier allocations leak; wrap each alloc.dupe call with an
errdefer that frees that duplicated buffer (using the same allocator) so the
temp is cleaned up on error, and cancel the corresponding errdefer(s) after
list_ptr.append succeeds; specifically, when creating the temporary duped values
used to build the appended element (the duped path, the duped name, and the
optional duped detail), add an errdefer cleanup for each and call the defer
cancel/disable right after append returns successfully to transfer ownership
into list_ptr.append (refer to Dedup.contains, alloc.dupe calls and
list_ptr.append and the symbol fields sym.name/sym.detail).
In `@src/index.zig`:
- Around line 2455-2463: When building candidates in overlay mode
(AnyTrigramIndex.candidates -> .mmap_overlay) do not treat a null return from
mo.base.candidates or mo.overlay.candidates as a partial success; instead, if
either base or over is null (allocation failure), return null immediately and
only call mergeOverlayCandidates when both succeeded. Update the .mmap_overlay
branch and the similar blocks handled around lines 2467-2507 and 2509-2517 so
they check both results and propagate null on any failure before attempting to
merge.
In `@src/mcp.zig`:
- Around line 1593-1600: The plain-text error emitted when getInt(args,
"max_results") returns n <= 0 should instead return a JSON error when the client
requested JSON output: detect the requested format (check args for "format" ==
"json" or use the same format-detection logic elsewhere in codedb_search), and
when true write a JSON error object (e.g. {"error":"max_results must be >=
1","details":n}) to out using the same JSON writer used by codedb_search;
otherwise keep the existing plain-text cio.listWriter path. Apply the identical
change for the second occurrence noted around the other max_results check (the
block near line ~1623) so both places respect the format=json contract.
---
Nitpick comments:
In `@src/test_mcp.zig`:
- Around line 2071-2073: Replace uses of testing.allocator when initializing
Explorer with a std.heap.ArenaAllocator: create an arena via
std.heap.ArenaAllocator.init(&testing.allocator) (or similar), pass
arena.allocator() into Explorer.init instead of testing.allocator, and add defer
arena.deinit() before deferring explorer.deinit(); update the Explorer.init
calls referenced (e.g., Explorer.init(...)) and corresponding defer lines so
each test uses the arena allocator and deinitializes the arena.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 89289cf1-087f-4e74-a955-d239bebd3e54
⛔ Files ignored due to path filters (1)
experiments/ranking/scratch/direct.outis excluded by!**/*.out
📒 Files selected for processing (80)
.gitignoreCHANGELOG.mdCLAUDE.mdbench/swe-lite/validators/results/bandit-structured-nosec-directives_baseline.jsonbench/swe-lite/validators/results/bandit-structured-nosec-directives_codedb.jsonbench/swe-lite/validators/results/bandit-structured-nosec-directives_codegraph.jsonbench/swe-lite/validators/results/bandit-structured-nosec-directives_graphify.jsonbench/swe-lite/validators/results/bandit-structured-nosec-directives_leanctx.jsonbench/swe-lite/validators/results/cattrs-partial-structuring-recovery_baseline.jsonbench/swe-lite/validators/results/cattrs-partial-structuring-recovery_codedb.jsonbench/swe-lite/validators/results/cattrs-partial-structuring-recovery_codegraph.jsonbench/swe-lite/validators/results/cattrs-partial-structuring-recovery_graphify.jsonbench/swe-lite/validators/results/cattrs-partial-structuring-recovery_leanctx.jsonbench/swe-lite/validators/results/httpx-streaming-json-iteration_baseline.jsonbench/swe-lite/validators/results/httpx-streaming-json-iteration_codedb.jsonbench/swe-lite/validators/results/httpx-streaming-json-iteration_codegraph.jsonbench/swe-lite/validators/results/httpx-streaming-json-iteration_graphify.jsonbench/swe-lite/validators/results/httpx-streaming-json-iteration_leanctx.jsonbench/swe-lite/validators/results/kombu-virtual-queue-dead-lettering_baseline.jsonbench/swe-lite/validators/results/kombu-virtual-queue-dead-lettering_codedb.jsonbench/swe-lite/validators/results/kombu-virtual-queue-dead-lettering_codegraph.jsonbench/swe-lite/validators/results/kombu-virtual-queue-dead-lettering_graphify.jsonbench/swe-lite/validators/results/kombu-virtual-queue-dead-lettering_leanctx.jsonbench/swe-lite/validators/results/narwhals-rolling-window-suite_baseline.jsonbench/swe-lite/validators/results/narwhals-rolling-window-suite_codedb.jsonbench/swe-lite/validators/results/narwhals-rolling-window-suite_codegraph.jsonbench/swe-lite/validators/results/narwhals-rolling-window-suite_graphify.jsonbench/swe-lite/validators/results/narwhals-rolling-window-suite_leanctx.jsonbench/swe-lite/validators/results/sqlite-utils-safe-import-checkpoints_baseline.jsonbench/swe-lite/validators/results/sqlite-utils-safe-import-checkpoints_codedb.jsonbench/swe-lite/validators/results/sqlite-utils-safe-import-checkpoints_codegraph.jsonbench/swe-lite/validators/results/sqlite-utils-safe-import-checkpoints_graphify.jsonbench/swe-lite/validators/results/sqlite-utils-safe-import-checkpoints_leanctx.jsonbench/swe-lite/validators/results/vulture-persistent-analysis-cache_baseline.jsonbench/swe-lite/validators/results/vulture-persistent-analysis-cache_codedb.jsonbench/swe-lite/validators/results/vulture-persistent-analysis-cache_codegraph.jsonbench/swe-lite/validators/results/vulture-persistent-analysis-cache_graphify.jsonbench/swe-lite/validators/results/vulture-persistent-analysis-cache_leanctx.jsonbench/swe-lite/validators/validate_bandit.pybench/swe-lite/validators/validate_cattrs.pybench/swe-lite/validators/validate_httpx.pybench/swe-lite/validators/validate_kombu.pybench/swe-lite/validators/validate_narwhals.pybench/swe-lite/validators/validate_sqliteutils.pybench/swe-lite/validators/validate_vulture.pycodedb.snapshotdocs/agents.mdexperiments/ranking/failed.mdexperiments/ranking/research.mdexperiments/ranking/scratch/direct.errexperiments/ranking/scratch/step4a_out.txtexperiments/ranking/scratch/step4a_time.txtexperiments/ranking/scratch/step4c_out.txtexperiments/ranking/scratch/step4c_time.txtexperiments/ranking/todo.mdsrc/cio.zigsrc/codegraph.zigsrc/explore.zigsrc/git.zigsrc/hot_cache.zigsrc/index.zigsrc/linter_pref.zigsrc/main.zigsrc/mcp.zigsrc/nuke.zigsrc/root_policy.zigsrc/snapshot.zigsrc/store.zigsrc/test_bench.zigsrc/test_core.zigsrc/test_explore.zigsrc/test_index.zigsrc/test_mcp.zigsrc/test_parser.zigsrc/test_query.zigsrc/test_search.zigsrc/test_snapshot.zigsrc/tests.zigsrc/update.zigsrc/watcher.zig
✅ Files skipped from review due to trivial changes (43)
- bench/swe-lite/validators/results/narwhals-rolling-window-suite_leanctx.json
- bench/swe-lite/validators/results/httpx-streaming-json-iteration_codedb.json
- bench/swe-lite/validators/results/cattrs-partial-structuring-recovery_leanctx.json
- bench/swe-lite/validators/results/sqlite-utils-safe-import-checkpoints_leanctx.json
- experiments/ranking/scratch/step4a_out.txt
- experiments/ranking/scratch/step4a_time.txt
- bench/swe-lite/validators/results/cattrs-partial-structuring-recovery_graphify.json
- bench/swe-lite/validators/results/httpx-streaming-json-iteration_leanctx.json
- bench/swe-lite/validators/results/narwhals-rolling-window-suite_graphify.json
- bench/swe-lite/validators/results/vulture-persistent-analysis-cache_baseline.json
- src/test_bench.zig
- bench/swe-lite/validators/results/narwhals-rolling-window-suite_baseline.json
- bench/swe-lite/validators/results/bandit-structured-nosec-directives_graphify.json
- bench/swe-lite/validators/results/bandit-structured-nosec-directives_codegraph.json
- bench/swe-lite/validators/results/httpx-streaming-json-iteration_graphify.json
- experiments/ranking/scratch/step4c_time.txt
- experiments/ranking/scratch/direct.err
- bench/swe-lite/validators/results/sqlite-utils-safe-import-checkpoints_graphify.json
- bench/swe-lite/validators/results/sqlite-utils-safe-import-checkpoints_codegraph.json
- bench/swe-lite/validators/results/cattrs-partial-structuring-recovery_baseline.json
- bench/swe-lite/validators/results/kombu-virtual-queue-dead-lettering_graphify.json
- bench/swe-lite/validators/results/bandit-structured-nosec-directives_codedb.json
- bench/swe-lite/validators/results/sqlite-utils-safe-import-checkpoints_baseline.json
- bench/swe-lite/validators/results/narwhals-rolling-window-suite_codegraph.json
- bench/swe-lite/validators/results/kombu-virtual-queue-dead-lettering_leanctx.json
- bench/swe-lite/validators/results/vulture-persistent-analysis-cache_leanctx.json
- CLAUDE.md
- bench/swe-lite/validators/results/cattrs-partial-structuring-recovery_codegraph.json
- experiments/ranking/scratch/step4c_out.txt
- bench/swe-lite/validators/results/httpx-streaming-json-iteration_codegraph.json
- docs/agents.md
- bench/swe-lite/validators/results/kombu-virtual-queue-dead-lettering_baseline.json
- bench/swe-lite/validators/results/vulture-persistent-analysis-cache_codedb.json
- bench/swe-lite/validators/results/vulture-persistent-analysis-cache_graphify.json
- bench/swe-lite/validators/results/kombu-virtual-queue-dead-lettering_codegraph.json
- bench/swe-lite/validators/results/bandit-structured-nosec-directives_leanctx.json
- CHANGELOG.md
- bench/swe-lite/validators/results/kombu-virtual-queue-dead-lettering_codedb.json
- bench/swe-lite/validators/results/cattrs-partial-structuring-recovery_codedb.json
- .gitignore
- bench/swe-lite/validators/results/sqlite-utils-safe-import-checkpoints_codedb.json
- experiments/ranking/todo.md
- bench/swe-lite/validators/results/narwhals-rolling-window-suite_codedb.json
🚧 Files skipped from review as they are similar to previous changes (6)
- src/update.zig
- src/linter_pref.zig
- src/nuke.zig
- src/root_policy.zig
- src/codegraph.zig
- src/cio.zig
| d = tempfile.mkdtemp() | ||
| p = os.path.join(d, "probe.py") | ||
| with open(p, "w") as fh: | ||
| fh.write(src) | ||
|
|
||
| b_conf = b_config.BanditConfig() | ||
| mgr = b_manager.BanditManager(b_conf, "file") | ||
| mgr.b_ts = b_test_set.BanditTestSet(config=b_conf) | ||
| mgr.ignore_nosec = ignore_nosec | ||
| mgr.discover_files([p], True) | ||
| mgr.run_tests() | ||
| issues = mgr.get_issue_list() | ||
| totals = mgr.metrics.data["_totals"] | ||
| return issues, totals |
There was a problem hiding this comment.
Clean up the temporary scan directory.
_scan() creates a fresh directory on every probe run and never removes it, so repeated validator runs will keep leaking files under the system temp root.
💡 Minimal fix
def _scan(src, ignore_nosec=False):
"""Run a real bandit scan over `src`. Returns (issue_list, totals_dict)."""
from bandit.core import config as b_config
from bandit.core import manager as b_manager
from bandit.core import test_set as b_test_set
- d = tempfile.mkdtemp()
- p = os.path.join(d, "probe.py")
- with open(p, "w") as fh:
- fh.write(src)
-
- b_conf = b_config.BanditConfig()
- mgr = b_manager.BanditManager(b_conf, "file")
- mgr.b_ts = b_test_set.BanditTestSet(config=b_conf)
- mgr.ignore_nosec = ignore_nosec
- mgr.discover_files([p], True)
- mgr.run_tests()
- issues = mgr.get_issue_list()
- totals = mgr.metrics.data["_totals"]
- return issues, totals
+ with tempfile.TemporaryDirectory() as d:
+ p = os.path.join(d, "probe.py")
+ with open(p, "w", encoding="utf-8") as fh:
+ fh.write(src)
+
+ b_conf = b_config.BanditConfig()
+ mgr = b_manager.BanditManager(b_conf, "file")
+ mgr.b_ts = b_test_set.BanditTestSet(config=b_conf)
+ mgr.ignore_nosec = ignore_nosec
+ mgr.discover_files([p], True)
+ mgr.run_tests()
+ issues = mgr.get_issue_list()
+ totals = dict(mgr.metrics.data["_totals"])
+ return issues, totals📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| d = tempfile.mkdtemp() | |
| p = os.path.join(d, "probe.py") | |
| with open(p, "w") as fh: | |
| fh.write(src) | |
| b_conf = b_config.BanditConfig() | |
| mgr = b_manager.BanditManager(b_conf, "file") | |
| mgr.b_ts = b_test_set.BanditTestSet(config=b_conf) | |
| mgr.ignore_nosec = ignore_nosec | |
| mgr.discover_files([p], True) | |
| mgr.run_tests() | |
| issues = mgr.get_issue_list() | |
| totals = mgr.metrics.data["_totals"] | |
| return issues, totals | |
| with tempfile.TemporaryDirectory() as d: | |
| p = os.path.join(d, "probe.py") | |
| with open(p, "w", encoding="utf-8") as fh: | |
| fh.write(src) | |
| b_conf = b_config.BanditConfig() | |
| mgr = b_manager.BanditManager(b_conf, "file") | |
| mgr.b_ts = b_test_set.BanditTestSet(config=b_conf) | |
| mgr.ignore_nosec = ignore_nosec | |
| mgr.discover_files([p], True) | |
| mgr.run_tests() | |
| issues = mgr.get_issue_list() | |
| totals = dict(mgr.metrics.data["_totals"]) | |
| return issues, totals |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bench/swe-lite/validators/validate_bandit.py` around lines 33 - 46, The temp
directory created by tempfile.mkdtemp() (variable d) is never removed, causing
temp file leaks; change the _scan() flow to ensure the directory is cleaned up
after use (e.g., wrap the write/discover/run/get_issue sequence in a try/finally
or switch to tempfile.TemporaryDirectory) and call shutil.rmtree(d) (or rely on
the context manager) after mgr.get_issue_list()/metrics access; reference
variables and functions: d, p, mgr.discover_files(...), mgr.run_tests(),
mgr.get_issue_list(), and totals = mgr.metrics.data["_totals"] so the cleanup
always runs even if run_tests() or get_issue_list() raises.
| stream = _streaming_body([b'{"a":', b' 1}']) | ||
| r = httpx.Response( | ||
| 200, headers={"Content-Type": "application/json"}, content=stream | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In current HTTPX versions, does httpx.Response(..., content=)create a true streaming response, or must streaming bodies be provided viastream= / a streaming byte-stream type?
💡 Result:
In current versions of HTTPX, providing a generator to httpx.Response(content=<generator>) does not create a true streaming response that remains unbuffered [1]. When you initialize an httpx.Response object with the content parameter, the library automatically processes and buffers the body [1]. Specifically, if the provided content is a generator (or any byte iterator), HTTPX converts it into a stream but then immediately calls self.read() on that stream, which loads the entire response body into memory [1]. This ensures that the response.content and response.text properties are immediately available after initialization [2][1]. Conversely, the stream parameter in httpx.Response is intended for low-level usage where you are manually passing an existing ByteStream (e.g., received from a transport layer) [1]. Using stream directly does not trigger the automatic read() call, allowing the body to remain unbuffered [1]. However, the official documentation notes that end-users generally do not need to use stream= directly; instead, they should use the httpx.stream() interface or client.stream() for streaming requests, which are designed to handle response streaming properly without manual byte-stream management [3][4][1].
Citations:
- 1: https://github.com/encode/httpx/blob/f13ab4d288d0b790f6f1c515a6c0ea45e9615748/httpx/_models.py
- 2: https://github.com/encode/httpx/blob/def4778d/tests/models/test_responses.py
- 3: https://github.com/encode/httpx/blob/master/docs/compatibility.md
- 4: https://www.python-httpx.org/compatibility/
Fix streaming test to avoid buffering: use stream= instead of content= (bench/swe-lite/validators/validate_httpx.py, lines 241-244)
Passing a generator via httpx.Response(..., content=generator) causes HTTPX to buffer/read the body during Response initialization, so the test may not exercise the real streaming-consumption path that iter_json() is intended to validate. Switch this to the unbuffered path by constructing Response with stream= using an httpx.ByteStream(...) wrapper around the chunk generator, so the stream is consumed on iteration and subsequent iteration raises StreamConsumed as expected.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bench/swe-lite/validators/validate_httpx.py` around lines 241 - 244, The test
currently passes a generator to httpx.Response via content= which causes HTTPX
to buffer the body at Response initialization; change the constructor to use
stream= with an httpx.ByteStream wrapper so the chunks from _streaming_body are
consumed only when iterating (e.g., httpx.Response(...,
stream=httpx.ByteStream(_streaming_body([...]))), ensuring iter_json() exercises
streaming and that a subsequent iteration raises httpx.StreamConsumed as
expected; update the instantiation that currently calls httpx.Response(200,
headers={...}, content=stream) to use stream= instead.
| ch._inplace_augment_message(msg, "exS", "rkS") | ||
| msg["properties"]["delivery_info"]["queue"] = "qS" |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Are Kombu channel methods like _inplace_augment_messageand_size considered private implementation details that may vary across versions/transports, or are they supported/public APIs?
💡 Result:
In Kombu, method names prefixed with an underscore (such as _inplace_augment_message and _size) are strictly considered private implementation details [1][2][3]. They are not supported public APIs and are intended for internal use within the library [4][2]. Key points regarding these methods include: 1. Internal Naming Convention: Following standard Python practices, methods starting with an underscore are designated as internal. Their signatures, behaviors, and existence may change, be refactored, or be removed between versions or across different transport implementations (e.g., SQLAlchemy, Redis, RabbitMQ) without warning [2][3]. 2. Transport-Specific Implementation: Methods like _size are often implemented individually by specific transport classes to handle the unique storage mechanisms of different message brokers [2][3]. Because each transport has different capabilities, there is no guarantee that a _size method exists or behaves consistently across all Kombu transports [3]. 3. Public Alternatives: If you need to interact with queues or messages, you should rely on the documented public API, such as kombu.Consumer or kombu.SimpleQueue [5][6][7]. For instance, to check the size of a queue, the public SimpleQueue.qsize() or the equivalent queue_declare with passive=True (which returns the message count) is the appropriate approach [5]. Relying on underscore-prefixed methods makes your code fragile and likely to break during Kombu library updates [2][8]. [2][3]
Citations:
- 1: https://kombu.readthedocs.io/en/latest/_modules/kombu/messaging.html
- 2: celery/kombu@b6e4747
- 3: https://docs.celeryq.dev/projects/kombu/en/v4.5.0/%5Fmodules/kombu/transport/mongodb.html
- 4: https://docs.celeryq.dev/projects/kombu/en/stable/reference/kombu.transport.base.html
- 5: https://kombu.readthedocs.io/en/latest/%5Fmodules/kombu/simple.html
- 6: https://docs.celeryq.dev/projects/kombu/en/latest/%5Fsources/reference/kombu.rst.txt
- 7: https://docs.celeryq.dev/projects/kombu/en/latest/reference/kombu.html
- 8: Remove nested query from sqlalchemy _size celery/kombu#2315
Avoid private Kombu Channel internals in the validator.
_inplace_augment_message (lines 412-413) and _size (481-485) are underscore-prefixed Kombu internals (not supported/public APIs), so this validator can break across Kombu versions/transports even when the public dead-letter/max-length behavior it’s testing is correct.
ch._inplace_augment_message(msg, "exS", "rkS")
msg["properties"]["delivery_info"]["queue"] = "qS"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bench/swe-lite/validators/validate_kombu.py` around lines 412 - 413, The code
is calling Kombu private internals ch._inplace_augment_message and _size and
mutating msg["properties"]["delivery_info"]["queue"]; replace those with public
APIs: construct the test message as a kombu.message.Message (or use
kombu.message.Message(body, delivery_info=..., properties=...)) and set the
queue name in its public delivery_info/properties instead of mutating internals,
and compute message byte-size using a public serializer (e.g.,
kombu.serialization.dumps followed by len(...) of the serialized payload) rather
than calling _size; update usages around _inplace_augment_message,
msg["properties"]["delivery_info"]["queue"], and _size to use these public
alternatives.
| def get_exc(*names): | ||
| for n in names: | ||
| c = getattr(dbmod, n, None) | ||
| if c is not None: | ||
| return c | ||
| return Exception | ||
|
|
||
| SafeImportNotEnabledError = get_exc("SafeImportNotEnabledError") | ||
| CheckpointNotActiveError = get_exc("CheckpointNotActiveError") | ||
| CheckpointNotFoundError = get_exc("CheckpointNotFoundError") |
There was a problem hiding this comment.
Fail the validator when the named checkpoint exceptions are missing.
get_exc() falls back to Exception, so checks that are supposed to verify CheckpointNotActiveError and CheckpointNotFoundError will also pass if the implementation raises the wrong type. That turns a spec violation into a false green.
🛠️ Minimal fix
def get_exc(*names):
for n in names:
c = getattr(dbmod, n, None)
if c is not None:
return c
- return Exception
+ raise AttributeError(f"Missing expected exception class: {', '.join(names)}")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def get_exc(*names): | |
| for n in names: | |
| c = getattr(dbmod, n, None) | |
| if c is not None: | |
| return c | |
| return Exception | |
| SafeImportNotEnabledError = get_exc("SafeImportNotEnabledError") | |
| CheckpointNotActiveError = get_exc("CheckpointNotActiveError") | |
| CheckpointNotFoundError = get_exc("CheckpointNotFoundError") | |
| def get_exc(*names): | |
| for n in names: | |
| c = getattr(dbmod, n, None) | |
| if c is not None: | |
| return c | |
| raise AttributeError(f"Missing expected exception class: {', '.join(names)}") | |
| SafeImportNotEnabledError = get_exc("SafeImportNotEnabledError") | |
| CheckpointNotActiveError = get_exc("CheckpointNotActiveError") | |
| CheckpointNotFoundError = get_exc("CheckpointNotFoundError") |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bench/swe-lite/validators/validate_sqliteutils.py` around lines 47 - 56, The
current get_exc() silently falls back to Exception which hides missing API
types; change get_exc to fail loudly when none of the requested names exist
(e.g., raise ImportError or RuntimeError with a clear message referencing the
requested names) so that assigning SafeImportNotEnabledError,
CheckpointNotActiveError, and CheckpointNotFoundError will error if the
implementation doesn't provide them; update references to
get_exc("SafeImportNotEnabledError"), get_exc("CheckpointNotActiveError"), and
get_exc("CheckpointNotFoundError") to rely on the new behavior (or perform an
explicit None check and raise) so the validator fails instead of silently using
Exception.
| res = db.import_csv("people", io.StringIO(csv_text), safe_mode=True, strict=False) | ||
| if isinstance(res, dict): | ||
| ok_dict = res.get("success") is True | ||
| else: | ||
| ok_dict = True |
There was a problem hiding this comment.
Require the documented non-strict safe-mode return envelope.
These branches currently treat any non-dict return as success. The supplied task spec says import_csv(..., safe_mode=True, strict=False) and import_json(..., safe_mode=True, strict=False) return structured {success: ...} envelopes, so this validator would incorrectly pass legacy or non-compliant behavior.
🛠️ Minimal fix
res = db.import_csv("people", io.StringIO(csv_text), safe_mode=True, strict=False)
- if isinstance(res, dict):
- ok_dict = res.get("success") is True
- else:
- ok_dict = True
+ ok_dict = isinstance(res, dict) and res.get("success") is True
present = "people" in db.table_names() and db["people"].count == 2
results["checks"]["import_csv_safe_mode_commits"] = present and ok_dict
@@
res = db.import_json("jt", data, safe_mode=True, strict=False)
- if isinstance(res, dict):
- ok_dict = res.get("success") is True
- else:
- ok_dict = True
+ ok_dict = isinstance(res, dict) and res.get("success") is True
present = "jt" in db.table_names() and db["jt"].count == 2
results["checks"]["import_json_safe_mode_commits"] = present and ok_dictAlso applies to: 287-291
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bench/swe-lite/validators/validate_sqliteutils.py` around lines 272 - 276,
The validator currently treats any non-dict return from db.import_csv(...,
safe_mode=True, strict=False) and db.import_json(..., safe_mode=True,
strict=False) as success; change the logic so you require the documented
safe-mode envelope: for the db.import_csv result (variable ok_dict) only set
ok_dict = True when isinstance(res, dict) and res.get("success") is True,
otherwise False; apply the same strict check to the db.import_json result
(variable ok_json) so legacy/non-compliant non-dict returns do not pass
validation.
| defer if (!load_ok) { | ||
| // POSIX mmap and Windows' aligned heap-read fallback both feed borrowed | ||
| // content slices into Explorer. If validation rejects the snapshot after | ||
| // adoption, remove files inserted during this attempt before dropping the | ||
| // backing sections they may still borrow from. | ||
| for (inserted_paths.items) |path| explorer.removeFile(path); | ||
| explorer.releaseOutlineSectionsFrom(outline_section_mark); | ||
| explorer.releaseContentSectionsFrom(content_section_mark); | ||
| }; |
There was a problem hiding this comment.
Rollback keeps borrowed path slices past the CONTENT buffer lifetime.
In the heap-read fallback, path aliases heap_section, but the heap_section defer runs before the !load_ok rollback defer. That makes the stale/outline-only branches append freed slices into inserted_paths, and explorer.removeFile(path) can read dangling memory on any later return false.
💡 Minimal fix
if (disk_content) |dc| {
word_index_can_load_from_disk = false;
if (outline_states.fetchRemove(path)) |removed| {
allocator.free(removed.key);
var stale_outline = removed.value;
stale_outline.deinit();
}
explorer.indexFile(path, dc) catch continue;
- inserted_paths.appendAssumeCapacity(path);
+ const stable = explorer.outlines.getEntry(path) orelse continue;
+ inserted_paths.appendAssumeCapacity(stable.key_ptr.*);
const hash = std.hash.Wyhash.hash(0, dc);
_ = store.recordSnapshot(path, dc.len, hash) catch {};
} else if (outline_states.fetchRemove(path)) |removed| {
const t_ins: i128 = if (prof) cio.nanoTimestamp() else 0;
insertRestoredFile(explorer, removed.key, content, removed.value, allocator, content_borrowed) catch {
@@
} else {
word_index_can_load_from_disk = false;
explorer.indexFileOutlineOnly(path, content) catch continue;
- inserted_paths.appendAssumeCapacity(path);
+ const stable = explorer.outlines.getEntry(path) orelse continue;
+ inserted_paths.appendAssumeCapacity(stable.key_ptr.*);
const hash = record.stored_hash orelse std.hash.Wyhash.hash(0, content);
_ = store.recordSnapshot(path, content.len, hash) catch {};
}Also applies to: 991-1017, 1146-1174
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/snapshot.zig` around lines 913 - 921, The rollback defer is calling
explorer.removeFile(path) where path is a borrowed slice that can alias the
heap_section/CMS CONTENT buffer which is freed earlier by the heap_section
defer; fix by ensuring inserted_paths stores owned copies instead of borrowed
slices (or move the rollback defer to run before heap_section defer).
Specifically, change the code paths that append into inserted_paths to duplicate
the path (e.g., allocate/duplicate the string into the snapshot/explorer
allocator) so that entries in inserted_paths outlive heap_section/CONTENT, and
keep the existing rollback defer that calls explorer.removeFile(path) and the
symbols load_ok, inserted_paths, heap_section, explorer.removeFile,
releaseOutlineSectionsFrom and releaseContentSectionsFrom intact.
| pub fn isSensitivePath(path: []const u8) bool { | ||
| const basename = if (std.mem.lastIndexOfScalar(u8, path, '/')) |sep| path[sep + 1 ..] else path; | ||
| // Fast path: most source files have extensions like .zig, .ts, .py — none start with '.' | ||
| // or match sensitive patterns. Skip the full check for common cases. | ||
| if (basename.len == 0) return false; | ||
| const first = basename[0]; | ||
| // Only check sensitive names if basename starts with '.', 'c', 's', 'i' or has key/cert extension | ||
| if (first != '.' and first != 'c' and first != 's' and first != 'i') { | ||
| // Still need to check extensions and directory patterns | ||
| if (std.mem.endsWith(u8, basename, ".env") or | ||
| std.mem.endsWith(u8, basename, ".pem") or | ||
| std.mem.endsWith(u8, basename, ".key") or | ||
| std.mem.endsWith(u8, basename, ".p12") or | ||
| std.mem.endsWith(u8, basename, ".pfx") or | ||
| std.mem.endsWith(u8, basename, ".jks")) return true; | ||
| if (std.mem.indexOf(u8, path, ".ssh/") != null or | ||
| std.mem.indexOf(u8, path, ".gnupg/") != null or | ||
| std.mem.indexOf(u8, path, ".aws/") != null) return true; | ||
| return false; | ||
| } | ||
| // .env, .env.<token>; do NOT match .envoy, .envrc, .environment, etc. | ||
| if (basename.len >= 4 and std.mem.eql(u8, basename[0..4], ".env") and | ||
| (basename.len == 4 or basename[4] == '.' or basename[4] == '-' or basename[4] == '_')) return true; | ||
| // Exact matches | ||
| const sensitive_names = [_][]const u8{ | ||
| ".env", | ||
| ".env.local", | ||
| ".env.production", | ||
| ".env.development", | ||
| ".env.staging", | ||
| ".env.test", | ||
| ".dev.vars", | ||
| "credentials.json", | ||
| "service-account.json", | ||
| "secrets.json", | ||
| "secrets.yaml", | ||
| "secrets.yml", | ||
| ".npmrc", | ||
| ".pypirc", | ||
| ".netrc", | ||
| "id_rsa", | ||
| "id_ed25519", | ||
| ".pem", | ||
| ".dev.vars", ".npmrc", ".pypirc", ".netrc", | ||
| "credentials.json", "service-account.json", "secrets.json", "secrets.yaml", | ||
| "secrets.yml", "id_rsa", "id_ed25519", ".git-credentials", | ||
| "id_ecdsa", "id_dsa", "id_ecdsa_sk", "id_ed25519_sk", | ||
| }; | ||
|
|
||
| // Check exact filename (basename) | ||
| const basename = if (std.mem.lastIndexOfScalar(u8, path, '/')) |sep| path[sep + 1 ..] else path; | ||
|
|
||
| for (sensitive_names) |name| { | ||
| if (std.mem.eql(u8, basename, name)) return true; | ||
| } | ||
|
|
||
| // Catch .env, .env.anything; do NOT match .envoy, .envrc, .environment, etc. | ||
| if (basename.len >= 4 and std.mem.eql(u8, basename[0..4], ".env") and | ||
| (basename.len == 4 or basename[4] == '.' or basename[4] == '-' or basename[4] == '_')) return true; | ||
|
|
||
| // Check extensions | ||
| if (endsWith(basename, ".pem")) return true; | ||
| if (endsWith(basename, ".key")) return true; | ||
| if (endsWith(basename, ".p12")) return true; | ||
| if (endsWith(basename, ".pfx")) return true; | ||
| if (endsWith(basename, ".jks")) return true; | ||
|
|
||
| // Check directory patterns | ||
| if (std.mem.indexOf(u8, path, ".ssh/") != null) return true; | ||
| if (std.mem.indexOf(u8, path, ".gnupg/") != null) return true; | ||
| if (std.mem.indexOf(u8, path, ".aws/") != null) return true; | ||
|
|
||
| if (std.mem.endsWith(u8, basename, ".env") or | ||
| std.mem.endsWith(u8, basename, ".pem") or | ||
| std.mem.endsWith(u8, basename, ".key") or | ||
| std.mem.endsWith(u8, basename, ".p12") or | ||
| std.mem.endsWith(u8, basename, ".pfx") or | ||
| std.mem.endsWith(u8, basename, ".jks")) return true; | ||
| if (std.mem.indexOf(u8, path, ".ssh/") != null or | ||
| std.mem.indexOf(u8, path, ".gnupg/") != null or | ||
| std.mem.indexOf(u8, path, ".aws/") != null) return true; | ||
| return false; |
There was a problem hiding this comment.
Windows paths bypass the sensitive-file filter.
isSensitivePath only splits on '/' and only checks ".ssh/", ".gnupg/", and ".aws/". On the new Windows port, paths like C:\Users\alice\.ssh\id_rsa or ...\ .aws\credentials keep the full path as basename, so the exact-name checks miss and those secrets can still be snapshotted or live-indexed.
💡 Minimal fix
- const basename = if (std.mem.lastIndexOfScalar(u8, path, '/')) |sep| path[sep + 1 ..] else path;
+ const basename = if (std.mem.lastIndexOfAny(u8, path, "/\\")) |sep| path[sep + 1 ..] else path;
@@
- if (std.mem.indexOf(u8, path, ".ssh/") != null or
- std.mem.indexOf(u8, path, ".gnupg/") != null or
- std.mem.indexOf(u8, path, ".aws/") != null) return true;
+ if (std.mem.indexOf(u8, path, ".ssh/") != null or
+ std.mem.indexOf(u8, path, ".ssh\\") != null or
+ std.mem.indexOf(u8, path, ".gnupg/") != null or
+ std.mem.indexOf(u8, path, ".gnupg\\") != null or
+ std.mem.indexOf(u8, path, ".aws/") != null or
+ std.mem.indexOf(u8, path, ".aws\\") != null) return true;
@@
- if (std.mem.indexOf(u8, path, ".ssh/") != null or
- std.mem.indexOf(u8, path, ".gnupg/") != null or
- std.mem.indexOf(u8, path, ".aws/") != null) return true;
+ if (std.mem.indexOf(u8, path, ".ssh/") != null or
+ std.mem.indexOf(u8, path, ".ssh\\") != null or
+ std.mem.indexOf(u8, path, ".gnupg/") != null or
+ std.mem.indexOf(u8, path, ".gnupg\\") != null or
+ std.mem.indexOf(u8, path, ".aws/") != null or
+ std.mem.indexOf(u8, path, ".aws\\") != null) return true;This risk is amplified by the Windows-native port scope in this PR.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pub fn isSensitivePath(path: []const u8) bool { | |
| const basename = if (std.mem.lastIndexOfScalar(u8, path, '/')) |sep| path[sep + 1 ..] else path; | |
| // Fast path: most source files have extensions like .zig, .ts, .py — none start with '.' | |
| // or match sensitive patterns. Skip the full check for common cases. | |
| if (basename.len == 0) return false; | |
| const first = basename[0]; | |
| // Only check sensitive names if basename starts with '.', 'c', 's', 'i' or has key/cert extension | |
| if (first != '.' and first != 'c' and first != 's' and first != 'i') { | |
| // Still need to check extensions and directory patterns | |
| if (std.mem.endsWith(u8, basename, ".env") or | |
| std.mem.endsWith(u8, basename, ".pem") or | |
| std.mem.endsWith(u8, basename, ".key") or | |
| std.mem.endsWith(u8, basename, ".p12") or | |
| std.mem.endsWith(u8, basename, ".pfx") or | |
| std.mem.endsWith(u8, basename, ".jks")) return true; | |
| if (std.mem.indexOf(u8, path, ".ssh/") != null or | |
| std.mem.indexOf(u8, path, ".gnupg/") != null or | |
| std.mem.indexOf(u8, path, ".aws/") != null) return true; | |
| return false; | |
| } | |
| // .env, .env.<token>; do NOT match .envoy, .envrc, .environment, etc. | |
| if (basename.len >= 4 and std.mem.eql(u8, basename[0..4], ".env") and | |
| (basename.len == 4 or basename[4] == '.' or basename[4] == '-' or basename[4] == '_')) return true; | |
| // Exact matches | |
| const sensitive_names = [_][]const u8{ | |
| ".env", | |
| ".env.local", | |
| ".env.production", | |
| ".env.development", | |
| ".env.staging", | |
| ".env.test", | |
| ".dev.vars", | |
| "credentials.json", | |
| "service-account.json", | |
| "secrets.json", | |
| "secrets.yaml", | |
| "secrets.yml", | |
| ".npmrc", | |
| ".pypirc", | |
| ".netrc", | |
| "id_rsa", | |
| "id_ed25519", | |
| ".pem", | |
| ".dev.vars", ".npmrc", ".pypirc", ".netrc", | |
| "credentials.json", "service-account.json", "secrets.json", "secrets.yaml", | |
| "secrets.yml", "id_rsa", "id_ed25519", ".git-credentials", | |
| "id_ecdsa", "id_dsa", "id_ecdsa_sk", "id_ed25519_sk", | |
| }; | |
| // Check exact filename (basename) | |
| const basename = if (std.mem.lastIndexOfScalar(u8, path, '/')) |sep| path[sep + 1 ..] else path; | |
| for (sensitive_names) |name| { | |
| if (std.mem.eql(u8, basename, name)) return true; | |
| } | |
| // Catch .env, .env.anything; do NOT match .envoy, .envrc, .environment, etc. | |
| if (basename.len >= 4 and std.mem.eql(u8, basename[0..4], ".env") and | |
| (basename.len == 4 or basename[4] == '.' or basename[4] == '-' or basename[4] == '_')) return true; | |
| // Check extensions | |
| if (endsWith(basename, ".pem")) return true; | |
| if (endsWith(basename, ".key")) return true; | |
| if (endsWith(basename, ".p12")) return true; | |
| if (endsWith(basename, ".pfx")) return true; | |
| if (endsWith(basename, ".jks")) return true; | |
| // Check directory patterns | |
| if (std.mem.indexOf(u8, path, ".ssh/") != null) return true; | |
| if (std.mem.indexOf(u8, path, ".gnupg/") != null) return true; | |
| if (std.mem.indexOf(u8, path, ".aws/") != null) return true; | |
| if (std.mem.endsWith(u8, basename, ".env") or | |
| std.mem.endsWith(u8, basename, ".pem") or | |
| std.mem.endsWith(u8, basename, ".key") or | |
| std.mem.endsWith(u8, basename, ".p12") or | |
| std.mem.endsWith(u8, basename, ".pfx") or | |
| std.mem.endsWith(u8, basename, ".jks")) return true; | |
| if (std.mem.indexOf(u8, path, ".ssh/") != null or | |
| std.mem.indexOf(u8, path, ".gnupg/") != null or | |
| std.mem.indexOf(u8, path, ".aws/") != null) return true; | |
| return false; | |
| pub fn isSensitivePath(path: []const u8) bool { | |
| const basename = if (std.mem.lastIndexOfAny(u8, path, "/\\")) |sep| path[sep + 1 ..] else path; | |
| // Fast path: most source files have extensions like .zig, .ts, .py — none start with '.' | |
| // or match sensitive patterns. Skip the full check for common cases. | |
| if (basename.len == 0) return false; | |
| const first = basename[0]; | |
| // Only check sensitive names if basename starts with '.', 'c', 's', 'i' or has key/cert extension | |
| if (first != '.' and first != 'c' and first != 's' and first != 'i') { | |
| // Still need to check extensions and directory patterns | |
| if (std.mem.endsWith(u8, basename, ".env") or | |
| std.mem.endsWith(u8, basename, ".pem") or | |
| std.mem.endsWith(u8, basename, ".key") or | |
| std.mem.endsWith(u8, basename, ".p12") or | |
| std.mem.endsWith(u8, basename, ".pfx") or | |
| std.mem.endsWith(u8, basename, ".jks")) return true; | |
| if (std.mem.indexOf(u8, path, ".ssh/") != null or | |
| std.mem.indexOf(u8, path, ".ssh\\") != null or | |
| std.mem.indexOf(u8, path, ".gnupg/") != null or | |
| std.mem.indexOf(u8, path, ".gnupg\\") != null or | |
| std.mem.indexOf(u8, path, ".aws/") != null or | |
| std.mem.indexOf(u8, path, ".aws\\") != null) return true; | |
| return false; | |
| } | |
| // .env, .env.<token>; do NOT match .envoy, .envrc, .environment, etc. | |
| if (basename.len >= 4 and std.mem.eql(u8, basename[0..4], ".env") and | |
| (basename.len == 4 or basename[4] == '.' or basename[4] == '-' or basename[4] == '_')) return true; | |
| // Exact matches | |
| const sensitive_names = [_][]const u8{ | |
| ".dev.vars", ".npmrc", ".pypirc", ".netrc", | |
| "credentials.json", "service-account.json", "secrets.json", "secrets.yaml", | |
| "secrets.yml", "id_rsa", "id_ed25519", ".git-credentials", | |
| "id_ecdsa", "id_dsa", "id_ecdsa_sk", "id_ed25519_sk", | |
| }; | |
| for (sensitive_names) |name| { | |
| if (std.mem.eql(u8, basename, name)) return true; | |
| } | |
| if (std.mem.endsWith(u8, basename, ".env") or | |
| std.mem.endsWith(u8, basename, ".pem") or | |
| std.mem.endsWith(u8, basename, ".key") or | |
| std.mem.endsWith(u8, basename, ".p12") or | |
| std.mem.endsWith(u8, basename, ".pfx") or | |
| std.mem.endsWith(u8, basename, ".jks")) return true; | |
| if (std.mem.indexOf(u8, path, ".ssh/") != null or | |
| std.mem.indexOf(u8, path, ".ssh\\") != null or | |
| std.mem.indexOf(u8, path, ".gnupg/") != null or | |
| std.mem.indexOf(u8, path, ".gnupg\\") != null or | |
| std.mem.indexOf(u8, path, ".aws/") != null or | |
| std.mem.indexOf(u8, path, ".aws\\") != null) return true; | |
| return false; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/snapshot.zig` around lines 1331 - 1373, isSensitivePath currently only
splits on '/' and only searches for UNIX-style directory tokens, which lets
Windows paths (backslashes) bypass checks; update isSensitivePath to treat both
'/' and '\\' as path separators when computing basename (e.g., use lastIndexOf
for both or a helper that finds the last of '/' or '\\'), and update
directory-pattern checks (std.mem.indexOf(u8, path, ".ssh/") etc.) to also look
for the backslash variants (".ssh\\", ".gnupg\\", ".aws\\") or check for the
token followed by either separator; keep the existing basename exact-name and
extension checks but run them on the normalized basename extracted using the new
separator logic so Windows paths are correctly flagged.
| // Advisory lock for cross-process safety. If it cannot be | ||
| // acquired, skip the diff persist rather than write unlocked | ||
| // (#597) — the version still records with data_offset = null. | ||
| const locked = blk: { | ||
| log.lock(io, .exclusive) catch break :blk false; | ||
| break :blk true; | ||
| }; | ||
| defer if (locked) log.unlock(io); | ||
| if (locked) { | ||
| defer log.unlock(io); | ||
|
|
||
| // Re-stat to get current end position (another process may have appended) | ||
| const end_pos = log.length(io) catch return error.Unexpected; | ||
| self.data_log_pos = end_pos; | ||
| // Re-stat to get current end position (another process may have appended) | ||
| const end_pos = log.length(io) catch return error.Unexpected; | ||
| self.data_log_pos = end_pos; | ||
|
|
||
| data_offset = self.data_log_pos; | ||
| data_len = @intCast(d.len); | ||
| try log.writePositionalAll(io, d, self.data_log_pos); | ||
| self.data_log_pos += d.len; | ||
| data_offset = self.data_log_pos; | ||
| data_len = @intCast(d.len); | ||
| try log.writePositionalAll(io, d, self.data_log_pos); | ||
| self.data_log_pos += d.len; | ||
| } |
There was a problem hiding this comment.
Compaction is not safe with the shared cross-process diff log.
appendVersion treats data_log as a multi-process file (lock(...) + re-stat current end before write), but compactDataLog rewrites and truncates using only this process's self.files. Any diffs appended by another process are invisible here and can be truncated as "orphaned" bytes even though that process still has live data_offsets pointing at them.
Also applies to: 147-215
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/store.zig` around lines 99 - 117, appendVersion treats data_log as a
multi-process file (uses log.lock and re-stat) but compactDataLog currently
rewrites/truncates using only this process's self.files and can therefore drop
bytes other processes referenced; fix compactDataLog to acquire the same
advisory lock on data_log (same lock(...) path used by appendVersion) and while
holding that exclusive lock re-stat the file, scan all live version records
across self.files to compute the true highest referenced data_offset/length (not
just this process's view), rebuild the compacted log into a temporary file and
atomically replace/truncate the original only after verifying offsets against
the locked+re-statted end_pos, and update self.data_log_pos and any in-process
metadata consistently while still holding the lock so no concurrent append can
make offsets stale; ensure symbols touched include compactDataLog,
appendVersion, data_log, self.files, data_log_pos and log.lock/log.unlock.
| cio.posixSetenv("CODEDB_LEX_FREQ_PENALTY", "0"); | ||
| defer cio.posixUnsetenv("CODEDB_LEX_FREQ_PENALTY"); | ||
| { | ||
| const results = try explorer.searchContent("evt", testing.allocator, 50); | ||
| defer { | ||
| for (results) |r| { | ||
| testing.allocator.free(r.path); | ||
| testing.allocator.free(r.line_text); | ||
| } | ||
| testing.allocator.free(results); | ||
| } | ||
| try testing.expect(results.len >= 2); | ||
| try testing.expectEqualStrings("src/dispatcher.zig", results[0].path); | ||
| } | ||
|
|
||
| // Default (on): dispatcher.zig saturates the query → demoted below handler.zig. | ||
| cio.posixUnsetenv("CODEDB_LEX_FREQ_PENALTY"); | ||
| { |
There was a problem hiding this comment.
Restore pre-existing env var state to prevent cross-test leakage.
Line 1115 and Line 1131 mutate a process-global env var but only unset it. If CODEDB_LEX_FREQ_PENALTY was already set before this test, the original value is lost, making later tests order-dependent.
Suggested minimal fix
- cio.posixSetenv("CODEDB_LEX_FREQ_PENALTY", "0");
- defer cio.posixUnsetenv("CODEDB_LEX_FREQ_PENALTY");
+ const prev = std.process.getEnvVarOwned(testing.allocator, "CODEDB_LEX_FREQ_PENALTY") catch null;
+ defer if (prev) |v| {
+ defer testing.allocator.free(v);
+ cio.posixSetenv("CODEDB_LEX_FREQ_PENALTY", v);
+ } else {
+ cio.posixUnsetenv("CODEDB_LEX_FREQ_PENALTY");
+ };
+ cio.posixSetenv("CODEDB_LEX_FREQ_PENALTY", "0");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| cio.posixSetenv("CODEDB_LEX_FREQ_PENALTY", "0"); | |
| defer cio.posixUnsetenv("CODEDB_LEX_FREQ_PENALTY"); | |
| { | |
| const results = try explorer.searchContent("evt", testing.allocator, 50); | |
| defer { | |
| for (results) |r| { | |
| testing.allocator.free(r.path); | |
| testing.allocator.free(r.line_text); | |
| } | |
| testing.allocator.free(results); | |
| } | |
| try testing.expect(results.len >= 2); | |
| try testing.expectEqualStrings("src/dispatcher.zig", results[0].path); | |
| } | |
| // Default (on): dispatcher.zig saturates the query → demoted below handler.zig. | |
| cio.posixUnsetenv("CODEDB_LEX_FREQ_PENALTY"); | |
| { | |
| const prev = std.process.getEnvVarOwned(testing.allocator, "CODEDB_LEX_FREQ_PENALTY") catch null; | |
| defer if (prev) |v| { | |
| defer testing.allocator.free(v); | |
| cio.posixSetenv("CODEDB_LEX_FREQ_PENALTY", v); | |
| } else { | |
| cio.posixUnsetenv("CODEDB_LEX_FREQ_PENALTY"); | |
| }; | |
| cio.posixSetenv("CODEDB_LEX_FREQ_PENALTY", "0"); | |
| { | |
| const results = try explorer.searchContent("evt", testing.allocator, 50); | |
| defer { | |
| for (results) |r| { | |
| testing.allocator.free(r.path); | |
| testing.allocator.free(r.line_text); | |
| } | |
| testing.allocator.free(results); | |
| } | |
| try testing.expect(results.len >= 2); | |
| try testing.expectEqualStrings("src/dispatcher.zig", results[0].path); | |
| } | |
| // Default (on): dispatcher.zig saturates the query → demoted below handler.zig. | |
| cio.posixUnsetenv("CODEDB_LEX_FREQ_PENALTY"); | |
| { |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/test_search.zig` around lines 1115 - 1132, Before you call
cio.posixSetenv("CODEDB_LEX_FREQ_PENALTY", "0") save the current environment
value (e.g., via cio.posixGetenv or std.os.getenv) into a local optional string,
and then in a defer restore it: if an original value existed call
cio.posixSetenv with that value, otherwise call cio.posixUnsetenv; do the same
pattern for the later block that calls cio.posixUnsetenv so the test always
restores the pre-test state of CODEDB_LEX_FREQ_PENALTY rather than permanently
unsetting it.
| var explorer = Explorer.init(testing.allocator, Explorer.DEFAULT_CONTENT_CACHE_CAPACITY); | ||
| defer explorer.deinit(); |
There was a problem hiding this comment.
Use ArenaAllocator for Explorer tests in this file.
Line 1815, Line 1903, and Line 1948 initialize Explorer with testing.allocator. In src/test_*.zig, these should use std.heap.ArenaAllocator per repo guideline.
Suggested pattern
- var explorer = Explorer.init(testing.allocator, Explorer.DEFAULT_CONTENT_CACHE_CAPACITY);
- defer explorer.deinit();
+ var arena = std.heap.ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ var explorer = Explorer.init(arena.allocator(), Explorer.DEFAULT_CONTENT_CACHE_CAPACITY);As per coding guidelines, src/test_*.zig: “Use std.heap.ArenaAllocator for Explorer tests in Zig”.
Also applies to: 1903-1904, 1948-1949
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/test_search.zig` around lines 1815 - 1816, Replace direct use of
testing.allocator when initializing Explorer with a std.heap.ArenaAllocator
instance: create an ArenaAllocator (e.g. via
std.heap.ArenaAllocator.init(testing.allocator)), pass the arena's allocator to
Explorer.init (instead of testing.allocator) for the
Explorer.DEFAULT_CONTENT_CACHE_CAPACITY calls, and ensure you deinit the arena
after the Explorer (mirror Explorer.deinit with arena.deinit/destroy as
appropriate). Update the three sites that call Explorer.init to use the arena
allocator and add the corresponding arena lifetime management adjacent to those
tests.
Source: Coding guidelines
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/root_policy.zig`:
- Around line 59-63: The Windows-root checker is missing normalization for Win32
device-prefix paths (e.g. "\\?\\C:\\..." and "\\.\\C:\\...") so functions like
isNativeWindowsSystemRoot bypass the drive-letter checks; update
isNativeWindowsSystemRoot to detect and strip the "\\?\" and "\\.\" UNC device
prefixes (and any equivalent case variants) before the existing drive-letter and
path-separator logic, then apply the same normalization change to the other
Windows classifier referenced around lines 172-185 (the other Windows
root-checking function), and add/adjust unit tests to cover "\\?\\C:\\...",
"\\.\\C:\\...", and ordinary "C:\\..." forms to ensure deny logic triggers
consistently.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d632d33e-665f-45f5-9758-f54c395f90a1
📒 Files selected for processing (1)
src/root_policy.zig
| fn isNativeWindowsSystemRoot(path: []const u8) bool { | ||
| if (path.len < 2 or path[1] != ':' or !std.ascii.isAlphabetic(path[0])) return false; | ||
| if (path.len == 2) return true; | ||
| if (!isPathSep(path[2])) return false; | ||
| if (path.len == 3) return true; |
There was a problem hiding this comment.
Normalize Win32 device-prefix paths before Windows root checks.
Line 60 assumes the drive letter is at byte 0, so valid absolute forms like \\?\C:\Windows\System32 and \\.\C:\ProgramData bypass the deny logic and remain indexable. Normalize those prefixes before both Windows classifiers, and add coverage for them.
Suggested targeted fix
+fn normalizeWindowsDrivePath(path_in: []const u8) []const u8 {
+ if (path_in.len >= 7 and
+ isPathSep(path_in[0]) and isPathSep(path_in[1]) and
+ (path_in[2] == '?' or path_in[2] == '.') and
+ isPathSep(path_in[3]) and
+ std.ascii.isAlphabetic(path_in[4]) and
+ path_in[5] == ':' and
+ isPathSep(path_in[6]))
+ {
+ return path_in[4..];
+ }
+ return path_in;
+}
+
-fn isNativeWindowsTempRoot(path: []const u8) bool {
+fn isNativeWindowsTempRoot(path_in: []const u8) bool {
+ const path = normalizeWindowsDrivePath(path_in);
if (path.len < 3 or path[1] != ':' or !std.ascii.isAlphabetic(path[0]) or !isPathSep(path[2])) return false;
@@
-fn isNativeWindowsSystemRoot(path: []const u8) bool {
+fn isNativeWindowsSystemRoot(path_in: []const u8) bool {
+ const path = normalizeWindowsDrivePath(path_in);
if (path.len < 2 or path[1] != ':' or !std.ascii.isAlphabetic(path[0])) return false;
@@
test "windows system and drive roots are denied" {
@@
try testing.expect(!isIndexableRoot("C:\\ProgramData\\Vendor"));
+ try testing.expect(!isIndexableRoot("\\\\?\\C:\\Windows\\System32"));
+ try testing.expect(!isIndexableRoot("\\\\.\\C:\\ProgramData\\Vendor"));
try testing.expect(isIndexableRoot("C:\\Users\\dev\\projects\\repo"));Also applies to: 172-185
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/root_policy.zig` around lines 59 - 63, The Windows-root checker is
missing normalization for Win32 device-prefix paths (e.g. "\\?\\C:\\..." and
"\\.\\C:\\...") so functions like isNativeWindowsSystemRoot bypass the
drive-letter checks; update isNativeWindowsSystemRoot to detect and strip the
"\\?\" and "\\.\" UNC device prefixes (and any equivalent case variants) before
the existing drive-letter and path-separator logic, then apply the same
normalization change to the other Windows classifier referenced around lines
172-185 (the other Windows root-checking function), and add/adjust unit tests to
cover "\\?\\C:\\...", "\\.\\C:\\...", and ordinary "C:\\..." forms to ensure
deny logic triggers consistently.
What
Native Windows port branch plus a quality/cleanup pass and new test coverage:
cio.userHomeformat=jsonerror envelopes for codedb_search/codedb_symbol were invalid JSON (trailing diagnostic text after the closing brace); now schema-stable with tests that parse the real dispatch outputcio.processId()returned 0 on Windows; nowGetCurrentProcessId.codex-tools/, drop committed machine-specificcodedb.snapshotKnown-failing test (intentional)
test_mcp.zigcontainsissue-XX: spawnDetached command line round-trips argv with trailing backslashes— a deliberately failing test documenting a real quoting bug incio.windowsCommandLine(args ending in\, e.g. drive-root project paths, fuse with the following arg under CommandLineToArgvW rules). Fix to follow in a separate change.Verification
zig build test: 738/746 pass, 7 skipped, 1 intentional failure (above)Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Chores
Tests