diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-27 13:32:34 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-27 13:32:34 -0500 |
| commit | 2c664cb5651fbf03bc52d7848add0c571708adcd (patch) | |
| tree | 95d44306c84197affa6f98b645d623d2ca74cfce /hooks/session-start-disarm.sh | |
| parent | 79ed3b09a9ee2a63fb55d2354aa0c77ea24c6efa (diff) | |
| download | rulesets-2c664cb5651fbf03bc52d7848add0c571708adcd.tar.gz rulesets-2c664cb5651fbf03bc52d7848add0c571708adcd.zip | |
fix(hooks): don't let a wrap sentinel outlive its session
wrap-it-up drops /tmp/ai-wrap-teardown-<project> to ask the Stop hook to kill the tmux session once the wrap certifies clean. When certification fails the Stop hook blocks and leaves the sentinel armed, which I did on purpose so a wrap blocked by a dirty tree can retry on a later stop without re-running the workflow.
I never bounded that retry to the session. An uncertified sentinel sat armed indefinitely and fired in whatever session next reached a clean tree. work's 11:37 wrap today left one armed. The 13:20 session ran startup, committed the task filing and the template sync, went clean, and the next stop consumed the two-hour-old sentinel and killed the terminal mid-work. Every stop before those commits had been blocked by the same sentinel failing certification, so the session spent its whole life either blocked or dead. archsetup's had been armed since Saturday on a live attached terminal, and home's was armed and waiting.
session-start-disarm.sh clears the project's sentinels at SessionStart. A new session means the wrap that armed one is gone, so its pending teardown is meaningless. Within-session retry is untouched, since the hook only runs at session start, and a test pins that. If teardown is still wanted, wrap-it-up re-arms it.
I disarmed the three live ones by hand before writing this, backed up under /tmp/disarmed-sentinels.
Four tests cover the disarm, one pins the retry behavior I did not want to lose. The scoping test matters most: a concurrent session in another project keeps its own sentinel.
Diffstat (limited to 'hooks/session-start-disarm.sh')
| -rwxr-xr-x | hooks/session-start-disarm.sh | 42 |
1 files changed, 42 insertions, 0 deletions
diff --git a/hooks/session-start-disarm.sh b/hooks/session-start-disarm.sh new file mode 100755 index 0000000..520d8c0 --- /dev/null +++ b/hooks/session-start-disarm.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# +# SessionStart: disarm any wrap sentinel left over from a previous session. +# +# wrap-it-up drops /tmp/ai-wrap-teardown-<project> (or -shutdown-) to ask the +# Stop hook to kill the tmux session once the wrap certifies clean. The Stop +# hook deliberately PRESERVES that sentinel when certification fails, so a wrap +# blocked by a dirty tree can retry on a later stop in the same session without +# the user re-running the workflow. +# +# Nothing bounded that retry to the session. A sentinel armed by a wrap that +# never certified survived indefinitely and fired in whatever session next +# happened to reach a clean tree: +# +# work, 2026-07-27. The 11:37 wrap requested teardown, failed certification +# on a dirty tree, and left the sentinel armed. A fresh session started at +# 13:20, committed twice during startup, went clean — and the next stop +# consumed the two-hour-old sentinel and killed the terminal mid-work. +# archsetup's had been armed for two days on a live attached session. +# +# A new session means the wrap that armed the sentinel is gone, so its pending +# teardown is meaningless: clear it. Within-session retry is untouched, because +# this only runs at session start. If the user still wants teardown, wrap-it-up +# re-arms it. +# +# Scoped to the current project's sentinels only — a concurrent session in +# another project keeps its own. +# +# Silent and exit 0 always. A SessionStart hook must never block a session from +# starting, and there is nothing here a user needs told. + +set -u + +payload="$(cat 2>/dev/null || true)" + +cwd="$(printf '%s' "$payload" | jq -r '.cwd // empty' 2>/dev/null)" +[ -z "$cwd" ] && cwd="$PWD" +proj="$(basename "$cwd")" + +rm -f "/tmp/ai-wrap-teardown-${proj}" "/tmp/ai-wrap-shutdown-${proj}" + +exit 0 |
