Agent/IDE CLI config or sandbox directories some clients launch their MCP
server from, but which are never a user’s project root. Adopting one as the
project root jails every real repository path out — the root cause of #580
(GitHub Copilot CLI launches from ~/.copilot; Cursor, Windsurf, Gemini CLI
and LM Studio behave similarly). This is the canonical set: every “is this an
agent dir?” check across the codebase delegates here so the list never drifts.
SECURITY canonicalize: always resolves symlinks, even under ~/Documents in a
launchd-standalone process. PathJail relies on this to detect symlink jail
escapes (#356 must never weaken the security boundary). A standalone process
only reaches here for a path the client explicitly asked to access, where a
one-time TCC prompt is legitimate — unlike the self-initiated heuristic
probes that safe_canonicalize suppresses.
SECURITY variant of safe_canonicalize_bounded — bypasses the #356 TCC
guard so PathJail keeps resolving symlinks to detect jail escapes. See
canonicalize_secure for why a prompt here (explicit request) is legitimate.
Every spelling of the user’s home directory a root guard must refuse: the
profile folder (dirs::home_dir, which on Windows ignores the
environment) and the override-friendly home (HOME/USERPROFILE,
crate::core::home::resolve_home_dir). On Unix both are $HOME and
one entry remains.
Returns true if dir is — or lies inside — a known agent/IDE config dir
(AGENT_CONFIG_DIRS). Separator-agnostic so Windows backslash paths
(C:\Users\me\.copilot) match too; #580 is a Windows Copilot report.
Returns true if the directory is too broad to be a valid project root.
Rejects home directories (including WSL-mounted Windows profiles), filesystem
root, . (bare CWD), agent/IDE config directories (AGENT_CONFIG_DIRS)
and LeanCTX’s own stores (is_lean_ctx_store_dir). Used to prevent
adopting a bogus project root and writing project-scoped data into the
global ~/.lean-ctx/ data directory.
Returns true if project_root collides with the lean-ctx data directory.
This prevents project-scoped files (overlays.json, policies.json) from being
written into ~/.lean-ctx/ or ~/.config/lean-ctx/.
dir is LeanCTX’s data, state, config or cache directory itself. A CLI
started there without a project (the Cockpit’s fallback working directory)
must never adopt it as a project. Folders inside a store stay allowed:
setups and fixtures with LEAN_CTX_DATA_DIR in a temp dir keep their
projects there. Resolved read-only: nothing is created. Both sides are
compared as given and canonical, so another spelling of the same folder
(a symlink, macOS /var → /private/var) matches too.
Returns true if path lies inside (or is) one of the macOS TCC-protected
home folders (~/Documents, ~/Desktop, ~/Downloads). Pure string/path
comparison — performs no filesystem access itself.
Lexical (string-only) part of normalize_tool_path: MSYS2 drive prefix
(Windows hosts only), separators, double slashes, trailing slash. Performs
no filesystem access, so it is safe on persisted paths in
TCC-standalone processes (launchd daemon, #356) and as a dedupe key where
symlink resolution is not worth a realpath per entry.
Returns true when this process is its own TCC identity on macOS — i.e.
it was started (or re-parented) by launchd rather than by a
TCC-granted host like a terminal or an editor.
Canonicalize a path and strip the Windows verbatim/extended-length prefix (\\?\)
that std::fs::canonicalize adds on Windows. This prefix breaks many tools and
string-based path comparisons.
Canonicalize with a timeout guard. Protects against hangs on WSL2 DrvFS,
Windows reparse points, NFS, FUSE, sshfs, and other slow filesystems.
Falls back to the original path if canonicalize doesn’t complete within the timeout.
Self-healing: after a timeout, subsequent calls to slow mounts skip the thread entirely.
Normalize paths from any client format to a consistent OS-native form.
Handles MSYS2/Git Bash drive prefixes on Windows hosts
(/c/Users/... -> C:/Users/...), mixed separators, double slashes, and
trailing slashes. Uses forward slashes for consistency. On non-Windows
hosts /c/... is a literal directory and passes through unchanged (#397).
Whether two recorded project roots name the same project: equal after
separator and trailing-slash normalization, or after resolving symlinks.
The one comparison for project-scoped records (agents, messages, shares):
a raw string comparison split one project into several by spelling.