Summary
Every /grok-build:* command is a silent no-op when the Claude Code plugin root is reached through a symlink. The bridge script exits 0 and prints nothing, so it looks like the command ran and produced no findings rather than like a failure.
Cause
scripts/grok-bridge.mjs guards its entrypoint by comparing the raw process.argv[1] against the module URL:
const isMain =
process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
path.resolve() normalizes but does not resolve symlinks, while import.meta.url is the realpath Node resolved when loading the module. If any component of the invoking path is a symlink, the two strings differ, isMain is false, main() never runs, and the process exits 0 with no output.
This is not exotic: Claude Code supports multiple config homes (CLAUDE_CONFIG_DIR), and a common setup is to symlink plugins/ between them so one install serves every profile. The slash commands invoke the bridge via $CLAUDE_PLUGIN_ROOT, which then points at the symlinked path.
Reproduction
ln -s ~/.claude ~/.claude-alt
# silent, exit 0, no output
node ~/.claude-alt/plugins/cache/xai-grok-build/grok-build/0.2.1/scripts/grok-bridge.mjs check --json
# works
node ~/.claude/plugins/cache/xai-grok-build/grok-build/0.2.1/scripts/grok-bridge.mjs check --json
Observed on plugin 0.2.1, Node v24.18.0, grok 0.2.118, macOS.
Suggested fix
Resolve symlinks on both sides before comparing, with a fallback for paths that cannot be realpath'd:
const resolveEntrypoint = (candidate) => {
try {
return fs.realpathSync(candidate);
} catch {
return path.resolve(candidate);
}
};
const isMain =
process.argv[1] &&
resolveEntrypoint(process.argv[1]) === resolveEntrypoint(fileURLToPath(import.meta.url));
fs is already imported in the module. Patching this locally makes every command work through the symlinked root.
Separately, it may be worth having an unknown/failed entrypoint state exit non-zero rather than 0, so a future variant of this fails loudly instead of looking like an empty result.
Summary
Every
/grok-build:*command is a silent no-op when the Claude Code plugin root is reached through a symlink. The bridge script exits0and prints nothing, so it looks like the command ran and produced no findings rather than like a failure.Cause
scripts/grok-bridge.mjsguards its entrypoint by comparing the rawprocess.argv[1]against the module URL:path.resolve()normalizes but does not resolve symlinks, whileimport.meta.urlis the realpath Node resolved when loading the module. If any component of the invoking path is a symlink, the two strings differ,isMainis false,main()never runs, and the process exits 0 with no output.This is not exotic: Claude Code supports multiple config homes (
CLAUDE_CONFIG_DIR), and a common setup is to symlinkplugins/between them so one install serves every profile. The slash commands invoke the bridge via$CLAUDE_PLUGIN_ROOT, which then points at the symlinked path.Reproduction
Observed on plugin 0.2.1, Node v24.18.0, grok 0.2.118, macOS.
Suggested fix
Resolve symlinks on both sides before comparing, with a fallback for paths that cannot be realpath'd:
fsis already imported in the module. Patching this locally makes every command work through the symlinked root.Separately, it may be worth having an unknown/failed entrypoint state exit non-zero rather than 0, so a future variant of this fails loudly instead of looking like an empty result.