fix(harness): split plugin root from consumer project root in all scripts (closes #63) - #66
Merged
Conversation
…ipts (closes #63) Scripts derived one root via '../..' from BASH_SOURCE, which conflates two different roots and breaks every script when run from the plugin install cache (the cache strips the .claude/ prefix, so ../.. escapes the plugin). New shared resolve-roots.sh (sourced by all 10 scripts — one implementation): - script_dir: this script's own dir — where SIBLINGS live in both layouts; all cross-script calls (bot-gh.sh etc.) now use it. - root: consumer project root — repo-tracked layout (<x>/.claude/scripts) wins so worktree gate runs keep anchoring to the worktree, else CLAUDE_PROJECT_DIR, else git toplevel of cwd, else cwd. Never fails (log-event.sh's never-block contract). cockpit.sh: agents ship with the plugin, so the routing panel falls back to the plugin-layout agents/ dir when the project has no .claude/agents; also replaced 2 literal NUL bytes (Map-key separator in embedded JS) with \u0000 so the file is text again and grep-able. smoke-fanout.sh stages resolve-roots.sh beside gate.sh (the smoke caught the missing-sibling case immediately). Verified: self gates green incl. fan-out smoke; cache-layout repro from the issue passes end-to-end (notify-poll reaches bot-gh.sh; gate.sh runs the consumer's gates from cwd=/ via CLAUDE_PROJECT_DIR and via git fallback; cockpit renders with plugin agents; log-event rc=0 writing consumer state). Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01NTZEsfFT4mrZpv8CY67Frd
robercano
approved these changes
Jul 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #63.
Problem
Every script derived one
rootvia../..fromBASH_SOURCE, conflating two different roots. The plugin install cache strips the.claude/prefix, so from the cache../..escapes the plugin and every sibling call ($root/.claude/scripts/bot-gh.sh) and project-asset path broke —pr-loopticks only worked in repos still tracking legacy script copies.Fix — one shared implementation
New
scripts/resolve-roots.sh, sourced by all 10 scripts:script_dir— the script's own dir, where siblings live in BOTH layouts (repo.claude/scripts/and cachescripts/). All cross-script calls now use it.root— consumer project root: repo-tracked layout (<x>/.claude/scripts→<x>) wins so worktree gate runs keep anchoring to the worktree, elseCLAUDE_PROJECT_DIR, else git toplevel of cwd, else cwd. Never fails (log-event's never-block contract).Extras:
cockpit.sh: agents ship with the plugin → routing panel falls back to the plugin-layoutagents/dir; also replaced 2 literal NUL bytes (JS Map-key separator) with\u0000so the file is text again (grep treated it as binary).smoke-fanout.shstagesresolve-roots.shbesidegate.sh— the smoke caught the missing sibling on the very first run.Testing
scripts/+agents/, no.claude/) + fixture consumer repo:notify-poll.shfrom cache reachesbot-gh.sh(fails on missing token, notNo such file)gate.shfrom cache runs the consumer's gates withcwd=/viaCLAUDE_PROJECT_DIR, and via git-toplevel fallback with the var unsetcockpit.shrenders from cache with plugin agents;log-event.shrc=0 writing consumer statecockpit.test.sh+log-event.test.shpass.🤖 Generated with Claude Code