Skip to content

fix(setup): stop baking a version-pinned path into CLAUDE.md - #3

Merged
cakel merged 1 commit into
mainfrom
fix/setup-version-pinned-path
Aug 3, 2026
Merged

fix(setup): stop baking a version-pinned path into CLAUDE.md#3
cakel merged 1 commit into
mainfrom
fix/setup-version-pinned-path

Conversation

@cakel

@cakel cakel commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Finding fivetaku#13 from the full-repo review, and it fired for real during this upgrade.

Problem

setup.sh substituted __PLUGIN_ROOT__ with $CLAUDE_PLUGIN_ROOT, which is .../cache/<marketplace>/<plugin>/<version>. After a plugin upgrade that directory is gone and every path in the injected CLAUDE.md block dangles — and setup never re-runs on its own to repair it.

Observed live: after updating 2.1.12.1.2 the block still read

python3 .../plugins/cache/fablize/fablize/2.1.1/scripts/goals.py
.../2.1.1/packs/investigation-protocol.txt
.../2.1.1/packs/verification-grounding-pack.txt

It only kept working because the old version directory happened to still be on disk. Deleting it would have broken every routing instruction in the block, silently.

The hooks were never affected — hooks.json resolves ${CLAUDE_PLUGIN_ROOT} at load time, so it always points at the running version. Only the injected text was pinned.

Change

  • setup.sh copies scripts/goals.py and packs/*.txt to ~/.fablize/lib/ and injects that stable path. Re-running setup refreshes the copies in place.
  • The version written to progress.json is read from .claude-plugin/plugin.json instead of a string literal that silently disagreed with the installed plugin after every release.
  • gate_prompt.stale_setup_notice() — that version field was written and never read. It now compares against the running plugin's manifest and prepends one line when they differ, pointing at /fablize:setup, which is the only thing that can refresh the copies and the block. Silent when they match, and silent on any error, so it can never block a prompt.

Tests

tests/test_setup_paths.py — 17 checks driving the real setup.sh against a temp HOME and a fake versioned plugin root:

  • no cache/<mkt>/<plugin>/<version> path survives in the injected block
  • the block points at the stable lib dir, and every path it names exists
  • assets are refreshed when setup re-runs after an upgrade (9.9.99.9.10)
  • the block is not duplicated on re-run
  • the notice fires on a version mismatch, and is silent when versions match or progress.json is unreadable

The subprocess call pins encoding="utf-8" because the console codepage here (cp949) cannot decode the script's own output — the default would raise instead of running.

tests/test_setup_paths.py            all 17 checks match
tests/test_gate_classification.py    PASS
tests/test_gate_false_positive.py    PASS
tests/test_gate.py                   PASS
tests/test_gate_robustness.py        PASS
tests/test_recovery.py               PASS
tests/test_shadow.py                 PASS
tests/test_shadow_m3.py              PASS
tests/test_shadow_m4.py              PASS

test_shadow_m4.py's deep→effort wiring guard caught the first draft of the docstring, which used the word "best-effort" in a gate decision file. Reworded — the guard did its job.

Note for existing installs

An install that already has a pinned block needs /fablize:setup re-run once to switch to the stable path. The new notice makes that visible on the next prompt after an upgrade.

Release 2.1.3.

setup.sh substituted __PLUGIN_ROOT__ with $CLAUDE_PLUGIN_ROOT, which is
.../cache/<marketplace>/<plugin>/<version>. After a plugin upgrade that
directory is gone and every path in the injected block dangles, with
nothing to notice or repair it — setup does not re-run on its own.

Observed live: after updating 2.1.1 -> 2.1.2 the block still pointed at
.../fablize/2.1.1/scripts/goals.py and .../2.1.1/packs/*.txt. It only
kept working because the old version directory happened to still be on
disk; removing it would have broken every routing instruction silently.

setup.sh now copies scripts/goals.py and packs/*.txt to ~/.fablize/lib/
and injects that stable path, so the block survives upgrades. The hooks
were never affected — hooks.json resolves ${CLAUDE_PLUGIN_ROOT} at load
time, so it is always the running version.

Also: the version written to progress.json was a string literal that
silently disagreed with the installed plugin after every release. It is
now read from .claude-plugin/plugin.json.

That field was written and never read. gate_prompt.stale_setup_notice()
now compares it against the running plugin's manifest and prepends one
line when they differ, pointing at /fablize:setup — the only thing that
can refresh the copies and the block. Silent when they match, and silent
on any error, so it can never block a prompt.

tests/test_setup_paths.py — 17 checks driving the real setup.sh against a
temp HOME and a fake versioned plugin root: no version in the injected
block, every referenced asset exists, assets refresh on re-run after an
upgrade, the block is not duplicated, and the notice fires only on a
mismatch. The subprocess call pins encoding=utf-8 because the console
codepage here (cp949) cannot decode the script's own output.

test_shadow_m4.py's deep->effort wiring guard caught the first draft of
the docstring, which used the word "best-effort" in a gate decision file.
Reworded.

  tests/test_setup_paths.py            all 17 checks match
  tests/test_gate_classification.py    PASS
  tests/test_gate_false_positive.py    PASS
  tests/test_gate.py                   PASS
  tests/test_gate_robustness.py        PASS
  tests/test_recovery.py               PASS
  tests/test_shadow.py                 PASS
  tests/test_shadow_m3.py              PASS
  tests/test_shadow_m4.py              PASS

Release 2.1.3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cakel
cakel merged commit 2060e57 into main Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant