diff --git a/docs/runbooks/althing-deploy.md b/docs/runbooks/althing-deploy.md index 79c6306..38a0454 100644 --- a/docs/runbooks/althing-deploy.md +++ b/docs/runbooks/althing-deploy.md @@ -132,6 +132,55 @@ reaped listener is inert and exit 3 only fires against a genuinely live holder. The pid in the file is read by `--stop` alone. (Recorded because infra-ops claimed the opposite, untested, on 2026-09-01; forseti measured it.) +## ⚠ The plugin cache does not take a content-only change + +`claude plugin update althing` matches on the version in `plugin.json` and +declines when it is unchanged. A release that edits hook or script CONTENT +without bumping the plugin version therefore leaves the Claude Code cache +stale while **every version check reports success** — the marketplace dir gets +the new bytes, the cache does not, and `update` says "already at the latest +version". + +Measured on the 3.3.0 deploy (2026-09-02): marketplace and live cache both read +`0.1.1`, and `pane-route.sh` + `README.md` differed. That delta was +documentation-only and harmless. The next one need not be. + +`scripts/deploy-althing.sh` now diffs the marketplace tree against the live +cache dir on every run and on `--check`, so the drift is reported rather than +inferred. **The fix is a plugin version bump upstream, not a hand-edit** — +`~/.claude/plugins/installed_plugins.json` and the cache directory are Claude +Code's own bookkeeping. + +The 2026-09-01 lesson was *compare the hook list, not the version string*. This +is the same lesson one turn deeper: here the hook list was identical too, and +only the file contents moved. + +## ⚠ The `cc` channel is an undocumented interface, taken deliberately + +From 3.3.0 a Claude Code seat is poked over its own message socket +(`$XDG_RUNTIME_DIR/cc-socks/.sock`) rather than by typing into its pane. +No process to reap, nothing near the input line, and delivery lands at the +receiver's next turn boundary. + +It is not a published interface and **is expected to break on some future +Claude Code release**. Accepted on the operator's reasoning: the FIFO poker it +replaces was also an unsanctioned hack of the background-watcher system, so +this is a better instance of a dependency class we already had, not a new one. + +When it breaks the failure mode is a seat going **pull-only with a logged +reason** — not lost mail; the post office holds it either way. Recovery is one +command: + + althing-route declare --pid # prefers cc, falls back to pane + +⚠ **Ordering constraint at any upgrade that changes the channel set:** a +pre-3.3.0 herald refuses `channel=cc` at parse, so a seat that declares the new +channel before the herald restarts goes silently pull-only. `uv tool install +--force` then the herald restart, and only then let anything re-declare. +`deploy-althing.sh` already runs them in that order and updates the plugin +(whose SessionStart hook declares the route) last, which is what keeps the +window to a couple of seconds. + ## Rollback uv tool install althing-core==3.1.2 diff --git a/scripts/deploy-althing.sh b/scripts/deploy-althing.sh index c1f7e90..d43ebad 100755 --- a/scripts/deploy-althing.sh +++ b/scripts/deploy-althing.sh @@ -50,6 +50,39 @@ market_plugin_version() { cache_versions() { ls -1 "$HOME/.claude/plugins/cache/althing/althing/" 2>/dev/null | tr '\n' ' ' || echo "absent" } +# ⚠ COMPARE CONTENT, NOT THE VERSION STRING. +# +# `claude plugin update` matches on the version in plugin.json and declines +# when it is unchanged — so a release that edits hook or script CONTENT without +# bumping the plugin version leaves the Claude Code cache stale while every +# version check in this script reports success. Measured on the 3.3.0 deploy +# (2026-09-02): marketplace and cache both read 0.1.1, update said "already at +# the latest version", and pane-route.sh + README differed. That delta was +# documentation-only and harmless — the next one need not be. +# +# The 2026-09-01 lesson was "compare the hook list, not the version string". +# This is the same lesson one turn deeper: the hook list was identical too. +plugin_content_drift() { + # Only the cache dir matching the marketplace version is live. Older dirs + # carry a .orphaned_at marker and Claude Code no longer loads them; they + # differ by construction and saying so every run is noise. + local live="$HOME/.claude/plugins/cache/althing/althing/$(market_plugin_version)" + local c + for c in "$HOME"/.claude/plugins/cache/althing/althing/*/; do + [[ -d "$c" && -f "$c/.orphaned_at" ]] && say " · cache $(basename "$c") orphaned, ignored" + done + if [[ ! -d "$live" ]]; then + say " ⚠ no cache dir for the marketplace version $(market_plugin_version)" + return 1 + fi + if diff -rq --exclude marketplace.json "$MARKET" "$live" >/dev/null 2>&1; then + say " ✓ live cache $(basename "$live") matches the marketplace tree" + return 0 + fi + say " ⚠ live cache $(basename "$live") DIFFERS from the marketplace tree:" + diff -rq --exclude marketplace.json "$MARKET" "$live" 2>&1 | sed 's/^/ /' + return 1 +} step "state" say " repo $(git -C "$REPO" describe --tags --always) $(git -C "$REPO" diff --quiet && echo clean || echo DIRTY)" @@ -57,6 +90,7 @@ say " installed tool $(uv tool list 2>/dev/null | awk '/^althing-core/{print $ say " repo plugin $(repo_plugin_version)" say " marketplace $(market_plugin_version)" say " CC cache $(cache_versions)" +plugin_content_drift || true if (( CHECK )); then say "" @@ -111,6 +145,15 @@ step "verify" say " installed tool $(uv tool list 2>/dev/null | awk '/^althing-core/{print $2}')" say " marketplace $(market_plugin_version)" say " CC cache $(cache_versions)" +if ! plugin_content_drift; then + say "" + say " ⚠ The cache did NOT take the new plugin content. This happens when the" + say " release changed hook/script content without bumping the plugin version" + say " — `claude plugin update` matches on version and declines. Ask the" + say " althing maintainer for a version bump; do NOT hand-edit the cache or" + say " ~/.claude/plugins/installed_plugins.json, which is Claude Code's own" + say " bookkeeping. Check the diff above for whether it is load-bearing." +fi say " herald $(systemctl --user is-active althing-po-herald)" say "" say "⚠ RESTART Claude Code to load the new plugin hooks. A running session keeps the old ones."