Conversation
Introduce a `--user` flag across all CLI commands to manage personal skills and MCP servers at `~/.agents/` that follow users across projects. Add `ScopeRoot` abstraction that encapsulates all resolved paths for either project or user scope. Refactor MCP and hook writers to accept path resolver callbacks instead of hardcoded project paths, enabling user-scope MCP configs at agent-specific absolute paths (e.g. `~/.claude.json`, `~/.cursor/mcp.json`). User scope skips gitignore management, hooks, and uses a single `~/.claude/skills/` → `~/.agents/skills/` symlink instead of per-agent project symlinks. Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/eYjE8hoooNGp5Pg6UdkqJAGi-Gkfq6hvhbVo0pwN5t0
There was a problem hiding this comment.
🟠 [5BT-PRY] TOCTOU race condition in symlink migration (src/cli/commands/init.ts:72) (medium confidence)
The migrateDirectory function in symlinks/manager.ts checks if a destination exists with lstat, then renames files. An attacker could exploit the gap between the check and rename to create a symlink at the destination, causing files to be written to an attacker-controlled location.
Identified by Warden via find-bugs
Replace the hardcoded USER_SKILLS_PARENT (~/.claude) with a per-agent userSkillsParentDirs field on AgentDefinition. User-scope symlinks are now agent-specific and correct: - Claude Code: ~/.claude/skills/ - Cursor: ~/.cursor/skills/ - VS Code Copilot: ~/.copilot/skills/ (not ~/.vscode/) - Codex: no symlink needed (reads ~/.agents/skills/ natively) - OpenCode: no symlink needed (reads ~/.agents/skills/ natively) Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/TdmzxZ3w6_kgqAhA6clUBmQ4iMu0_3c3MFx_mRarU10
VS Code Copilot, Codex, and OpenCode all read .agents/skills/ natively at both project and user scope. They don't need symlinks. Make skillsParentDir optional on AgentDefinition — undefined means the agent reads .agents/skills/ directly. Only Claude Code and Cursor still need symlinks (.claude/skills/ and .cursor/skills/). Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/2gT86KQBMq67cxlleIbXDtP24fiqjZ43k4UG890AH7E
There was a problem hiding this comment.
🟠 [YTL-4BZ] Potential path traversal via DOTAGENTS_HOME environment variable (src/agents/types.ts:28) (medium confidence)
The DOTAGENTS_HOME environment variable is used directly without validation in resolveScope(). A malicious environment value could cause the tool to write configs to arbitrary locations. While intended for testing, if exposed in production contexts, this could be exploited.
🟠 [Q9Q-AKF] TOCTOU race condition in symlink handling (src/agents/types.ts:28) (medium confidence)
The ensureSkillsSymlink function has a time-of-check-to-time-of-use (TOCTOU) race condition. Between lstat() checking the path type and the subsequent unlink()/symlink() operations, an attacker with local access could swap the target, potentially causing symlinks to be created pointing to unintended locations.
Identified by Warden via find-bugs
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| /** A skill whose source points to its own install location (adopted orphan). */ | ||
| function isInPlaceSkill(source: string): boolean { | ||
| return source.startsWith("path:.agents/skills/"); | ||
| return source.startsWith("path:.agents/skills/") || source.startsWith("path:skills/"); |
There was a problem hiding this comment.
isInPlaceSkill overly broad match breaks project-scope gitignore
Low Severity
The isInPlaceSkill function now matches path:skills/ sources regardless of scope, but that prefix is only valid for user scope. In project scope, a path:skills/foo source resolves to <project>/skills/foo (copied into .agents/skills/foo), yet isInPlaceSkill returns true, causing the skill to be excluded from the managed gitignore list. The skill ends up tracked by git when it shouldn't be.
Additional Locations (2)
| /** A skill whose source points to its own install location (adopted orphan). */ | ||
| function isInPlaceSkill(source: string): boolean { | ||
| return source.startsWith("path:.agents/skills/"); | ||
| return source.startsWith("path:.agents/skills/") || source.startsWith("path:skills/"); |
There was a problem hiding this comment.
Identical isInPlaceSkill function duplicated across three files
Low Severity
isInPlaceSkill is defined identically in install.ts, sync.ts, and update.ts. This PR extended the condition in all three copies to add the path:skills/ prefix. Having the same logic replicated increases the risk of inconsistent future updates — if the detection heuristic changes, all three sites must be updated in lockstep.


Add a
--userflag to all CLI commands (init,install,add,remove,update,sync,list) enabling personal skills and MCP servers managed at~/.agents/that follow users across projects.The core change introduces a
ScopeRootabstraction (src/scope.ts) that encapsulates resolved paths for either project or user scope. MCP and hook writers are refactored from hardcoded project-relative paths to accept resolver callbacks, enabling user-scope MCP configs written to agent-specific absolute paths (~/.claude.json,~/.cursor/mcp.json, etc.).Key differences in user scope:
~/.agents/skills/with a single~/.claude/skills/symlink~/.agents/is not a git repo)path:skills/prefix instead ofpath:.agents/skills/New files:
src/scope.ts—ScopeRootinterface andresolveScope()src/agents/paths.ts— user-scope MCP target paths,userMcpResolver(),USER_SKILLS_PARENTRefactored:
src/agents/mcp-writer.ts— acceptsMcpTargetResolvercallbacksrc/agents/hook-writer.ts— acceptsHookTargetResolvercallbackprojectRoot: string→scope: ScopeRootsrc/cli/index.ts— extracts--userflag before command dispatch