plugins/claude-code-hermit/skills/session-close/SKILL.md
Closes the current work session with a structured handoff. Archives the session report and prepares for the next session. Activates on messages like "I'm done", "wrap it up", "that's it for now", "done for today", "close the session". Also runs the midnight `--scheduled` decision (close now, queue, or noop) fired by the `daily-auto-close` routine.
npx skillsauth add gtapps/claude-code-hermit session-closeInstall 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.
/session-close is always a Full Shutdown. The operator explicitly invoked it — that's the confirmation. No close mode decision, no prompting.
When invoked with --auto by heartbeat (either after 12h SHELL.md inactivity, or via the daily-auto-close pending-flag drain after a 10-min lull), the operator did not invoke it. The auto-close path bypasses summary-gathering, skips reflect (step 5), skips the heartbeat-stop step (step below), and stamps closed_via: auto in the archive frontmatter via the session-archive.ts payload; session-archive.ts itself clears state/pending-close.json and writes the context-reset marker after a successful archive.
Idle transitions happen automatically at task boundaries (handled by the session skill). By the time the operator runs /session-close, they want out.
If heartbeat is running, stop it before archiving. Skip on --auto — heartbeat is the caller; stopping its Monitor would prevent all future ticks.
If watches are registered (state/monitors.runtime.json has entries), stop all watches before archiving — invoke /claude-code-hermit:watch stop --all.
scripts/session-archive.ts handles updating both SHELL.md (cosmetic) and state/runtime.json (lifecycle truth) during archiving. For full shutdown, it sets shutdown_completed_at in runtime.json — but only if shutdown_requested_at is already non-null (hermit-stop.ts's signal), so an unattended auto-close reusing this same "Full Shutdown" framing never falsely marks the always-on process as stopping.
Use this when the operator wants to end everything (via hermit-stop or explicit --shutdown).
A pre-existing shutdown_requested_at is the caller's stamp — hermit-stop sets it and then sends this command (hermit-stop.ts). It is never evidence of a competing close; proceed with the close. Do not send a second --shutdown after one has completed: each archive run creates a new report (session-archive is not idempotent).
--auto)When invoked with --auto by heartbeat, skip steps 1–5 and jump directly to step 6 (shutdown_skill), step 7 (Tasks cleanup), and step 8 (session-archive.ts archive — the script itself performs the step 9/10 marker bookkeeping on success). Pipe this templated payload on stdin to session-archive.ts archive --mode=auto:
Status: completed
Blockers: none
Lessons: none
Changed: <from session-diff.json if available, else none>
Artifacts: none
Closed Via: auto
Next Start Point: Fresh start.
Write Auto-closed by heartbeat. as the first line of ## Overview in the session report.
If step 8 returns ok === false, no markers were written and pending-close.json is left in place automatically, so the next heartbeat tick retries the drain.
--scheduled)Invoked by the daily-auto-close routine at 0 0 * * * (local) — the midnight decision layer that decides whether to close now, queue, or do nothing. The routine prompt is prefixed [hermit-routine:daily-auto-close] so scripts/record-operator-action.ts does not bump state/last-operator-action.json (load-bearing: the decision verb reads that clock to decide whether to close now or queue).
bun ${CLAUDE_PLUGIN_ROOT}/scripts/session-archive.ts auto-close-decision --state-dir=.claude-code-hermit
The verb owns the whole branch table: it reads session_state and the operator-action clock, deletes a stale pending-close.json itself when there is nothing to close, writes a fresh queue flag itself when the operator is active, and fails open to close-now when the clock is missing or invalid (operator idle indefinitely). A corrupt or unreadable runtime.json maps to noop, not close-now — closing a session whose state is unknowable would be fail-destructive.decision:
noop — stop: do not notify the operator, do not write to routine-metrics.jsonl.queued — stop. The heartbeat.ts precheck drain block emits AUTO_CLOSE on the next tick where the operator has been idle >10 minutes.close-now — close directly by proceeding through the Auto-close path (--auto) above (steps 6–8, Closed Via: auto). Stop.ok === false — append the returned reason to SHELL.md ## Findings and stop; the routine retries next midnight.This path is intentionally silent: no operator notification on queue or drain — the Auto-closed S-NNN signal from the --auto archive is the only operator-facing output. The 10-minute lull threshold lives in scripts/lib/auto-close.ts, shared by the decision verb, the heartbeat-precheck drain, and the watchdog post-close-clear backoff.
session-archive.ts owns the final write. Gather:
Status: one of completed | partial | blockedBlockers: one line each, enough context for a cold startLessons: only genuinely useful ones. Before compiling, run the close debrief — answer three self-directed questions:
rebuilt wm pipeline in /tmp, 5 scripts, ~40 min/rerun). Substantial re-derived knowledge goes to compiled/ via the Artifacts bullet below instead of a Lesson line. If nothing qualifies, add nothing — no placeholder lines. These lines are the input procedure-capture recurs on (reflect reads ## Lessons of archived reports).
For question 3 — on a positive answer, for each defective skill: (a) record the what/why as a ## Lessons line above (the durable content channel reflect reads at graduation); (b) append one observations-ledger counter row using the canonical bare skill name (read the name: frontmatter from .claude/skills/<name>/SKILL.md; strip any claude-code-hermit:/<plugin>: prefix; lowercase) — fail-open so the close never aborts:bun ${CLAUDE_PLUGIN_ROOT}/scripts/observations.ts observe .claude-code-hermit skill-correction --origin=own-work <<'HERMIT_OBSERVATION'
skill-correction:<canonical-name>
HERMIT_OBSERVATION
The row is a bare recurrence counter; the Lessons line carries the reason content. Gated to operator-close — --auto skips step 1 and writes no correction rows. No || true needed: a rejected row answers ERROR|<reason> on stdout at exit 0. (A mis-invocation — wrong verb, or a missing state dir or source — exits 1 on purpose so a broken call site is loud; read the usage line, fix the call, and carry on. Neither outcome aborts the close.)Changed: list of files modifiedArtifacts: if this session produced a durable output, route it by shape:
compiled/topic-<slug>.md. Merge new findings into the existing sections rather than appending a dated copy; bump updated, refresh the one-line summary, keep the page under 150 lines (compact older material when merging), and cross-link related pages with [[wikilinks]].compiled/<type>-<slug>-<date>.md as before.
Either way include session: S-NNN in the frontmatter and list the wikilink here. Don't leave domain output wedged in SHELL.md Findings or a proposal body.completed, pending)claude-code-hermit:proposal-create skillclaude-code-hermit:reflect skill to reflect on accumulated experience. Reflect no longer requires archived reports — it uses memory. This runs before archiving so any findings are included in the archived report. Skip on --auto — during auto-close, session_state is still in_progress, which forces reflect-precheck into compute phase before the closed_via: auto filter can run; there is no operator-curated session content to reflect on anyway.
If reflect returns reflect: no candidates, scan this session's ## Findings and ## Progress Log for non-obvious discoveries not already in memory and issue the standard "remember it" reflection for any that clear the auto-memory threshold. Apply WHAT_NOT_TO_SAVE as normal.shutdown_skill). Read shutdown_skill from .claude-code-hermit/config.json. If non-null, invoke it as a skill command (the value may include arguments, e.g. /serve stop) via the Skill tool. Best-effort: on error or if the skill does not return, log a Monitoring line and continue to archival — never abort the close. Runs on both operator and --auto paths.TaskList, format as a markdown table. Then TaskUpdate(status=deleted) for completed tasks only — pending/in_progress tasks persist for next session.scripts/session-archive.ts archive --mode=close (full close — finalize SHELL.md and replace with fresh template in one operation). session-archive.ts derives cost itself from the cost-log window — no Cost: line to compute or pass.
Pipe the following compact structured payload on stdin — keep it brief, no freeform prose:
bun ${CLAUDE_PLUGIN_ROOT}/scripts/session-archive.ts archive --mode=close --state-dir=.claude-code-hermit <<'HERMIT_PAYLOAD'
Status: <completed|partial|blocked>
Blockers: <one line each, or none>
Lessons: <one line each, or none>
Changed: <file list, or none>
Artifacts: <wikilinks to compiled/ outputs produced this session, or none>
Closed Via: <operator|auto>
Next Start Point: <one line>
## Plan
<task table, if native Tasks were created>
HERMIT_PAYLOAD
Parse the single line of JSON printed to stdout. ok === false means the archive did NOT happen — no markers were written; surface the returned reason to the operator and retry once before giving up.session-archive.ts deletes state/pending-close.json itself on close/auto archive success (reported in its markers output field) — any pending midnight-drain flag is invalidated by a successful close, regardless of trigger. Nothing to do here.--auto only). On auto archive success the script writes state/clear-requested.json itself. The watchdog reads it on the next tick and sends /clear when the session is still alive + idle + unattended, resetting stale conversation context before the next scheduled wake incurs a cold cache-write. /clear preserves CronCreate routines and Monitor tasks; no re-arm is needed.Verify these before proceeding with close (applies to both modes):
completed | partial | blocked)## Completed claims a deliverable that a skill persists to compiled/ (e.g. a deep-dive, briefing, or decision doc), confirm it appears in ## Artifacts. If it doesn't, verify whether the output actually reached compiled/: if it did, add it to ## Artifacts; if it didn't, the deliverable was dropped, so record it in ## Blockers rather than leaving ## Completed asserting success.blocked: have you run /debug to check for tool/hook failures? Include diagnosis in blockers if relevantFull shutdown only:
If any check fails, fix it before closing.
tools
Composes and delivers the daily fitness brief — a forward-looking morning read (readiness + today's plan) or a backward-looking evening read (today's training, or an earned-rest note, + tomorrow's setup) — in the operator's configured voice. Invoke with /claude-code-fitness-hermit:fitness-brief --morning|--evening|--slot <name>. Becomes the plugin's two daily beats — the morning Strava connectivity check and the evening activity sync, RPE binding, and Run deep-dive.
development
Renew the hermit's long-lived Claude login token over the channel, before it expires. Relays a one-time sign-in link to the operator, takes the code back, installs the new token, and restarts. Activates on messages like 'relogin', 'renew my login', 'reauth', 'the login is expiring', or when doctor's credential-expiry check flags setup-token.
development
Synthesizes the past 7 days of archived briefs into a weekly digest — top stories, emerging vs faded themes, category activity, and per-source performance built from archive frontmatter. Delivers to the operator's configured channel and archives a weekly note. Designed as a weekly routine. Invoke with /feed-hermit:weekly-digest.
development
Manage developing story arcs tracked across briefs — add, resolve, and list active arcs in compiled/story-arcs-*.md. Arc Watch keywords drive the feed-brief arc-tagging enrichment. Invoke with /feed-hermit:story-arcs add|resolve|list.