Skip to content

scripts break when run from the plugin install cache (CLAUDE_PLUGIN_ROOT): root derived as ../.. conflates plugin root with consumer project root #63

Description

@robercano-ghbot

Bug report: orchestrator scripts break when run from the plugin cache (CLAUDE_PLUGIN_ROOT)

Repo: robercano/ai-project-orchestrator · Plugin: orchestrator@ai-project-orchestrator v0.1.1
Found in: consumer repo robercano/reDeploy, 2026-07-07, while running a /pr-loop tick.

Symptom

The pr-loop tick prompt invokes helper scripts as:

bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/notify-poll.sh

With the plugin installed, CLAUDE_PLUGIN_ROOT resolves to the install cache:

~/.claude/plugins/cache/ai-project-orchestrator/orchestrator/0.1.1/

and the script immediately fails:

bash: /home/rcano/.claude/plugins/cache/ai-project-orchestrator/orchestrator/.claude/scripts/bot-gh.sh:
No such file or directory

Every script that shells out to bot-gh.sh is affected the same way. The tick only worked because
reDeploy still tracks legacy copies under .claude/scripts/ (the :-.claude fallback) — which defeats
the point of moving the scripts into the plugin.

Root cause — one root variable serving two different roots

All scripts assume they live at <repo-root>/.claude/scripts/<script>.sh and derive:

root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"   # scripts/*.sh, e.g. notify-poll.sh:14
gh() { bash "$root/.claude/scripts/bot-gh.sh" "$@"; }

That holds in the marketplace/source checkout (<repo>/.claude/scripts/…), but the install cache strips
the .claude/ prefix
— the plugin root contains scripts/, agents/, commands/, … directly. So from
the cache, ../.. lands on …/orchestrator/ (the version dir's parent), and $root/.claude/scripts/…
doesn't exist.

Affected root= derivations (all in v0.1.1 cache): bot-gh.sh:25, merge-ready.sh:29,
notify-poll.sh:14, pr-feedback.sh:18, gate.sh:12, prepare-pr.sh:47, seed-issues.sh:32,
worktree.sh:23, cockpit.sh:33.

There are really two distinct roots being conflated:

  1. Plugin root — where sibling scripts live (bot-gh.sh). Derivable from BASH_SOURCE in both
    layouts as simply the script's own dir.
  2. Consumer project root — where .env (GH_BOT_TOKEN), .claude/gates.json, the notify-poll
    cursor state, and the git repo live. From the cache this is not derivable from script location at
    all
    — even fixing (1) leaves bot-gh.sh looking for .env at $root/.env inside the cache.

Suggested fix

Split the two concerns in every script:

# siblings: same dir in both layouts (repo .claude/scripts/ AND cache scripts/)
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
gh() { bash "$script_dir/bot-gh.sh" "$@"; }

# consumer project: env var first, then git, then cwd
root="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
  • Sibling-script references ($root/.claude/scripts/X.sh) → $script_dir/X.sh.
  • Project assets (.env, gates.json, cursor/state files, cd "$root") → the new root.
  • gate.sh is a special case: it's repo-tracked in consumers (scaffolded/managed), so its ../..
    derivation is fine — but if it's ever invoked from the cache it has the same issue.
  • The command prompts (commands/pr-loop.md, pr-loop-self.md) can keep
    ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/… once the scripts themselves are layout-agnostic.

Repro

cd <any consumer repo with .env + gates.json>
CLAUDE_PLUGIN_ROOT=~/.claude/plugins/cache/ai-project-orchestrator/orchestrator/0.1.1 \
  bash "$CLAUDE_PLUGIN_ROOT/scripts/notify-poll.sh"
# -> No such file or directory: …/orchestrator/.claude/scripts/bot-gh.sh

Works from the marketplace checkout layout (<repo>/.claude/scripts/notify-poll.sh) because ../..
happens to be the repo root there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingmodule:harnessOrchestrator machinery under .claude

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions