fix(mcp): defer scan until roots handshake; harden root_policy#348
Conversation
Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de6b24ffeb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| if (s.deferred_scan) |ds| { | ||
| if (!ds.triggered.swap(true, .acq_rel)) { |
There was a problem hiding this comment.
Trigger deferred indexing only after accepting a root
The deferred-scan gate is flipped before confirming that any valid root was parsed, so an initial roots/list response with zero allowed roots permanently disables later scan attempts. In clients that send an empty roots set first and then roots/list_changed, ds.triggered is already true, so indexing never starts even after a valid workspace arrives.
Useful? React with 👍 / 👎.
| if (!snapshot_loaded) { | ||
| mcp_server.setScanState(.walking); | ||
| const scan_thread = std.Thread.spawn(.{}, scanBg, .{ ctx.io, ctx.store, ctx.explorer, abs_root, ctx.allocator, ctx.scan_done, ctx.shutdown, data_dir, abs_root, ctx.telem, ctx.startup_t0 }) catch return; | ||
| scan_thread.detach(); |
There was a problem hiding this comment.
Keep deferred scan thread joinable during shutdown
The deferred path detaches the scanBg worker, but shutdown only joins scan_thread from the non-deferred branch, so this background thread can outlive the MCP loop and continue touching store/explorer while mainImpl is tearing them down. If the client disconnects during the first deferred scan, this creates a race/use-after-free risk.
Useful? React with 👍 / 👎.
| std.process.exit(1); | ||
| }; | ||
| if (!root_policy.isIndexableRoot(abs_root)) { | ||
| const mcp_deferred_root = std.mem.eql(u8, cmd, "mcp") and std.mem.eql(u8, root, "."); |
There was a problem hiding this comment.
Distinguish implicit root from explicit '.' argument
Deferred mode is keyed on root == ".", which also matches the explicit invocation codedb . mcp. That path is user-provided and should behave like an explicit root, but it is treated as ambient/deferred, so non-roots clients can end up with no indexing despite a path being supplied.
Useful? React with 👍 / 👎.
After resolving conflicts by taking origin/release/0.2.579 for the tools_list and handleRemote hunks, re-applied all deferred-scan changes to src/mcp.zig: DeferredScan struct, Session.deferred_scan field, updated run() signature, and parseRoots trigger call. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Benchmark Regression ReportThresholds: 10.00% and 50,000 ns absolute delta
|
Summary
Fixes #346 — codedb MCP server was crashing on startup when launched by Cursor, Windsurf, or VS Code because those editors spawn the process with an unpredictable cwd (often
/), which hit theisIndexableRootguard and exited before even responding toinitialize.Closes #346
Closes #347
Closes #278
Root cause
Two bugs compounding each other:
isIndexableRootcheck ran unconditionally at startup — killed the process before the MCP handshake could completescanBgfired immediately on cwd, which could be/,/Applications,/usr, etc.Fix
root_policy.zig: block system path prefixes (/Applications,/usr,/opt,/System,/Library,/bin,/sbin,/etc,/dev,/proc,/sys,/snap,/nix,/var/folders)main.zig: skip theisIndexableRootcheck when in MCP mode with no explicit path arg (deferred mode); introduceDeferredScancontext andtriggerScanFromRootscallbackmcp.zig: afterroots/listresponse arrives and first valid root is parsed, fire the scan callback with the real project path and updateProjectCache.default_pathBehaviour after fix
Test plan
issue-346: root_policy rejects dangerous ambient cwd rootsnow passescwd=/, client sends roots, hot returns files from correct projectGenerated with Devin