Skip to content

fix(install): stop stripping the Claude Code mod from the marketplace clone; clear OpenCode 2's plugin cache - #1689

Merged
backnotprop merged 3 commits into
mainfrom
fix/installer-mod-and-opencode2-cache
Oct 4, 2026
Merged

backnotprop merged 3 commits into
mainfrom
fix/installer-mod-and-opencode2-cache

Conversation

@backnotprop

Copy link
Copy Markdown
Owner

Two install-script bugs, fixed in install.sh, install.ps1 and install.cmd together.

Bug 1: the installer stripped the Claude Code mod from the marketplace clone

${CLAUDE_CONFIG_DIR:-~/.claude}/plugins/marketplaces/plannotator is Claude Code's own git clone of this repo. Installing or updating the plugin copies apps/hook from it into plugins/cache/plannotator/plannotator/<version>/. On every run, all three installers overwrote the clone's apps/hook/hooks/hooks.json with a hard-coded copy. That copy has no "modules": ["./mod/register.ts"] and no description.

Evidence (Claude Code 2.1.289, temp CLAUDE_CONFIG_DIR, real backnotprop/plannotator marketplace):

  • Clone up to date and dirty: claude plugin marketplace update plannotator printed "Successfully updated" and kept the local edit (M apps/hook/hooks/hooks.json). claude plugin install plannotator@plannotator then copied the stripped file into the plugin cache, so the installed plugin had no mod. This is the usual case, because people run the installer right after a release.
  • Clone behind origin and dirty: 2.1.289 does not pull. It re-clones ("Replacing the existing marketplace clone…"), so the dirty file did not block the update. It does the same when the clone is behind and clean. Older Claude Code versions may have pulled instead, and a pull could be blocked by the local edit. I did not verify that.

History of the block

Why removing the rewrite is safe

  • fix: validate plugin hooks.json on install #230's reason no longer applies. The repo file has the correct timeout. Also, the clone is not what runs: the plugin runs from the cache, and Claude Code replaces the clone on every update.
  • fix: Support Windows markdown paths in CLI annotate flow #267's Windows exe path never stuck either. It only lands in the clone, and the next update re-clone throws it away. The plugin's bare plannotator command resolves once the installer's PATH entry is live (after a terminal restart, which the installer already asks for).
  • The copy keeps drifting. It has diverged from the repo file twice.
  • The duplicate-hook warning at the end of each installer is unchanged. It still keys on the clone's hooks.json existing.

Repair. A clone that an earlier installer rewrote stays dirty until the next upstream commit. So each installer now restores the file only when all of these are true:

  1. git is available, and the clone tracks apps/hook/hooks/hooks.json (git -C <clone> cat-file -e HEAD:<path>).
  2. The file differs from HEAD (git diff --quiet HEAD -- <path> exits 1; any other exit means skip).
  3. With all whitespace removed (and a BOM on Windows), the content matches exactly one of the shapes earlier installers wrote:
    • PermissionRequest only, or EnterPlanMode + PermissionRequest
    • command plannotator, an unquoted …plannotator.exe path, or a \"…plannotator.exe\" path
    • timeouts 5 and 345600, LF or CRLF line endings

When all three hold, the installer runs git -C <clone> checkout -q -- apps/hook/hooks/hooks.json and prints "Restored … from its git checkout". Any other local edit is left alone. install.sh holds the pattern as an ERE, and install.ps1 / install.cmd hold the identical .NET pattern, written with \x22/\x5c/\A/\z so cmd.exe passes it through verbatim. install.cmd runs the check through one powershell -NoProfile call, as it already does for config parsing.

plannotator uninstall assumed nothing about the clone; it removes the plugin through claude plugin uninstall.

Bug 2: OpenCode 2's plugin cache was never cleared

The installers removed ~/.cache/opencode/{node_modules,packages}/@plannotator, which is the OpenCode 1 layout. OpenCode 2 (2.0.22) uses a different layout, read from source:

  • packages/util/src/npm.ts puts a plugin in path.join(global.cache, "npm", key), where key = sanitize(${name}@${spec}). Each install is a numeric generation directory inside it.
  • packages/util/src/global-roots.ts sets cache = $XDG_CACHE_HOME || ~/.cache, then /opencode. This holds on Windows too: it uses os.homedir(), not %LOCALAPPDATA%.

So @plannotator/opencode@latest is cached in the nested directory …/opencode/npm/@plannotator/opencode@latest/<generation>/. Re-running the installer left OpenCode 2 on the old plugin.

What changed:

  • All three installers now build every cache path on ${XDG_CACHE_HOME:-$HOME/.cache}/opencode (Windows: %XDG_CACHE_HOME%, else %USERPROFILE%\.cache).
  • They remove only npm/@plannotator/opencode@*, then the @plannotator scope directory if it is empty. Other @plannotator/* packages and other plugins are kept.
  • --skip-opencode / PLANNOTATOR_SKIP_OPENCODE_INSTALL / skipInstall.opencode still leave the cache untouched.
  • plannotator uninstall now also removes npm/@plannotator/opencode[@*] under both XDG_CACHE_HOME and ~/.cache.
  • tests/devcontainer/README.md shows the OpenCode 2 path.

Tests

bun test scripts/install.test.ts packages/server/uninstall.test.ts: 212 pass, 13 skip (the existing pwsh-only drivers), 0 fail.

New in scripts/install.test.ts:

shellcheck -S warning scripts/install.sh reports the same 8 warnings as main, none in the changed lines. install.ps1 / install.cmd could not be run here (no pwsh). They are covered by the parity and source tests only.

After merge

The served install scripts (plannotator.ai/install.sh, install.ps1, install.cmd) are synced to S3 by hand. Until that sync, users keep getting the old scripts, which still rewrite the clone.

… clone; clear OpenCode 2's plugin cache

Bug 1: install.sh / install.ps1 / install.cmd overwrote
<claude config>/plugins/marketplaces/plannotator/apps/hook/hooks/hooks.json,
Claude Code's own git clone of this repo, with a hard-coded copy that has no
"modules" key, so a plugin installed or updated from that clone loses the
Claude Code mod. The installers no longer write the clone. Instead they
restore the file from the clone's HEAD when it differs from HEAD and its
content (whitespace removed) is exactly one of the shapes earlier installers
wrote; a user's own edit is never touched. Needs git; otherwise a no-op.

Bug 2: OpenCode 2 caches npm plugins at
${XDG_CACHE_HOME:-~/.cache}/opencode/npm/<name>@<spec>/<generation>/, so the
installers left it on the old plugin. All three now root the cache at
XDG_CACHE_HOME (as both OpenCode generations do, Windows included) and remove
npm/@plannotator/opencode@* entries only. --skip-opencode still leaves the
cache alone. plannotator uninstall removes the OpenCode 2 entries too.
…apped XDG_CACHE_HOME drive cannot abort the install
@backnotprop
backnotprop merged commit 99b4837 into main Oct 4, 2026
28 checks passed
backnotprop added a commit that referenced this pull request Oct 4, 2026
#1689 stopped pinning the absolute plannotator.exe path in the plugin hooks,
so the hook runs bare `plannotator` and needs the install directory on PATH.
install.ps1's Show-PathAdvice adds it to the user PATH; install.cmd only
printed advice. :PrintPathAdvice now appends INSTALL_DIR to HKCU\Environment
Path through one PowerShell call: read unexpanded and written back with its
own kind (as plannotator uninstall removes it), skipped when any entry
already names the directory, the directory passed via PLN_PATH_DIR, and a
command with no quote, percent, bang, caret, ampersand or redirect for cmd.exe
to reinterpret. A failure falls back to the old printed setx advice; the
session-only note stays. Source-scan tests plus a pwsh parse check (CI).
backnotprop added a commit that referenced this pull request Oct 4, 2026
…error, deferred pi discovery, Windows PATH (#1692)

* fix(claude-mod): refuse strict-gate and --hook annotate words instead of losing the decision

/plannotator-annotate <file> --require-approval (or --result-file, --hook)
launched a detached CLI whose exit code / result file nothing reads, so the
reviewer's feedback was lost. The mod now refuses such words up front with a
message pointing at the terminal. One list, SCRIPT_ONLY_ANNOTATE_FLAGS, in the
shared tool contract (copied into the mod), is what the shell take-over also
consults.

* fix(cli): an occupied fixed port is one clean startup line, not a stack trace

In remote mode the port is fixed (19432), so a second concurrent session —
more common now that the Claude Code mod's servers outlive the terminal —
died with a raw Bun stack. startBunServerOnAvailablePort now throws a typed
PortInUseError (same message, so Pi's self-preemption pattern and tests are
unchanged), and the CLI wraps its server starts so that error prints one line
naming the port and PLANNOTATOR_PORT and exits through the startup-failure
code (1, or 2 under a strict annotate gate). The port policy is unchanged.
The mod already shows the CLI's stderr as the command's startup error.

* fix(ai): defer pi model discovery so /api/ai/capabilities never waits on spawning pi

Pi's fetchModels spawns `pi` and waits up to 10s; it ran eagerly at startup
and beforeCapabilities awaited it, so every plain capabilities answer (and the
first-run announcement gated on it) could stall up to 10s, even when a session
bridge replaced every SDK provider. Pi now goes through the same deferred
discovery as Codex/OpenCode (?activate= or first session, catalogProviders
unchanged), in both runtimes; a Pi session spawns its own pi, so it does not
block on discovery. The now-empty eager list and beforeCapabilities hook go.

* fix(install.cmd): add the install directory to the user PATH

#1689 stopped pinning the absolute plannotator.exe path in the plugin hooks,
so the hook runs bare `plannotator` and needs the install directory on PATH.
install.ps1's Show-PathAdvice adds it to the user PATH; install.cmd only
printed advice. :PrintPathAdvice now appends INSTALL_DIR to HKCU\Environment
Path through one PowerShell call: read unexpanded and written back with its
own kind (as plannotator uninstall removes it), skipped when any entry
already names the directory, the directory passed via PLN_PATH_DIR, and a
command with no quote, percent, bang, caret, ampersand or redirect for cmd.exe
to reinterpret. A failure falls back to the old printed setx advice; the
session-only note stays. Source-scan tests plus a pwsh parse check (CI).

* test(smoke): activate pi-sdk before expecting its models (discovery is deferred)
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