aboutsummaryrefslogtreecommitdiff
path: root/hooks/ai-wrap-teardown.sh
blob: 6133075a90fc161edb951b9c3e20a8f3858778ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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