plugins/claude-code-hermit/skills/hermit-routines/SKILL.md
Manages scheduled routines as per-session CronCreate jobs. Each enabled routine in config.json becomes an idle-gated CronCreate registered at session launch.
npx skillsauth add gtapps/claude-code-hermit hermit-routinesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Register and manage scheduled routines as per-session CronCreate jobs. Each routine fires only when the REPL is idle — no mid-task interruptions. Mirrors the /watch skill pattern.
/claude-code-hermit:hermit-routines load register all enabled config.routines as CronCreates
/claude-code-hermit:hermit-routines list list configured routines from config.json
/claude-code-hermit:hermit-routines status list active CronCreate registrations
/claude-code-hermit:hermit-routines stop [id] stop a specific routine's CronCreate
/claude-code-hermit:hermit-routines stop --all stop all active routine CronCreates
Called automatically by hermit-start.py on always-on launches. Can also be called manually to apply config changes mid-session.
Resolve the plugin root path: run echo $CLAUDE_PLUGIN_ROOT via Bash. Store as pluginRoot. Read config.timezone from .claude-code-hermit/config.json. Store as configTz (may be null — the shift helper treats null as a no-op). This env var is available at skill execution time but NOT inside cron-delivered prompts — it must be baked into each prompt at registration.
Validate pluginRoot before proceeding. If pluginRoot is empty or <pluginRoot>/scripts/log-routine-event.sh does not exist (test -f), abort load immediately — do not run the Step 3 reset or register any CronCreate — and log one line: Routine load aborted: plugin scripts not found at "<pluginRoot>" (CLAUDE_PLUGIN_ROOT unresolved or wrong). No routines registered or reset. This is a whole-load abort, not the per-routine isolation of Step 4: a bad pluginRoot breaks every routine's baked paths, and aborting before Step 3 protects the prior session's CronCreates from being torn down and left unreplaced.
Read config.routines, filter enabled: true. If none, log "No enabled routines in config." and stop.
Call CronList. For each entry whose prompt contains [hermit-routine:: call CronDelete with its ID. Unconditional reset — ensures stale entries from prior sessions are cleared and the 7-day auto-expiry clock is reset.
For each enabled routine, build the prompt string (see templates below), then call CronCreate:
node <pluginRoot>/scripts/cron-tz-shift.js "<routine.schedule>" "<configTz>" via Bash. Use the script's stdout (trimmed) as the cron value. If the script writes a WARN: line to stderr, record it for the Step 5 summary but proceed — the script outputs the original schedule unchanged on unsupported patterns.cron: the shifted schedule (script stdout)recurring: truedurable: falseprompt: the resolved prompt stringPer-routine error isolation: if CronCreate throws for one routine (bad cron expression, hit the 50-task session limit, etc.), record the failure and continue with the next routine. Do not abort the loop — one bad config entry must not prevent unrelated routines from registering.
Log one line summarizing outcomes: Routines registered: <id1>, <id2> (<N> ok, <M> failed[, <K> tz-shifted, <W> tz-warned]). If any failed, list each failed id with the error on its own line. If any warned, list each warned id with the WARN reason on its own line.
Use run_during_waiting (rdw) from the config entry to select the template. Default run_during_waiting is false when the field is absent.
rdw=true — routine fires even when session_state is waiting:
[hermit-routine:<id>] Invoke /<skill>. After it completes, run:
<pluginRoot>/scripts/log-routine-event.sh <id> fired
rdw=false (default) — routine is suppressed when session_state is waiting:
[hermit-routine:<id>] Read .claude-code-hermit/state/runtime.json. If session_state is "waiting", run:
<pluginRoot>/scripts/log-routine-event.sh <id> skipped-waiting
and stop. Otherwise: invoke /<skill>. After it completes, run:
<pluginRoot>/scripts/log-routine-event.sh <id> fired
Replace <pluginRoot> with the resolved absolute path from step 1, <id> with the routine's id, and <skill> with the routine's skill field. The skill string is passed verbatim to the slash invocation (so claude-code-hermit:brief --morning becomes /claude-code-hermit:brief --morning). log-routine-event.sh takes <id> <event> only.
Special case — heartbeat-restart: append Then invoke /claude-code-hermit:hermit-routines load to re-arm all routine CronCreates and reset the 7-day expiry clock. to the prompt (after the trailing fired log line). Daily re-arm via this routine is what keeps routine CronCreates from ever reaching the 7-day auto-expiry in always-on deployments.
reflect_after: true: when a routine config entry has reflect_after: true, append Then, only if the skill actually fired (not skipped-waiting), invoke /claude-code-hermit:reflect --quick. to the prompt (after the trailing fired log line, and after the heartbeat-restart append if both apply). Skip this append when the routine's skill is claude-code-hermit:reflect — chaining reflect after reflect is wasteful and a config foot-gun.
Show configured routines from config.json (not from CronList — this is the config view, not the live view).
config.routines.Routines (config.json):
# ID Schedule Skill RDW RA Status
1. heartbeat-restart 0 4 * * * claude-code-hermit:heartbeat start true false enabled
2. weekly-review 0 23 * * 0 claude-code-hermit:weekly-review false false disabled
RA is true when reflect_after: true is set on the routine entry, false otherwise.
Show active CronCreate registrations for hermit routines.
CronList. Filter entries whose prompt starts with [hermit-routine:./claude-code-hermit:hermit-routines load to register."Active routine CronCreates:
ID CRON-ID SCHEDULE
heartbeat-restart 4e007cf4 0 4 * * *
Extract the routine ID from the [hermit-routine:<id>] prefix in the prompt.
stop <id>:
CronList. Find the entry whose prompt contains [hermit-routine:<id>].CronDelete it. Log: "Stopped routine: <id>."stop (no id):
CronList, filter [hermit-routine:*] entries.--all).stop --all:
CronList. For each [hermit-routine:*] entry: CronDelete.config.timezone. load shifts each routine's cron from config.timezone to the machine's local timezone before registering with CronCreate (which only knows about machine local time). If config.timezone is null, schedules pass through unchanged. The shift uses minute granularity, so half-hour and 45-minute IANA zones (Asia/Kolkata, Australia/Adelaide, Asia/Kathmandu) work correctly.load. The heartbeat-restart daily reload self-corrects across DST transitions within 24h. On the DST transition day itself, one fire may land at the wrong wall-clock hour — the routine fires on the previous day's offset, then load runs and re-registers with the corrected offset. Schedules that cannot be expressed as a single cron after shifting (mixed day-wrap on restricted-DOW, step patterns that lose their structure) pass through unchanged with a WARN: line.hermit-settings routines automatically invokes /claude-code-hermit:hermit-routines load after writing config. If you edit config.json by hand, run /claude-code-hermit:hermit-routines load to apply — no session restart needed.hermit-start.py calls /claude-code-hermit:hermit-routines load only on always-on launches. Operators using /session interactively who want routines must run /claude-code-hermit:hermit-routines load themselves.$CLAUDE_PLUGIN_ROOT is NOT available in cron-delivered prompts. Always resolve and bake the absolute path at load time.durable: false (default). CronCreates die with the session. hermit-start.py re-registers on every always-on launch.heartbeat-restart. load resets the 7-day clock unconditionally on each call. The heartbeat-restart routine fires daily and re-invokes /claude-code-hermit:hermit-routines load, so entries never reach expiry. If you disable heartbeat-restart, routine CronCreates expire after 7 days — re-enable it, or run /claude-code-hermit:hermit-routines load weekly by hand.tools
Presence history & tracker-health report — current home/away state, reliability, recent arrival/departure transitions, and activity patterns for person/device_tracker entities. Use when the operator asks about presence history or when a presence-dependent automation (locks, alarm, vacuum, climate) misbehaves.
development
Evening house brief — end-of-day security check, device status, and energy snapshot. Runs as a daily routine at 22:30 or on demand.
tools
Browse and explain the hermit's Home Assistant automations — list by topic, filter by keyword with plain-language YAML explanations, or sort by last-fired. Read-only. Use when the operator asks "what automations do I have / what does this one do / which haven't fired."
tools
On-demand HA-voice brainstorm — reads entity inventory, automation/script listings, and operator intent to surface at most 2 capability-gap ideas, each gated by proposal-triage before becoming a PROP. Invoke when the operator asks "what automations am I missing?", "any coverage gaps?", or "brainstorm improvements". Never runs autonomously.