--- /dev/null
+#!/bin/sh
+# 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.
+
+file=$($GUIX_BIN/sed --regexp-extended --quiet \
+ 's/.*"file_path"\s*:\s*"([^"]*)".*/\1/p')
+[ -z "$file" ] && exit 0
+
+if ! $GUIX_BIN/guix style --whole-file "$file"; then
+ exec 1>&2
+ $GUIX_BIN/echo "❌ guix style failed on $file."
+ exit 2
+fi
+
+if $GUIX_BIN/grep --quiet '^unbalanced$' "$file"; then
+ exec 1>&2
+ $GUIX_BIN/echo "❌ guix style added \`unbalanced\` token to $file for each"
+ $GUIX_BIN/echo "extra closing parenthesis. Remove these tokens AND fix the"
+ $GUIX_BIN/echo "closing-paren count. Edit to re-trigger the check."
+ exit 2
+fi
+
+long_lines=$($GUIX_BIN/grep --line-number --extended-regexp '^.{81,}$' "$file")
+if [ -n "$long_lines" ]; then
+ exec 1>&2
+ $GUIX_BIN/echo "❌ Lines exceed 80 characters in $file:"
+ $GUIX_BIN/echo ""
+ $GUIX_BIN/echo "$long_lines"
+ $GUIX_BIN/echo ""
+ $GUIX_BIN/echo "To avoid feedback loops with guix style, do not re-indent."
+ $GUIX_BIN/echo "Instead use let-bindings or move code into a conf/ module."
+ $GUIX_BIN/echo "Edit to re-trigger formatting."
+ 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
+
+LOAD_PATH="$HOME/.config/guix/current/share/guile/site/3.0"
+LOAD_COMPILED_PATH="$HOME/.config/guix/current/lib/guile/3.0/site-ccache"
+
+out=$(
+ GUIX_PACKAGE_PATH="$CLAUDE_PROJECT_DIR" \
+ GUILE_LOAD_PATH="$LOAD_PATH:$GUILE_LOAD_PATH" \
+ GUILE_LOAD_COMPILED_PATH="$LOAD_COMPILED_PATH:$GUILE_LOAD_COMPILED_PATH" \
+ $GUIX_BIN/guild compile \
+ --optimize=0 \
+ --warn=unbound-variable \
+ --warn=macro-use-before-definition \
+ --warn=use-before-definition \
+ --warn=non-idempotent-definition \
+ --warn=arity-mismatch \
+ --warn=duplicate-case-datum \
+ --warn=bad-case-datum \
+ --warn=format \
+ --load-path="$CLAUDE_PROJECT_DIR" \
+ --output="$tmpdir/out.go" \
+ "$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
+ $GUIX_BIN/echo "❌ Compilation issues in $file:"
+ $GUIX_BIN/echo ""
+ $GUIX_BIN/echo "$out"
+ $GUIX_BIN/echo ""
+ $GUIX_BIN/echo "Fix errors and warnings. Edit the file to re-trigger the"
+ $GUIX_BIN/echo "build check."
+ exit 2
+fi
+
+$GUIX_BIN/echo '{"systemMessage":"✅ Format and build Guile file."}'