Repository navigation
fix(mcp): a projectPath naming a file resolves through its directory - #3075
Merged
Merged
Conversation
An agent that passed the file it was looking at as `projectPath` got
`isError: true` ("Path is not a directory"), while a path that doesn't
exist at all walked up to the project's `.codegraph/` fine. An error teaches
an agent to stop calling codegraph, and this mistake is recoverable.
`execute()` now takes an existing file to stand for its directory, right
after the length check, and rewrites `args.projectPath` there, so the
project gate, the worker dispatch, the worktree notice and getCodeGraph all
see that directory. A path that can't be stat'd walks up as before.
Path safety: the directory goes through the same checks as any
projectPath, the sensitive-folder refusal on the path itself and on the
root its up-walk lands on, so a file reaches exactly what its directory
reaches and never more. `/etc/hosts` is refused as `/etc` is, a file in
`~/.ssh` as `~/.ssh` is, and `win.ini` in the Windows folder as that folder
is. A symlink inside a project that points at a file in a sensitive folder
is answered from the link's own folder, as a symlinked folder already is;
nothing in the sensitive folder is opened.
Validation on main de0a4eb (Windows 11):
- Red/green: the PR's 2 tests (a file in an indexed project, and a file in
a linked worktree, which keeps its worktree notice) and 1 of ours fail
with main's tools.ts and pass with the fix. Ours asks for a file and for
its directory in 4 cases, an indexed project, `~/.ssh`, a folder in no
project, and the Windows folder's `win.ini`, and requires the same
answer for both, refusals included.
- A real `serve --mcp` session over stdio with a private HOME: on main,
codegraph_search and codegraph_status for `<project>/src/a.ts` both
returned isError "Path is not a directory"; with the fix both answer from
the project, and `win.ini` is refused as a sensitive system directory.
Original PR #2996 by @maxmilian.
Fixes #2982
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
An MCP tool call whose
projectPathnames a file inside a project, such as the file an agent is looking at, now answers from that project. Before, it gotisError: true("Path is not a directory"), while a path that doesn't exist at all walked up to the project's.codegraph/fine. An error like that teaches an agent to stop calling codegraph, and this mistake is recoverable (AGENTS.md, "Errors teach abandonment").Lands #2996 by @maxmilian.
Root cause
getCodeGraphrunsvalidateProjectPathon any path that exists. Besides the sensitive-folder check, that function requires a directory, so an existing file was refused as a security-stylePathRefusalError.The fix (from @maxmilian, verbatim)
Right after the length check,
execute()takes an existing file to stand for its directory and rewritesargs.projectPaththere. The project gate, the worker dispatch, the #155 worktree notice andgetCodeGraphthen all see that directory. A path that can't be stat'd walks up as before.Path safety
A file reaches exactly what its directory reaches, and never more. The directory goes through every check any
projectPathgets: the sensitive-folder refusal on the path itself when it exists, and on the root its up-walk lands on (#2929, #1930; #3012 left this split assensitiveDirectoryRefusal). The directory is also a path the agent could pass itself, so no index is reachable that wasn't before./etc/hostsis refused as/etcis, a file in~/.sshas~/.sshis, andC:\Windows\win.iniasC:\Windowsis.isError; everything else stays success-shaped.What we added
answers a file exactly as it answers the folder holding it. It asks for a file and for its directory in 4 cases and requires the same answer for both,isErrorand text alike: an indexed project,~/.ssh(in the sandboxed home), a folder in no project, and, on Windows,C:\Windows\win.ini.### Fixesto beside the otherprojectPathbullet, with the contributor credited.Validation (Windows 11, main de0a4eb)
tools.tsand pass with the fix.serve --mcpsession over stdio, with a private HOME, CODEGRAPH_NO_DAEMON=1:codegraph_search,projectPath: <project>\src\a.tscodegraph_status, same pathcodegraph_search,projectPath: C:\Windows\win.iniCODEGRAPH_KERNEL_EXPECT=1 npx vitest run --maxWorkers=4 --minWorkers=1, with CODEGRAPH_NO_DAEMON unset: Test Files 813 passed | 1 failed | 6 skipped (820); Tests 9638 passed | 1 failed | 87 skipped (9726). The one failure, mcp-daemon.test.ts > "a proxy serving in-process hands the writer lock back to a fresh daemon (A proxy that falls back to in-process serving keeps the project's writer lock for life #2277)", is the known daemon-timing flake under full-suite load ("Daemon candidates … were still running"). The file passes alone, twice, on this commit.Fixes #2982
Lands #2996 by @maxmilian
🤖 Generated with Claude Code