review-provider.mjs silently no-ops when invoked via a symlinked path (cross-model review inert) #12
Labels
No labels
already-shipped
bug
documentation
duplicate
enhancement
good first issue
help wanted
in progress
invalid
needs-decision
proposal
question
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: crenshawdev/cadence#12
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
cadence-core/bin/review-provider.mjssilently no-ops (exits 0, zero bytes on stdout and stderr) when invoked through a symlinked path — e.g. when the plugin cache dir, or a parent like~/.claude, is a symlink. Because this script is the only place cross-modelreview/consult/detect-modelshappen, the entire cross-model review subsystem silently falls back toclaude-subagentwith no error. A configuredopenai/geminireviewer looks enrolled but is inert.Root cause
The run-as-script guard at the tail of
review-provider.mjs:Node resolves ESM
import.meta.urlthrough realpath, butprocess.argv[1]is left as-typed (path.resolvedoes not follow symlinks). When the script is invoked via a symlinked path — which is what${CLAUDE_PLUGIN_ROOT}expands to when the plugin cache lives under a symlink — the two sides differ:process.argv[1]→/home/user/.claude/plugins/cache/cadence/.../review-provider.mjsfileURLToPath(import.meta.url)→/data/.../.claude/plugins/cache/cadence/.../review-provider.mjsThe guard is false,
main()never runs, and the process exits 0 with no output.emit()is never reached, so there is not even a{ok:false}line — the caller sees empty stdout and degrades toclaude-subagentwithout surfacing anything.Reproduction
Invoking via the realpath prints the expected JSON, confirming the guard as the cause.
Impact
plan/diff/risk_surface/phase_diff/pre_shiptriggers) and consult silently do nothing on any setup where the plugin is reached through a symlink./cad-config --reviewdetection also returns empty, so tier assignment appears to hang / produce nothing.Suggested fix
Compare through realpath (and guard against argv[1] not resolving):
Only
review-provider.mjsis affected among the runtime bins —config.mjsandroute.mjsuseimport.meta.urlforHERE/dirname only and have no argv guard, so they work fine through a symlink.Found on cadence 1.1.0, Node ESM, Linux, with
~/.claudesymlinked to another volume.Fixed in v1.2.0 (REV-01). The run-as-script guard now compares realpaths on both sides, so
review-provider.mjsno longer no-ops when the plugin is installed through a symlink — cross-model review/consult/detect-models reach the real provider instead of degrading silently. A symlink regression test invokes the script through a link and asserts a non-empty JSON line, and an empty/unusable provider result now surfaces one line before the claude-subagent fallback. Shipped in #34, tag v1.2.0.