#!/bin/sh
-# Copyright (c) 2026 Jakub Czajka <jakub@ekhem.eu.org>
-# License: GPL-3.0 or later.
-#
# block-builds - Guard guix/make build/reconfigure.
+if [ -z "$GUIX_BIN" ]; then
+ echo '{"systemMessage":"❌ block-builds: GUIX_BIN not set"}' >&2
+ exit 0
+fi
+if [ ! -x "$GUIX_BIN/sed" ]; then
+ echo '{"hookSpecificOutput":{"permissionDecision":"deny",' \
+ '"permissionDecisionReason":"block-builds: GUIX_BIN/sed missing",' \
+ '"systemMessage":"❌ block-builds broken: GUIX_BIN/sed not found"}}'
+ exit 0
+fi
+if [ ! -x "$GUIX_BIN/echo" ]; then
+ echo '{"hookSpecificOutput":{"permissionDecision":"deny",' \
+ '"permissionDecisionReason":"block-builds: GUIX_BIN/echo missing",' \
+ '"systemMessage":"❌ block-builds broken: GUIX_BIN/echo not found"}}'
+ exit 0
+fi
+if [ ! -x "$GUIX_BIN/grep" ]; then
+ echo '{"hookSpecificOutput":{"permissionDecision":"deny",' \
+ '"permissionDecisionReason":"block-builds: GUIX_BIN/grep missing",' \
+ '"systemMessage":"❌ block-builds broken: GUIX_BIN/grep not found"}}'
+ exit 0
+fi
+
command=$($GUIX_BIN/sed --quiet \
's/.*"command"[[:space:]]*:[[:space:]]*"\([^"]\+\)".*/\1/p')
case "$command" in
*'guix home build'*|*'guix system build'*)
$GUIX_BIN/echo \
- '{"hookSpecificOutput": {' \
- ' "hookEventName": "PreToolUse",' \
- ' "permissionDecision": "ask"' \
- '}}'
- exit 0
- ;;
+ '{"hookSpecificOutput":{"hookEventName":"PreToolUse",' \
+ '"permissionDecision":"ask"}}'
+ exit 0 ;;
*'guix home reconfigure'*|*'guix system reconfigure'*)
if $GUIX_BIN/echo "$PWD" | $GUIX_BIN/grep -q '/worktrees/'
then
$GUIX_BIN/echo \
- '{"hookSpecificOutput": {' \
- ' "hookEventName": "PreToolUse",' \
- ' "permissionDecision": "deny",' \
- ' "systemMessage": "🚫 Reconfigure only on main worktree."' \
- '}}'
+ '{"hookSpecificOutput":{"hookEventName":"PreToolUse",' \
+ '"permissionDecision":"deny",' \
+ '"systemMessage":"🚫 Reconfigure only on main."}}'
exit 0
fi
$GUIX_BIN/echo \
- '{"hookSpecificOutput": {' \
- ' "hookEventName": "PreToolUse",' \
- ' "permissionDecision": "ask"' \
- '}}'
- exit 0
- ;;
+ '{"hookSpecificOutput":{"hookEventName":"PreToolUse",' \
+ '"permissionDecision":"ask"}}'
+ exit 0 ;;
*'make build-'*|*'make test-'*)
$GUIX_BIN/echo \
- '{"hookSpecificOutput": {' \
- ' "hookEventName": "PreToolUse",' \
- ' "permissionDecision": "ask"' \
- '}}'
- exit 0
- ;;
+ '{"hookSpecificOutput":{"hookEventName":"PreToolUse",' \
+ '"permissionDecision":"ask"}}'
+ exit 0 ;;
*'make home-'*|*'make system-'*|*'make vps-'*)
if $GUIX_BIN/echo "$PWD" | $GUIX_BIN/grep -q '/worktrees/'
then
$GUIX_BIN/echo \
- '{"hookSpecificOutput": {' \
- ' "hookEventName": "PreToolUse",' \
- ' "permissionDecision": "deny",' \
- ' "systemMessage": "🚫 Reconfigure only on main worktree."' \
- '}}'
+ '{"hookSpecificOutput":{"hookEventName":"PreToolUse",' \
+ '"permissionDecision":"deny",' \
+ '"systemMessage":"🚫 Reconfigure only on main."}}'
exit 0
fi
$GUIX_BIN/echo \
- '{"hookSpecificOutput": {' \
- ' "hookEventName": "PreToolUse",' \
- ' "permissionDecision": "ask"' \
- '}}'
- exit 0
- ;;
+ '{"hookSpecificOutput":{"hookEventName":"PreToolUse",' \
+ '"permissionDecision":"ask"}}'
+ exit 0 ;;
esac
#
# block-worktrees — Enforce worktree isolation.
#
-# Matches all tool types (* — runs after create-worktree PWD gate).
-# Only active when already inside a worktree.
+# Matches all tool types (*). Only active inside a worktree.
+# Denies writes to main, branch switches, and exit-with-remove.
-if ! echo "$PWD" | sed --quiet '\|/worktrees/|q0;$q1'
+# Pre-flight: critical environment variables.
+if [ -z "$GUIX_BIN" ]; then
+ echo '{"systemMessage":"❌ block-worktrees: GUIX_BIN not set"}' >&2
+ exit 0
+fi
+if [ -z "$CLAUDE_CODE_SESSION_ID" ]; then
+ echo '{"systemMessage":"❌ block-worktrees: CLAUDE_CODE_SESSION_ID not set"}' >&2
+ exit 0
+fi
+
+# Pre-flight: commands referenced by this hook.
+if [ ! -x "$GUIX_BIN/sed" ]; then
+ echo '{"hookSpecificOutput":{"permissionDecision":"deny",' \
+ '"permissionDecisionReason":"block-worktrees: GUIX_BIN/sed missing",' \
+ '"systemMessage":"❌ block-worktrees broken: GUIX_BIN/sed not found"}}'
+ exit 0
+fi
+if [ ! -x "$GUIX_BIN/echo" ]; then
+ echo '{"hookSpecificOutput":{"permissionDecision":"deny",' \
+ '"permissionDecisionReason":"block-worktrees: GUIX_BIN/echo missing",' \
+ '"systemMessage":"❌ block-worktrees broken: GUIX_BIN/echo not found"}}'
+ exit 0
+fi
+GIT_BIN="$HOME/.guix-profile/bin/git"
+if [ ! -x "$GIT_BIN" ]; then
+ echo '{"hookSpecificOutput":{"permissionDecision":"deny",' \
+ '"permissionDecisionReason":"block-worktrees: git missing",' \
+ '"systemMessage":"❌ block-worktrees broken: git not found"}}'
+ exit 0
+fi
+
+# Only active inside a worktree.
+if ! echo "$PWD" \
+ | $GUIX_BIN/sed --quiet '\|/worktrees/|q0;$q1'
then
exit 0
fi
stdin=$(cat)
tool=$(echo "$stdin" \
- | sed -n 's/.*"tool_name"[[:space:]]*:[[:space:]]*"\([^"]\+\)".*/\1/p')
-worktree="$CLAUDE_PROJECT_DIR/.claude/worktrees/$CLAUDE_CODE_SESSION_ID"
+ | $GUIX_BIN/sed -n \
+ 's/.*"tool_name"[[:space:]]*:[[:space:]]*"\([^"]\+\)".*/\1/p')
+proj_root=$($GIT_BIN -C "$PWD" rev-parse --show-toplevel 2>/dev/null)
+[ -z "$proj_root" ] && exit 0
+worktree="$proj_root/.claude/worktrees/$CLAUDE_CODE_SESSION_ID"
die() {
echo \
- '{"hookSpecificOutput":{' \
- '"permissionDecision":"deny",' \
+ '{"hookSpecificOutput":{"permissionDecision":"deny",' \
'"permissionDecisionReason":"'"$1"'"}}'
exit 0
}
ExitWorktree)
action=$(echo "$stdin" \
- | sed -n 's/.*"action"[[:space:]]*:[[:space:]]*"\([^"]\+\)".*/\1/p')
- if [ "$action" = "remove" ]
- then
- die "Worktree deletion is disabled. Use" \
- "ExitWorktree with action: keep instead."
- fi
- ;;
+ | $GUIX_BIN/sed -n \
+ 's/.*"action"[[:space:]]*:[[:space:]]*"\([^"]\+\)".*/\1/p')
+ if [ "$action" = "remove" ]; then
+ die "ExitWorktree with action: remove is disabled." \
+ "Use action: keep instead."
+ fi ;;
Write|Edit)
path=$(echo "$stdin" \
- | sed -n 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]\+\)".*/\1/p')
+ | $GUIX_BIN/sed -n \
+ 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]\+\)".*/\1/p')
case "$path" in
- "$CLAUDE_PROJECT_DIR"*)
- if ! echo "$path" | sed --quiet "\|$worktree|q0;\$q1"
+ "$proj_root"*)
+ if ! echo "$path" \
+ | $GUIX_BIN/sed --quiet "\|$worktree|q0;\$q1"
then
die "Write blocked: target outside worktree."
- fi
- ;;
- esac
- ;;
+ fi ;;
+ esac ;;
Bash)
cmd=$(echo "$stdin" \
- | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"]\+\)".*/\1/p')
-
+ | $GUIX_BIN/sed -n \
+ 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"]\+\)".*/\1/p')
case "$cmd" in
*"git checkout"*|*"git switch"*)
die "Branch switching disabled." ;;
-
*"git worktree remove"*|*"git worktree prune"*)
if ! echo "$cmd" \
- | sed --quiet '/merge-worktree/q0;$q1'
+ | $GUIX_BIN/sed --quiet '/merge-worktree/q0;$q1'
then
die "Git on main blocked. Use /merge-worktree."
fi ;;
-
- *"$CLAUDE_PROJECT_DIR"*|\
- *'$CLAUDE_PROJECT_DIR'*|\
- *'${CLAUDE_PROJECT_DIR}'*)
- if echo "$cmd" | sed --quiet "\|$worktree|q0;\$q1"
+ *"$proj_root"*|*'$proj_root'*|*'${proj_root}'*)
+ if echo "$cmd" \
+ | $GUIX_BIN/sed --quiet "\|$worktree|q0;\$q1"
then : # references own worktree — allowed
elif echo "$cmd" \
- | sed --quiet '/merge-worktree/q0;$q1'
+ | $GUIX_BIN/sed --quiet '/merge-worktree/q0;$q1'
then : # merge-worktree — allowed
- else die "Blocked: reference to main project."
+ else
+ die "Reference to main blocked."
fi ;;
- esac
- ;;
-
+ esac ;;
esac
exit 0
#
# create-worktree — PWD gate: force entry into a worktree.
#
-# Matches all tool types (*). Denies every tool call (except
-# EnterWorktree) unless already in this session's worktree.
+# Matches all tool types (*). Denies every tool call except
+# EnterWorktree unless already in this session's worktree.
+
+# Pre-flight: critical environment variables.
+if [ -z "$GUIX_BIN" ]; then
+ echo '{"systemMessage":"❌ create-worktree: GUIX_BIN not set"}' >&2
+ exit 0
+fi
+if [ -z "$CLAUDE_PROJECT_DIR" ]; then
+ echo '{"systemMessage":"❌ create-worktree: CLAUDE_PROJECT_DIR not set"}' >&2
+ exit 0
+fi
+if [ -z "$CLAUDE_CODE_SESSION_ID" ]; then
+ echo '{"systemMessage":"❌ create-worktree: CLAUDE_CODE_SESSION_ID not set"}' >&2
+ exit 0
+fi
+
+# Pre-flight: commands referenced by this hook.
+if [ ! -x "$GUIX_BIN/sed" ]; then
+ echo '{"hookSpecificOutput":{"permissionDecision":"deny",' \
+ '"permissionDecisionReason":"create-worktree: GUIX_BIN/sed missing",' \
+ '"systemMessage":"❌ create-worktree broken: GUIX_BIN/sed not found"}}'
+ exit 0
+fi
+if [ ! -x "$GUIX_BIN/grep" ]; then
+ echo '{"hookSpecificOutput":{"permissionDecision":"deny",' \
+ '"permissionDecisionReason":"create-worktree: GUIX_BIN/grep missing",' \
+ '"systemMessage":"❌ create-worktree broken: GUIX_BIN/grep not found"}}'
+ exit 0
+fi
stdin=$(cat)
# Exempt EnterWorktree.
if echo "$stdin" \
- | grep -q '"tool_name"[[:space:]]*:[[:space:]]*"EnterWorktree"'
+ | $GUIX_BIN/grep -q '"tool_name"[[:space:]]*:[[:space:]]*"EnterWorktree"'
then
exit 0
fi
# Exempt git push (needed from main project after merge).
if echo "$stdin" \
- | grep -q '"tool_name"[[:space:]]*:[[:space:]]*"Bash"'
+ | $GUIX_BIN/grep -q '"tool_name"[[:space:]]*:[[:space:]]*"Bash"'
then
cmd=$(echo "$stdin" \
- | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
+ | $GUIX_BIN/sed -n \
+ 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
case "$cmd" in
- "git push"*) exit 0 ;;
+ "git push"*|*"/git push"*) exit 0 ;;
esac
fi
# Deny if not in this session's worktree.
-if ! echo "$PWD" | grep -q "/worktrees/${CLAUDE_CODE_SESSION_ID}"
+if ! echo "$PWD" | $GUIX_BIN/grep -q \
+ "/worktrees/${CLAUDE_CODE_SESSION_ID}"
then
echo \
'{"hookSpecificOutput": {' \
exit 0
fi
-exit 0
\ No newline at end of file
+exit 0
# Copyright (c) 2026 Jakub Czajka <jakub@ekhem.eu.org>
# License: GPL-3.0 or later.
#
-# guix-build — Stop hook that drives a build-queue state machine.
+# guix-build — Stop hook: drive the build-queue state machine.
#
# State machine (queue = .claude/.build-queue.<session_id>):
#
# │
# ▼
-# clean tree ────────────────────---─► exit 0
+# clean tree AND at main tip? ───────────► exit 0
# │
# ▼
-# no queue file ─────────────────---─► exit 2
-# │ ask user for targets
-# │ write queue file or touch empty file
+# no queue file ─────────────────────► exit 0
+# │ list available Makefile targets
# ▼
# queue non-empty?
# │
-# ├─ no ─────────────────────────---─► exit 0
+# ├─ no ─────────────────────────────► exit 0
# │ rm queue file
# ▼
# read first target
# │
# ▼
-# target stale? ─────────────────---─► exit 2
+# target stale? ─────────────────────► exit 2
# │ drop it, check next on re-stop
# ▼
# build target
# │
-# ├─ failure ───────────────────---──► exit 2
+# ├─ failure ────────────────────────► exit 2
# │
# └─ success ──► remove from queue ──► exit 2
# build next on re-stop
-MAKEFILE="$CLAUDE_PROJECT_DIR/Makefile"
+# Pre-flight: critical environment variables.
+if [ -z "$GUIX_BIN" ]; then
+ echo '{"systemMessage":"❌ guix-build: GUIX_BIN not set"}' >&2
+ exit 0
+fi
+if [ -z "$CLAUDE_CODE_SESSION_ID" ]; then
+ echo '{"systemMessage":"❌ guix-build: CLAUDE_CODE_SESSION_ID not set"}' >&2
+ exit 0
+fi
-if [ -z "$($HOME/.guix-home/profile/bin/git -C "$CLAUDE_PROJECT_DIR" \
- status --porcelain --untracked-files=no)" ]
-then
- $GUIX_BIN/echo '{"systemMessage":"🟢 Working tree clean."}'
+# Pre-flight: commands referenced by this hook.
+if [ ! -x "$GUIX_BIN/echo" ]; then
+ echo '{"systemMessage":"❌ guix-build broken: GUIX_BIN/echo not found"}' >&2
+ exit 0
+fi
+if [ ! -x "$GUIX_BIN/mkdir" ]; then
+ echo '{"systemMessage":"❌ guix-build broken: GUIX_BIN/mkdir not found"}' >&2
+ exit 0
+fi
+if [ ! -x "$GUIX_BIN/head" ]; then
+ echo '{"systemMessage":"❌ guix-build broken: GUIX_BIN/head not found"}' >&2
+ exit 0
+fi
+if [ ! -x "$GUIX_BIN/grep" ]; then
+ echo '{"systemMessage":"❌ guix-build broken: GUIX_BIN/grep not found"}' >&2
+ exit 0
+fi
+if [ ! -x "$GUIX_BIN/sed" ]; then
+ echo '{"systemMessage":"❌ guix-build broken: GUIX_BIN/sed not found"}' >&2
+ exit 0
+fi
+if [ ! -x "$GUIX_BIN/tr" ]; then
+ echo '{"systemMessage":"❌ guix-build broken: GUIX_BIN/tr not found"}' >&2
+ exit 0
+fi
+GIT_BIN="$HOME/.guix-profile/bin/git"
+if [ ! -x "$GIT_BIN" ]; then
+ echo '{"systemMessage":"❌ guix-build broken: git not found"}' >&2
exit 0
fi
+MAKE_BIN="$HOME/.guix-profile/bin/make"
+if [ ! -x "$MAKE_BIN" ]; then
+ echo '{"systemMessage":"❌ guix-build broken: make not found"}' >&2
+ exit 0
+fi
+
+DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
+M="$DIR/Makefile"
+
+clean_tree=$($GIT_BIN -C "$DIR" status --porcelain)
+ahead=$($GIT_BIN -C "$DIR" rev-list --count main..HEAD 2>/dev/null)
+behind=$($GIT_BIN -C "$DIR" rev-list --count HEAD..main 2>/dev/null)
-$GUIX_BIN/mkdir --parents "$CLAUDE_PROJECT_DIR/.claude"
-QUEUE_FILE="$CLAUDE_PROJECT_DIR/.claude/.build-queue.$CLAUDE_CODE_SESSION_ID"
+if [ -z "$clean_tree" ] && [ "$ahead" = 0 ] && [ "$behind" = 0 ]; then
+ echo "🟢 Working tree clean and on main tip."
+ exit 0
+fi
-# No queue file yet.
-if [ ! -f "$QUEUE_FILE" ]
-then
- "$CLAUDE_PROJECT_DIR/.claude/scripts/ask-build-queue"
+if [ "$behind" != 0 ] && [ "$behind" != "" ]; then
+ echo "⚠️ Branch is $behind commit(s) behind main."
+ if [ "$ahead" != 0 ] && [ "$ahead" != "" ]; then
+ echo "⚠️ Branch has diverged — $ahead commit(s) ahead of main."
+ fi
+ echo "Rebase onto main before building."
exit 2
fi
-# Build the first target if the queue is non-empty.
-if [ -s "$QUEUE_FILE" ]
-then
- target=$($GUIX_BIN/head --lines=1 "$QUEUE_FILE")
- if ! $GUIX_BIN/grep --quiet "^${target}:" "$MAKEFILE"
- then
- $GUIX_BIN/sed --in-place '1d' "$QUEUE_FILE"
+$GUIX_BIN/mkdir -p "$DIR/.claude"
+Q="$DIR/.claude/.build-queue.$CLAUDE_CODE_SESSION_ID"
- exec 1>&2
- $GUIX_BIN/echo "Target $target no longer exists, skipped."
+# No queue file yet — list available targets.
+if [ ! -f "$Q" ]; then
+ b=$($GIT_BIN -C "$DIR" branch --show-current 2>/dev/null \
+ || echo unknown)
+ [ "$b" = "main" ] \
+ && p="guix.*reconfigure" \
+ || p="guix.*build"
+ t=$($GUIX_BIN/sed --regexp-extended --quiet \
+ "/^([a-zA-Z_-][a-zA-Z0-9_-]*):/h;/$p/{g;s/^([a-zA-Z_-][a-zA-Z0-9_-]*):.*/\1/p}" \
+ "$M" | $GUIX_BIN/tr '\n' ' ')
+ echo "Queue empty. Targets: ${t}"
+ exit 0
+fi
+
+# Build the first target if the queue is non-empty.
+if [ -s "$Q" ]; then
+ target=$($GUIX_BIN/head -n1 "$Q")
+ if ! $GUIX_BIN/grep -q "^${target}:" "$M"; then
+ $GUIX_BIN/sed -i '1d' "$Q"
+ echo "Target $target no longer exists, skipped."
exit 2
fi
-
- $GUIX_BIN/make -C "$CLAUDE_PROJECT_DIR" "$target" 1>&2 || {
- exec 1>&2
-
- $GUIX_BIN/echo "❌ Build failed (exit $?) — fix and retry."
+ $MAKE_BIN -C "$DIR" "$target" || {
+ echo "❌ Build failed (exit $?) — fix and retry."
exit 2
}
- $GUIX_BIN/sed --in-place '1d' "$QUEUE_FILE"
-
- exec 1>&2
- $GUIX_BIN/echo "Stop to continue."
+ $GUIX_BIN/sed -i '1d' "$Q"
+ echo "Stop to continue."
exit 2
fi
# Queue is empty — we are finished.
-$GUIX_BIN/echo '{"systemMessage":"✅ Queue finished."}'
-$GUIX_BIN/rm --force "$QUEUE_FILE"
-
+echo "✅ Queue finished."
+$GUIX_BIN/rm -f "$Q"
#!/bin/sh
+# guix-check — Check Guile files after edit.
# Copyright (c) 2026 Jakub Czajka <jakub@ekhem.eu.org>
# License: GPL-3.0 or later.
-#
-# guix-check — Checks to be applied after every edit of a guile file. If a check
-# fails, the script exits with status code 2 to make Claude Code fix the issues.
+
+# Pre-flight: critical environment variables.
+if [ -z "$GUIX_BIN" ]; then
+ echo '{"systemMessage":"❌ guix-check: GUIX_BIN not set"}' >&2
+ exit 0
+fi
+if [ -z "$CLAUDE_PROJECT_DIR" ]; then
+ echo '{"systemMessage":"❌ guix-check: CLAUDE_PROJECT_DIR not set"}' >&2
+ exit 0
+fi
+
+# Pre-flight: commands referenced by this hook.
+for cmd in "$GUIX_BIN/sed" "$GUIX_BIN/grep" "$GUIX_BIN/guix" \
+ "$GUIX_BIN/mktemp" "$GUIX_BIN/rm" "$GUIX_BIN/guild" "$GUIX_BIN/echo"
+do
+ if [ ! -x "$cmd" ]; then
+ echo '{"systemMessage":"❌ guix-check broken: '"$cmd"' not found"}' >&2
+ exit 0
+ fi
+done
file=$($GUIX_BIN/sed --quiet \
's/.*"file_path"\s*:\s*"\([^"]*\)".*/\1/p')
exit 2
fi
-# guild compile needs an output file, but the hook only cares about warnings.
-# Create a disposable directory and schedule its removal.
tmpdir=$($GUIX_BIN/mktemp --directory)
trap '$GUIX_BIN/rm --recursive --force "$tmpdir"' EXIT
"$file" 2>&1
)
-# guild exits non-zero for fatal errors but still exits 0 for warnings so we
-# check both.
if [ $? -ne 0 ] || $GUIX_BIN/echo "$out" | $GUIX_BIN/grep --quiet warning
then
exec 1>&2
# License: GPL-3.0 or later.
#
# notify — Send a desktop notification for Claude Code events.
+#
+# Non-critical: exits silently when commands are missing.
-type=$(sed -n \
- 's/.*"notification_type":"\([^"]*\)".*/\1/p')
-case "$type" in
- permission_prompt)
- body="🤖🔐 Needs Approval"
- ;;
- idle_prompt)
- body="🤖✅ Finished"
- ;;
- *)
+# Pre-flight: critical environment variables.
+if [ -z "$GUIX_BIN" ]; then
+ exit 0
+fi
+
+# Pre-flight: commands referenced by this hook.
+if [ ! -x "$GUIX_BIN/sed" ]; then
exit 0
- ;;
+fi
+
+DUNSTIFY="$HOME/.guix-profile/bin/dunstify"
+if [ ! -x "$DUNSTIFY" ]; then
+ exit 0
+fi
+
+type=$($GUIX_BIN/sed -n \
+ 's/.*"notification_type":"\([^"]*\)".*/\1/p')
+case "$type" in
+ permission_prompt) body="🤖🔐 Needs Approval" ;;
+ idle_prompt) body="🤖✅ Finished" ;;
+ *) exit 0 ;;
esac
-$HOME/.guix-home/profile/bin/dunstify \
- --urgency normal \
- --timeout 10000 \
- --appname "Claude Code" \
- "Claude Code" \
- "$body"
+$DUNSTIFY --urgency normal --timeout 10000 \
+ --appname "Claude Code" "Claude Code" "$body"
# Copyright (c) 2026 Jakub Czajka <jakub@ekhem.eu.org>
# License: GPL-3.0 or later.
#
-# status-line - Display current session/worktree in the Claude Code prompt.
+# status-line — Display the current branch or worktree in the
+# Claude Code status bar.
-if echo "$PWD" | grep -q '/worktrees/'
-then
+# Pre-flight: critical environment variables.
+if [ -z "$GUIX_BIN" ]; then
+ echo "🌿 (GUIX_BIN not set)"
+ exit 0
+fi
+
+# Pre-flight: commands referenced by this hook.
+if [ ! -x "$GUIX_BIN/grep" ]; then
+ echo "🌿 (grep not found)"
+ exit 0
+fi
+
+GIT_BIN="$HOME/.guix-profile/bin/git"
+
+if echo "$PWD" | $GUIX_BIN/grep -q '/worktrees/'; then
echo "🌿 $CLAUDE_CODE_SESSION_ID"
else
- branch=$(git -C "$PWD" branch --show-current 2>/dev/null)
- [ -n "$branch" ] && echo "🌿 $branch"
+ if [ -x "$GIT_BIN" ]; then
+ branch=$($GIT_BIN -C "$PWD" branch --show-current 2>/dev/null)
+ [ -n "$branch" ] && echo "🌿 $branch"
+ fi
fi
{
"env": {
- "CLAUDE_PROJECT_DIR": "$PWD",
"GUIX_BIN": "/run/current-system/profile/bin",
"PROFILE": "nonfree"
},