Apply this to the file you just opened. The mistake that matters most: dirname-walking from the script's own location to find the user's project root. It works only when the plugin happens to be a local checkout — it breaks the moment the plugin runs from ~/.claude/plugins/cache/<plugin>/..., which is the normal case.
This is not a hook script — no JSON envelope on stdin, no JSON response on stdout, no host-managed exit-code semantics. It runs as ordinary bash, invoked by the skill's own markdown via bash "${CLAUDE_PLUGIN_ROOT}/skills/<name>/scripts/<script>.sh" or via !`...` using ${CLAUDE_SKILL_DIR}.
-
No cd "$(dirname "${BASH_SOURCE[0]}")/../.."-style walk to find the project root, the plugin root, or a sibling skill. Flag any dirname/BASH_SOURCE walk that isn't explicitly gated as the standalone-invocation fallback (see below) — replace with the env vars.
-
Project root resolved via the three-tier fallback, not a bare pwd:
PROJECT_DIR="${MYPLUGIN_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}}"
Tier 1: the plugin's own namespaced var (set by its SessionStart hook, survives subshells via lib/source-session-env.sh). Tier 2: ${CLAUDE_PROJECT_DIR}, set by the host in every Bash-tool subprocess. Tier 3: git rev-parse/pwd, for standalone invocation outside Claude Code.
-
This skill's own bundled files referenced via ${CLAUDE_SKILL_DIR} (this skill's subdirectory, not the plugin root); another skill's bundled files via ${CLAUDE_PLUGIN_ROOT}/skills/<other>/.... Never a relative walk to a sibling.
-
Persistent state written to ${CLAUDE_PLUGIN_DATA}/, never ${CLAUDE_PLUGIN_ROOT}/. The plugin install directory is replaced wholesale on update and purged ~7 days after — anything written there is lost.
-
cwd never assumed to be the project root. The script's cwd is whatever the agent last cd-ed to, not guaranteed to be anything in particular. Resolve via the fallback chain in point 2, not pwd alone.
-
Standalone invocation has a defensive fallback, or the skill body states "invoke only via the agent." If a developer might run the script directly outside Claude Code, CLAUDE_* vars are unset — the dirname-walk fallback is the one place it's legitimate, guarded behind an if [ -z "$VAR" ] check.
-
set -euo pipefail present — skill scripts should be as defensive as hook scripts about strict mode.
-
Third-party CLI calls reviewed against shelling-out-from-plugins — env hygiene at every call site.
-
No export FOO=bar expecting a later, separately-invoked Bash-tool call to see it. Bash-tool subprocesses don't inherit each other's env; only SessionStart/Setup/CwdChanged/FileChanged writes to $CLAUDE_ENV_FILE propagate.