diff --git a/docs/runbooks/althing-deploy.md b/docs/runbooks/althing-deploy.md index 53ca13d..05d5fd0 100644 --- a/docs/runbooks/althing-deploy.md +++ b/docs/runbooks/althing-deploy.md @@ -272,6 +272,62 @@ and reports it on every run and on `--check` — and **deliberately never sets it.** That file is the operator's permission configuration; a deploy script that edits it is a deploy script granting itself trust. +## The status-line segment belongs to althing, not to this repo + +From althing **3.4.0** the segment is a console script, `althing-statusline`, +installed on PATH by the same `uv tool install --force` as the other seven +executables. `scripts/claude-statusline-command.sh` calls it and does not +reimplement it: + + althing=$(printf '%s' "$input" | timeout 5 althing-statusline 2>/dev/null) + +⚠ **Keep that outer timeout LOOSER than the program's own 2 s budget.** If the +outer one fires first the segment comes back empty, which renders as "not an +althing directory" — the outage conflation, reintroduced by the guard meant to +prevent a hang. + +**Why a console script rather than the `sync_skill.sh` canonical-file pattern:** +a script, unlike a document, has somewhere to be *installed*. Installing it makes +drift impossible instead of merely visible. + +**Why it needed solving at all.** On 2026-09-02 the same twelve lines existed in +three places — althing's `plugin/scripts/statusline.sh`, the operator's wired +`~/.claude/statusline-command.sh`, and this repo's copy — and one audit found a +**different defect in each**. All three were fixed separately, by hand, the same +day. Two of the defects were in this repo's copy and are worth knowing because +they are both silent: + +- the handle was resolved as the most recent launch in the directory, so a + directory hosting both a claude and a codex seat (`~/development/Worldtree`) + reports the **codex** handle's unread count and reachability to a Claude + session the moment the codex pane relaunches last; +- the post-office address was hardcoded, so it survives exactly until the post + office moves and then reports an outage that is really a stale constant. + +### Deploying it + +`scripts/claude-statusline-command.sh` is the tracked copy of what Claude Code +actually runs at `~/.claude/statusline-command.sh` (`settings.json` → +`statusLine.command`). Copies, not symlinks — same rule as `stacks/`. + + diff -u scripts/claude-statusline-command.sh ~/.claude/statusline-command.sh + cp scripts/claude-statusline-command.sh ~/.claude/statusline-command.sh + +⚠ **Diff before you copy, in both directions.** On 2026-09-02 the live path was +edited directly and was *ahead* of the tracked copy; a reflexive `cp` from the +repo would have destroyed the better version. The tracked copy is intent, the +live path is reality, and reality is sometimes right. + +Smoke test — Claude Code runs it in a **bare shell**, so unset the althing +env vars or you will test your own session's identity instead of the resolution +logic: + + printf '{"workspace":{"current_dir":"/tmp"}}' \ + | env -u ALTHING_HANDLE -u ALTHING_POST_OFFICE bash scripts/claude-statusline-command.sh + +Want: empty althing segment for a directory with no identity, `🔔` in a seat's +own directory, `📵` against an unreachable address. + ## Rollback uv tool install althing-core==3.1.2 diff --git a/scripts/claude-statusline-command.sh b/scripts/claude-statusline-command.sh index f4b30d0..8ad2e40 100755 --- a/scripts/claude-statusline-command.sh +++ b/scripts/claude-statusline-command.sh @@ -1,29 +1,10 @@ #!/usr/bin/env bash -# CANONICAL COPY of the Claude Code statusline. Deployed to (and read from): -# -# ~/.claude/statusline-command.sh <- the LIVE path CC actually runs -# -# Install / update after editing here: -# cp scripts/claude-statusline-command.sh ~/.claude/statusline-command.sh -# -# Copies, not symlinks — same rule as stacks/: this tree is intent, the live -# path is reality, and they diverge until someone deploys. Diff them with -# diff -u scripts/claude-statusline-command.sh ~/.claude/statusline-command.sh -# -# It is version-controlled here because the althing v3 cutover broke it in a way -# that was invisible: the segment gated on `command -v althing-cli`, a binary the -# cutover deleted, so the 📬 badge and 🔔 bell silently vanished for every session -# on the box. With 71 of 73 handles pull-only, that badge is the ONLY out-of-band -# signal telling a session with no armed waiter that it has mail — a dead -# statusline made a working bus look like an empty one. -# -# Smoke test (CC pipes session JSON on stdin): -# echo '{"model":{"display_name":"opus"},"workspace":{"current_dir":"/tmp"},"cwd":"/tmp"}' \ -# | ALTHING_HANDLE= bash scripts/claude-statusline-command.sh -# want: a leading `📬 N`; and with an unreachable post office, degradation in -# ~2s rather than a hang. # Claude Code statusline. Layout: -# [📬N] [🔔/🔕] | ⎇ * ↑ | | ctx: | $ | 5h:% 7d:% +# [📬N] [🔔/🔕/📵] | ⎇ * ↑ | | ctx: | $ | 5h:% 7d:% +# +# 🔔 reachable — the post office will push to this session +# 🔕 pull-only — nothing will poke it; mail waits until it looks +# 📵 the post office could not be asked — an OUTAGE, not an empty inbox # ctx% and rate-limit %s are threshold-colored: green <60, yellow 60-90, red >90. # All segments degrade gracefully (missing tool / non-git dir / no handle => segment omitted). input=$(cat) @@ -126,78 +107,28 @@ if [ -n "$cwd" ]; then fi fi -# --- althing: unread count (📬 N) + reachability (🔔 / 🔕 / 📵 outage) --- -# v3 (the post office, 2026-08-28). ⚠ This block used to gate on -# `command -v althing-cli`, which the v3 cutover DELETED -- so the whole segment, -# badge and bell both, silently disappeared for every session on this box. That is -# worse than a cosmetic loss: 71 of 73 handles are pull-only (no waiter armed, never -# poked), and this badge is the ONLY out-of-band signal telling such a session it has -# mail waiting. A dead statusline made the new bus look like an empty one. +# --- althing: unread count (📬 N) + reachability (🔔 push / 🔕 pull / 📵 outage) --- +# NOT implemented here any more. `althing-statusline` is the canonical segment, +# shipped as a console script by the althing package (3.4.0+). +# +# This block used to be one of THREE hand-maintained copies of the same twelve +# lines -- this file, althing's plugin/scripts/statusline.sh, and infra-ops' +# copy in eshpfi-management. An audit on 2026-09-02 found DIFFERENT defects in +# each and all three were fixed separately, by hand, on the same day. That is a +# drift surface with a countdown on it, and it is the same failure that left +# this very block dead for a month after the v3 cutover deleted the binary it +# gated on. A script, unlike a document, has somewhere to be installed: PATH. +# +# The payload goes in on STDIN -- the segment resolves the handle from the cwd +# it carries, and falls back to nothing rather than guessing. +# +# The outer `timeout` is deliberately LOOSER than the program's own 2s budget. +# If the outer one fired first we would get an empty segment, which reads as +# "not an althing directory" -- the outage conflation, reintroduced by the +# guard meant to prevent a hang. althing=""; mon="" -if command -v postbox >/dev/null 2>&1; then - # postbox has NO default post-office address and the statusline runs in a bare - # shell with neither var set. Hardcoded here deliberately: an unset address makes - # postbox error, which in a must-never-crash segment is indistinguishable from - # "no mail" -- the exact conflation v3 exists to prevent. - export ALTHING_POST_OFFICE="${ALTHING_POST_OFFICE:-http://10.100.50.40:8390}" - h="${ALTHING_HANDLE:-}" - # ALTHING_HANDLE isn't set in the statusline env, so resolve the handle from the - # cwd Claude Code passes on stdin. - # - # ⚠ PREFER launch-history.json. session_handles.json is a **v2 artifact** — v3's - # postbox never opens it (`grep -rn session_handles althing/` is empty; resolve_config - # takes --handle then ALTHING_HANDLE and nothing else), and the tool that used to - # maintain it, `althing-cli use`, was deleted at the cutover. Whatever is in it now is - # hand-kept and drifts silently. - # - # launch-history.json is written by dev_launch, which is the thing that sets - # ALTHING_HANDLE in the first place, so it is the real cwd->handle binding. Shape is - # {cwd: {command: {at, handle}}} with several commands per directory (claude, kimi, - # grok), so take the most recent by `at` rather than whichever key sorts first. - if [ -z "$h" ] && [ -n "$cwd" ]; then - h=$(jq -r --arg d "$cwd" '(.[$d] // {}) | to_entries | max_by(.value.at) | .value.handle // empty' \ - "$HOME/.althing/launch-history.json" 2>/dev/null) - # Fallback only: broader coverage, but frozen and hand-maintained. - [ -z "$h" ] && h=$(jq -r --arg d "$cwd" '.[$d] // empty' "$HOME/.althing/session_handles.json" 2>/dev/null) - fi - if [ -n "$h" ]; then - # `timeout` is load-bearing, not belt-and-braces: v2 read a local SQLite file, - # v3 makes an HTTP call. An unreachable post office must cost this segment two - # seconds and nothing else — a statusline that hangs blocks the whole prompt. - # ONE call, both fields. The status payload already carries `reachable`; - # the previous version fetched it and threw it away. - st=$(timeout 2 postbox --handle "$h" status --json 2>/dev/null /dev/null) - case "${unread:-0}" in ''|0|*[!0-9]*) : ;; *) althing="📬 $unread" ;; esac - - # ⚠ ASK THE POST OFFICE WHETHER THIS SEAT IS REACHABLE. Do not infer it - # from a local artifact. - # - # This bell used to report 🔔 iff `wake-listener-.lock` named a - # live pid — one MECHANISM, not the property. That was right while the - # FIFO waiter was the only channel and became wrong the moment 3.3.0 - # added `cc`: this very seat is reachable over its Claude Code socket, - # has no waiter lock at all, and rendered 🔕 while the post office said - # push/reachable. A pane-routed seat was wrong the same way before that. - # - # Asking the post office means this segment knows nothing about althing's - # internals — no lock paths, no channel names — so adding a fourth channel - # cannot make it stale. The old form also used `kill -0`, which proves a - # pid exists and not which process it is; see the identity-vs-liveness note - # in docs/runbooks/althing-deploy.md. - # - # THREE states, because an outage is not an empty inbox: - # 📵 the post office did not answer (timeout, down, wrong address) - # 🔔 reachable — a poke will arrive - # 🔕 declared but not reachable, or pull-only - if [ -z "$st" ]; then - mon="📵" - elif [ "$(printf '%s' "$st" | jq -r '.reachable // false' 2>/dev/null)" = "true" ]; then - mon="🔔" - else - mon="🔕" - fi - fi +if command -v althing-statusline >/dev/null 2>&1; then + althing=$(printf '%s' "$input" | timeout 5 althing-statusline 2>/dev/null) fi # --- assemble ---