Summary
bb's slash-command typeahead shows no project commands when .claude/commands/*.md are symlinks. The host-daemon command scanner skips every symlinked directory entry, so a repo that symlinks its commands into a shared location gets an empty "project commands" section, while Claude Code's own CLI loads them fine.
Where
host-daemon src/command-discovery.ts, walkMarkdownTree:
for (const entry of entries) {
if (entry.isSymbolicLink()) {
continue;
}
...
}
Symlinks are followed only for user-origin skill roots:
function canFollowSkillSymlink(root: CommandScanRoot): boolean {
return root.origin === "user" && root.source === "skill";
}
walkMarkdownTree backs the command shape (resolveCommandScanRoots -> <cwd>/.claude/commands, origin: "project"), so a project command that is a symlink is never discovered, in any shape.
Reproduce
- In a project,
mkdir -p .agents/skills/foo && printf -- '---\ndescription: demo\n---\nhi\n' > .agents/skills/foo/SKILL.md
ln -s ../../.agents/skills/foo/SKILL.md .claude/commands/foo.md
- Open the slash-command menu in bb (or
GET /api/v1/projects/<id>/commands?provider=claude-code&environmentId=<env>).
Expected: foo listed under project commands.
Actual: absent. Replacing the symlink with a regular file makes it appear immediately, which isolates the cause to the symlink check rather than the path, the environment, or caching.
Impact
Sharing one instruction file between Claude Code and Codex via symlinks is a common monorepo layout (.claude/commands/x.md -> .agents/skills/x/SKILL.md keeps the two harnesses from drifting). In our repo all 14 project commands are invisible in bb for this reason; the only visible non-builtin command is a regular file in ~/.claude/commands/.
Suggested fix
For the command shape, resolve symlinked entries with fs.stat and accept them when the target is a regular file inside the root's boundary. The cycle and root-escape concerns that motivate the blanket skip apply to recursive directory traversal; a symlinked leaf .md can be followed safely with a boundary check. Alternatively, extend canFollowSkillSymlink to project roots for file (non-directory) entries.
Version
bb 0.39.0, macOS (darwin 25.6.0), provider claude-code.
Summary
bb's slash-command typeahead shows no project commands when
.claude/commands/*.mdare symlinks. The host-daemon command scanner skips every symlinked directory entry, so a repo that symlinks its commands into a shared location gets an empty "project commands" section, while Claude Code's own CLI loads them fine.Where
host-daemonsrc/command-discovery.ts,walkMarkdownTree:Symlinks are followed only for user-origin skill roots:
walkMarkdownTreebacks thecommandshape (resolveCommandScanRoots-><cwd>/.claude/commands,origin: "project"), so a project command that is a symlink is never discovered, in any shape.Reproduce
mkdir -p .agents/skills/foo && printf -- '---\ndescription: demo\n---\nhi\n' > .agents/skills/foo/SKILL.mdln -s ../../.agents/skills/foo/SKILL.md .claude/commands/foo.mdGET /api/v1/projects/<id>/commands?provider=claude-code&environmentId=<env>).Expected:
foolisted under project commands.Actual: absent. Replacing the symlink with a regular file makes it appear immediately, which isolates the cause to the symlink check rather than the path, the environment, or caching.
Impact
Sharing one instruction file between Claude Code and Codex via symlinks is a common monorepo layout (
.claude/commands/x.md->.agents/skills/x/SKILL.mdkeeps the two harnesses from drifting). In our repo all 14 project commands are invisible in bb for this reason; the only visible non-builtin command is a regular file in~/.claude/commands/.Suggested fix
For the
commandshape, resolve symlinked entries withfs.statand accept them when the target is a regular file inside the root's boundary. The cycle and root-escape concerns that motivate the blanket skip apply to recursive directory traversal; a symlinked leaf.mdcan be followed safely with a boundary check. Alternatively, extendcanFollowSkillSymlinkto project roots for file (non-directory) entries.Version
bb 0.39.0, macOS (darwin 25.6.0), provider
claude-code.