app
hook
Public Made by Adomby adom
Registry-native cross-agent auto-update hook. Keeps adom/core apps and skills current from wiki.adom.inc on every Claude Code and Codex prompt, in the background.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
#!/usr/bin/env bash
# adom auto-update hook -- registry-native and cross-agent.
#
# Keeps the adom/core apps and skills current from wiki.adom.inc via the
# adom-wiki CLI. No git, no private repo. Runs under BOTH Claude Code and Codex
# `UserPromptSubmit`; its stdout becomes the model's additionalContext. Throttled
# and backgrounded so it never blocks a prompt or interrupts the agent.
#
# The update runs DETACHED so it never blocks a prompt, which means its result is
# only knowable on a LATER run. This script therefore does two jobs each time it
# fires: (1) report the OUTCOME of the previous background update, and (2) start a
# fresh one if we're behind. Job 1 exists because the old version piped the update
# to /dev/null and then unconditionally printed "updating now" -- so a hard
# failure (adom/core depending on a deleted package) was indistinguishable from
# success, and masked a fleet-wide auto-update outage for four days. A failed
# update is now surfaced, loudly, until it succeeds.
#
# Tunables (env):
# ADOM_HOOK_INTERVAL seconds between checks (default 1800 = 30 min)
# ADOM_HOOK_FORCE any non-empty value forces a check this run
# ADOM_HOOK_DISABLE any non-empty value disables the hook entirely
[ -n "${ADOM_HOOK_DISABLE:-}" ] && exit 0
# A UserPromptSubmit hook can fire from a non-login shell whose PATH lacks
# ~/.local/bin (where the adom-wiki CLI is installed). Without this the check
# silently no-ops ("command -v adom-wiki" fails). Put the CLI dir on PATH first.
export PATH="$HOME/.local/bin:$PATH"
ADOM_DIR="${HOME}/.adom"
STAMP="${ADOM_DIR}/last-core-check"
LOG="${ADOM_DIR}/last-update.log" # combined output of the last update
STATUS="${ADOM_DIR}/last-update.status" # 'running' | integer exit code
INTERVAL="${ADOM_HOOK_INTERVAL:-1800}"
RUN_STALE_AFTER=600 # a 'running' marker older than this = crashed mid-update
mkdir -p "$ADOM_DIR" 2>/dev/null
now=$(date +%s 2>/dev/null || echo 0)
# --- 1. Report the outcome of the PREVIOUS background update. -----------------
# Runs before the throttle: a genuinely-broken auto-update is a loud, every-prompt
# condition (that is the whole point -- it used to be silent). Nothing prints when
# the last update succeeded or none has run.
if [ -f "$STATUS" ]; then
st="$(cat "$STATUS" 2>/dev/null)"
if [ "$st" != "running" ] && [ -n "$st" ] && [ "$st" != "0" ]; then
reason="$(tail -n 3 "$LOG" 2>/dev/null | tr '\n' ' ' | cut -c1-300)"
echo "adom: the last background auto-update FAILED (exit $st) -- the fleet may be behind wiki.adom.inc. See why with 'adom-wiki pkg install adom/core'. Recent output: ${reason:-<none>}"
# Fall through and retry below; a transient failure self-heals, a real one
# (a deleted dependency) keeps surfacing here until it is fixed at the source.
fi
fi
# --- 2. Throttle. -------------------------------------------------------------
last=0
[ -f "$STAMP" ] && last=$(stat -c %Y "$STAMP" 2>/dev/null || echo 0)
if [ -z "${ADOM_HOOK_FORCE:-}" ] && [ "$last" -gt 0 ] && [ $(( now - last )) -lt "$INTERVAL" ]; then
exit 0
fi
touch "$STAMP" 2>/dev/null
command -v adom-wiki >/dev/null 2>&1 || exit 0
# --- 3. Don't stack a second updater on top of one still running. -------------
# But treat a 'running' marker older than RUN_STALE_AFTER as crashed (container
# restarted mid-update), so a wedged marker can never block updates forever.
if [ -f "$STATUS" ] && [ "$(cat "$STATUS" 2>/dev/null)" = "running" ]; then
run_age=$(( now - $(stat -c %Y "$STATUS" 2>/dev/null || echo "$now") ))
[ "$run_age" -lt "$RUN_STALE_AFTER" ] && exit 0
fi
# --- 4. Cheap, bounded staleness probe. ---------------------------------------
# `adom-wiki pkg outdated --quiet` exits non-zero when something is behind.
if timeout 12 adom-wiki pkg outdated --quiet >/dev/null 2>&1; then
rm -f "$STATUS" 2>/dev/null # current -> clear any stale failure marker
exit 0
fi
# --- 5. Behind: apply in the background, CAPTURING the result. ----------------
# Output goes to a log and the exit code to a status file so the next run (job 1
# above) can tell success from failure. `pkg update` bumps in-range versions;
# `pkg install adom/core` re-resolves and pulls any newly added core deps. Both
# run regardless (as before), and the status reflects a failure of EITHER, with
# the install failure preferred since installing core is the actual goal.
#
# CONCURRENCY: cron (*/30 + @reboot) and UserPromptSubmit can fire within the same
# second, and the STATUS='running' check has a check-then-write gap. Two updaters
# racing into the same package extraction (which is NOT atomic -- it wipes and
# repopulates the module dir in place) makes one see a half-written tree, e.g.
# `cp: cannot stat 'skills/.../SKILL.md'`. The flock below serializes the hook's
# own updaters: the second to arrive skips instead of stacking. It is held for the
# WHOLE background update (inside the subshell), not just the parent launch.
# (It does NOT cover a MANUAL `adom-wiki pkg install` racing the hook -- that needs
# a CLI-level install lock. See adom/hook #1 follow-up.)
echo running > "$STATUS" 2>/dev/null
(
if command -v flock >/dev/null 2>&1; then
exec 9>"${ADOM_DIR}/update.lock" 2>/dev/null || true
flock -n 9 2>/dev/null || { rm -f "$STATUS" 2>/dev/null; exit 0; }
fi
adom-wiki pkg update > "$LOG" 2>&1
u=$?
adom-wiki pkg install adom/core >> "$LOG" 2>&1
i=$?
rc=0
[ "$u" -ne 0 ] && rc="$u"
[ "$i" -ne 0 ] && rc="$i"
echo "$rc" > "$STATUS" 2>/dev/null
) </dev/null >/dev/null 2>&1 &
disown 2>/dev/null
echo "adom: core skills/apps are behind wiki.adom.inc; an update is running in the background. If it fails, the next check will report it here."
exit 0