feat(althing): four-surface deploy script + runbook
Deploying althing touches four independent surfaces on nh3-dev. Three were known. The fourth -- the plugin -- had no step in any runbook and drifted for five days before anyone noticed. The plugin chain is repo plugin/ to the marketplace directory to Claude Code's cache, and neither hop was automated. The marketplace directory was a frozen copy from Aug 28 carrying only the UserPromptSubmit hook, with no SessionStart, no SessionEnd and no pane-route.sh at all. So the claim that CC seats re-declare their pane route automatically at session start was never true on this box, which is why every seat had to be hand-declared with a pid measured by hand. The script backs up the marketplace directory before syncing, re-stamps its marketplace.json from the repo's plugin.json, and uses `claude plugin update` for the cache rather than hand-editing installed_plugins.json -- that is Claude Code's own bookkeeping and a subtle mistake there breaks the plugin in a way that looks like an upstream bug. The runbook also carries the two things most likely to waste someone's afternoon: `uv tool install .` without --force is a silent no-op that exits 0 having done nothing, and a live waiter reporting mode:pull is a seat that will never be poked, with the audit loop for finding them.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
# althing deploy — four surfaces
|
||||
|
||||
**`scripts/deploy-althing.sh`** does all four. `--check` reports drift without
|
||||
touching anything. This page is the why.
|
||||
|
||||
Deploying althing means updating **four independent surfaces on nh3-dev**. Three
|
||||
were known; the fourth had no step in any runbook and drifted for five days
|
||||
before anyone noticed, on 2026-09-01.
|
||||
|
||||
| # | surface | what it is | how it drifts |
|
||||
|---|---|---|---|
|
||||
| 1 | **binaries** | `uv tool install --force .` — the 7 console scripts | silently, see below |
|
||||
| 2 | **herald** | `systemctl --user restart althing-po-herald` | new guards live here; nothing takes effect until it restarts |
|
||||
| 3 | **skill** | `scripts/sync_skill.sh` → `~/.agents/skills/althing/SKILL.md` | covered by its own `--check` |
|
||||
| 4 | **plugin** | repo `plugin/` → marketplace dir → Claude Code cache | **two hops, neither was automated** |
|
||||
|
||||
## ⚠ `uv tool install .` without `--force` is a silent no-op
|
||||
|
||||
$ uv tool install .
|
||||
`althing-core @ file:///home/lkraven/development/althing` is already installed
|
||||
$ echo $?
|
||||
0
|
||||
|
||||
uv matches on the source **spec**, not its **contents**. On a box that already
|
||||
installed from that path it declines and reports success. An operator following
|
||||
a runbook literally would restart the herald, see everything green, and wonder
|
||||
why the new binary was missing — with every command exiting 0.
|
||||
|
||||
## ⚠ Surface 4 is the one that bites, and it ate a hook
|
||||
|
||||
The chain is:
|
||||
|
||||
repo plugin/ → ~/.local/share/althing-plugin/ → ~/.claude/plugins/cache/althing/althing/<ver>/
|
||||
^^^ nothing synced this hop
|
||||
|
||||
On 2026-09-01 the marketplace directory was a frozen copy from **Aug 28**:
|
||||
|
||||
deployed 0.0.1 hooks: ['UserPromptSubmit']
|
||||
repo 0.1.1 hooks: ['UserPromptSubmit', 'SessionStart', 'SessionEnd']
|
||||
+ scripts/pane-route.sh (absent from the deployment entirely)
|
||||
|
||||
So **"CC seats re-declare their pane route automatically at next SessionStart"
|
||||
was never true on this box.** The hook existed and worked upstream; it was never
|
||||
deployed. That is why every seat — including infra-ops' own Claude Code seat —
|
||||
had to be hand-declared with a pid someone measured by hand, and why the
|
||||
`idle_cursor` pin from 3.2.1 would not have self-applied either.
|
||||
|
||||
**Use the supported CLI for the second hop.** `claude plugin update althing`
|
||||
(also `install` / `uninstall` / `list` / `details` / `validate` / `marketplace`).
|
||||
⚠ Do **not** hand-edit `~/.claude/plugins/installed_plugins.json` or fabricate a
|
||||
cache directory — that is Claude Code's own bookkeeping, and a subtle mistake
|
||||
there breaks the plugin in a way that looks like an upstream bug.
|
||||
|
||||
⚠ **A Claude Code restart is required** for new plugin hooks. They are read at
|
||||
session start; a running session keeps the old ones.
|
||||
|
||||
## Verifying a seat is actually reachable
|
||||
|
||||
`postbox status` reports `mode: push`, but until 3.2.4 a **failed** push
|
||||
declaration was silent — `_declare_push` devnulled both streams with
|
||||
`check=False`, so a missing binary, a non-zero exit and a server-side rejection
|
||||
rendered identically as nothing. A seat could have been silently pull-only since
|
||||
3.1.2.
|
||||
|
||||
Audit for "looks armed but is not", cross-referencing local waiter locks against
|
||||
what the post office believes:
|
||||
|
||||
```bash
|
||||
for f in ~/.althing/wake-listener-*.lock; do
|
||||
h=$(basename "$f" .lock); h=${h#wake-listener-}
|
||||
pid=$(cat "$f" 2>/dev/null)
|
||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
printf " %-24s waiter %-8s mode: %s\n" "$h" "$pid" \
|
||||
"$(postbox status --handle "$h" 2>/dev/null | grep -oP 'mode: \K\w+')"
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
A live waiter reporting `mode: pull` is a seat that will never be poked. From
|
||||
3.2.4 onward `$ALTHING_ROOT/listen.log` records failed declarations directly.
|
||||
|
||||
⚠ **A stale `wake-listener-*.lock` is NOT a fault.** The gate is `flock -n` on an
|
||||
open fd, which the kernel releases when the holder dies, so a lock file left by a
|
||||
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.)
|
||||
|
||||
## Rollback
|
||||
|
||||
uv tool install althing-core==3.1.2
|
||||
|
||||
Routes written by later versions stay parseable — the old reader ignores unknown
|
||||
keys — so nothing is stranded.
|
||||
Reference in New Issue
Block a user