Summary
The Claude Code plugin (plugins/claude-code/) cannot run any of its commands (/sleep status, /sleep dry-run, etc.) when installed via Claude Code's /plugin marketplace add. The thin wrapper plugins/claude-code/scripts/sleep.sh looks for a sibling runner at plugins/run-sleep.sh that exists in the repo but is not copied into the Claude Code plugin cache.
Reproduction
Environment: Claude Code 2.1.165 on Linux, SkillOpt repo cloned to ~/Developer/SkillOpt.
# Add the plugin directory as a local marketplace
claude plugin marketplace add /home/<user>/Developer/SkillOpt/plugins/claude-code
# → "Successfully added marketplace: skillopt-sleep (declared in user settings)"
# Install the plugin
claude plugin install skillopt-sleep@skillopt-sleep
# → "Successfully installed plugin: skillopt-sleep@skillopt-sleep (scope: user)"
# Try the status command (or any other /sleep action)
bash /home/<user>/.claude/plugins/cache/skillopt-sleep/skillopt-sleep/0.1.0/scripts/sleep.sh status --project "$(pwd)" --scope invoked
Observed
bash: /home/<user>/.claude/plugins/cache/skillopt-sleep/skillopt-sleep/run-sleep.sh: No such file or directory
The cache only contains the plugins/claude-code/ subtree; the shared plugins/run-sleep.sh (which the thin wrapper calls via BASH_SOURCE[0]/../../run-sleep.sh) is missing.
Expected
Either:
/sleep commands work out-of-the-box after claude plugin install, or
- The install fails fast with a clear error message ("missing shared runner; please also
git clone siblings of the plugin folder") instead of silently producing a non-functional plugin.
Workaround (for anyone hitting this)
Set CLAUDE_PLUGIN_ROOT in your shell env to point at the plugin's directory inside the original clone (not the cache):
export CLAUDE_PLUGIN_ROOT=/home/<user>/Developer/SkillOpt/plugins/claude-code
This makes sleep.sh's fallback SHARED="$(cd "$CLAUDE_PLUGIN_ROOT/.." && pwd)/run-sleep.sh" resolve to /home/<user>/Developer/SkillOpt/plugins/run-sleep.sh.
Root cause
plugins/claude-code/scripts/sleep.sh is a thin wrapper that execs a shared runner at plugins/run-sleep.sh:
# plugins/claude-code/scripts/sleep.sh
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SHARED="$(cd "$HERE/../.." && pwd)/run-sleep.sh" # expects <repo>/plugins/run-sleep.sh
if [ ! -f "$SHARED" ] && [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
SHARED="$(cd "$CLAUDE_PLUGIN_ROOT/.." && pwd)/run-sleep.sh"
fi
exec bash "$SHARED" "$@"
The default path $HERE/../.. resolves to the cache root ~/.claude/plugins/cache/skillopt-sleep/skillopt-sleep/, which Claude Code does not populate with run-sleep.sh. The $SKILLOPT_SLEEP_REPO env var is read inside run-sleep.sh, not in sleep.sh, so it never gets a chance to help.
Suggested fixes (any of these would work)
-
Ship the shared runner inside the plugin folder. Copy plugins/run-sleep.sh to plugins/claude-code/scripts/run-sleep.sh (and the engine package skillopt_sleep/ if not already reachable) and update the relative path in sleep.sh. Simplest fix; no upstream restructuring needed.
-
Add a root-level .claude-plugin/marketplace.json to the SkillOpt repo so Claude Code's marketplace add <github-url> form (which only reads root-level manifests) also works. This would also enable the git-subdir install form documented in the plugin's own marketplace.json.
-
Fail fast. If the shared runner cannot be found, sleep.sh should print a clear error explaining the packaging issue, rather than exec bash of a missing path.
Additional context
- The
plugins/claude-code/.claude-plugin/marketplace.json declares the plugin's source as git-subdir with path: "plugins/claude-code". This declaration would only work for a downstream marketplace consuming SkillOpt; it does not affect the local-path install path used here.
- The README's recommended install command (
/plugin marketplace add ./skillopt-sleep-plugin) points to a path that does not exist in the repo; the actual path is ./plugins/claude-code/. This is a separate, additional doc issue.
- Verified the
status action itself works correctly once the env-var workaround is in place — output:
[sleep] nights so far: 0
[sleep] project: /home/<user>
[sleep] no staged proposals yet.
So the issue is strictly in plugin packaging, not in the engine.
Summary
The Claude Code plugin (
plugins/claude-code/) cannot run any of its commands (/sleep status,/sleep dry-run, etc.) when installed via Claude Code's/plugin marketplace add. The thin wrapperplugins/claude-code/scripts/sleep.shlooks for a sibling runner atplugins/run-sleep.shthat exists in the repo but is not copied into the Claude Code plugin cache.Reproduction
Environment: Claude Code
2.1.165on Linux, SkillOpt repo cloned to~/Developer/SkillOpt.Observed
The cache only contains the
plugins/claude-code/subtree; the sharedplugins/run-sleep.sh(which the thin wrapper calls viaBASH_SOURCE[0]/../../run-sleep.sh) is missing.Expected
Either:
/sleepcommands work out-of-the-box afterclaude plugin install, orgit clonesiblings of the plugin folder") instead of silently producing a non-functional plugin.Workaround (for anyone hitting this)
Set
CLAUDE_PLUGIN_ROOTin your shell env to point at the plugin's directory inside the original clone (not the cache):This makes
sleep.sh's fallbackSHARED="$(cd "$CLAUDE_PLUGIN_ROOT/.." && pwd)/run-sleep.sh"resolve to/home/<user>/Developer/SkillOpt/plugins/run-sleep.sh.Root cause
plugins/claude-code/scripts/sleep.shis a thin wrapper that execs a shared runner atplugins/run-sleep.sh:The default path
$HERE/../..resolves to the cache root~/.claude/plugins/cache/skillopt-sleep/skillopt-sleep/, which Claude Code does not populate withrun-sleep.sh. The$SKILLOPT_SLEEP_REPOenv var is read insiderun-sleep.sh, not insleep.sh, so it never gets a chance to help.Suggested fixes (any of these would work)
Ship the shared runner inside the plugin folder. Copy
plugins/run-sleep.shtoplugins/claude-code/scripts/run-sleep.sh(and the engine packageskillopt_sleep/if not already reachable) and update the relative path insleep.sh. Simplest fix; no upstream restructuring needed.Add a root-level
.claude-plugin/marketplace.jsonto the SkillOpt repo so Claude Code'smarketplace add <github-url>form (which only reads root-level manifests) also works. This would also enable the git-subdir install form documented in the plugin's ownmarketplace.json.Fail fast. If the shared runner cannot be found,
sleep.shshould print a clear error explaining the packaging issue, rather thanexec bashof a missing path.Additional context
plugins/claude-code/.claude-plugin/marketplace.jsondeclares the plugin'ssourceasgit-subdirwithpath: "plugins/claude-code". This declaration would only work for a downstream marketplace consuming SkillOpt; it does not affect the local-path install path used here./plugin marketplace add ./skillopt-sleep-plugin) points to a path that does not exist in the repo; the actual path is./plugins/claude-code/. This is a separate, additional doc issue.statusaction itself works correctly once the env-var workaround is in place — output: