diff options
Diffstat (limited to 'hooks')
| -rwxr-xr-x | hooks/ai-wrap-teardown.sh | 69 | ||||
| -rw-r--r-- | hooks/settings-snippet.json | 7 |
2 files changed, 76 insertions, 0 deletions
diff --git a/hooks/ai-wrap-teardown.sh b/hooks/ai-wrap-teardown.sh new file mode 100755 index 0000000..6133075 --- /dev/null +++ b/hooks/ai-wrap-teardown.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Stop hook: tear down the ai-term session (or power off) AFTER a wrap-up. +# +# wrap-it-up.org drops a sentinel only at the very end of a teardown- or +# shutdown-mode wrap, once commit+push is verified and the valediction is +# delivered. This hook fires when Claude stops responding; on every NORMAL +# stop there is no sentinel, so it is a silent no-op. Only after a wrap that +# requested teardown does the matching sentinel exist, and only then does this +# hook act — which is what keeps a routine end-of-turn from killing the +# session. +# +# Decoupling via the Stop hook (rather than an inline workflow step) is what +# lets the valediction flush before the session dies: teardown kills the very +# tmux session Claude runs in, so it cannot happen inline. +# +# Two sentinels, keyed by the project basename (one ai-term session per +# project, named aiv-<basename>): +# /tmp/ai-wrap-teardown-<basename> -> cj/ai-term-quit "<basename>" +# kill the aiv-<basename> tmux session (takes claude with it), kill the +# vterm buffer, restore geometry. Defined in .emacs.d/modules/ai-term.el. +# /tmp/ai-wrap-shutdown-<basename> -> cj/ai-term-shutdown-countdown +# abort-able 10->1 echo-area countdown, then sudo shutdown now. Shutdown +# supersedes teardown (killing the buffer is moot if powering off). +# +# The sentinel is removed BEFORE the emacsclient call: cj/ai-term-quit kills +# the tmux session this hook runs inside, so the hook process may not survive +# the call. Clearing first guarantees the sentinel never lingers to re-fire on +# a later session in the same project. The emacs daemon is a separate process, +# so it completes the teardown even when the hook is cut off mid-call. +# +# emacsclient absent or daemon unreachable: clear the sentinel and exit 0. The +# session simply stays up — graceful degradation, never a wedge. +# +# Wire in ~/.claude/settings.json (see hooks/settings-snippet.json): +# +# "Stop": [ +# { "hooks": [ +# { "type": "command", +# "command": "~/.claude/hooks/ai-wrap-teardown.sh" } ] } ] +set -u + +# Stop-hook stdin JSON carries cwd; basename it to the project / aiv- session. +cwd="$(jq -r '.cwd // empty' 2>/dev/null)" +[ -z "$cwd" ] && cwd="$PWD" +proj="$(basename "$cwd")" + +teardown_sentinel="/tmp/ai-wrap-teardown-${proj}" +shutdown_sentinel="/tmp/ai-wrap-shutdown-${proj}" + +fire() { + # $1 = elisp form. Best-effort: only when emacsclient resolves. + command -v emacsclient >/dev/null 2>&1 || return 0 + emacsclient -e "$1" >/dev/null 2>&1 || true +} + +# Shutdown supersedes teardown when both are somehow present. +if [ -f "$shutdown_sentinel" ]; then + rm -f "$shutdown_sentinel" "$teardown_sentinel" + fire '(cj/ai-term-shutdown-countdown)' + exit 0 +fi + +if [ -f "$teardown_sentinel" ]; then + rm -f "$teardown_sentinel" + fire "(cj/ai-term-quit \"${proj}\")" + exit 0 +fi + +exit 0 diff --git a/hooks/settings-snippet.json b/hooks/settings-snippet.json index a5f9d9c..0f0e784 100644 --- a/hooks/settings-snippet.json +++ b/hooks/settings-snippet.json @@ -30,6 +30,13 @@ { "type": "command", "command": "~/.claude/hooks/destructive-bash-confirm.py" } ] } + ], + "Stop": [ + { + "hooks": [ + { "type": "command", "command": "~/.claude/hooks/ai-wrap-teardown.sh" } + ] + } ] } } |
