Skip to content
Development
Skill

/session-snapshot

Internal skill for commands. Write a recovery-grade session snapshot (file-primary) for /lets:end and for the --session snapshot-only flag of /lets:end and /lets:note. Always writes a .lets/sessions/ file; adds a one-line task pointer only when a task is unambiguously active. Do

From plugin
lets-workflow
1719 skills15 agents25 commands
Install
$ npx -y skills add restarter/lets-workflow --skill session-snapshot --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/session-snapshot

Context preview

The summary Claude sees to decide when to auto-load this skill.

Internal skill for commands. Write a recovery-grade session snapshot (file-primary) for /lets:end and for the --session snapshot-only flag of /lets:end and /lets:note. Always writes a .lets/sessions/ file; adds a one-line task pointer only when a task is unambiguously active. Do

SKILL.md

session-snapshot.SKILL.md
name: session-snapshot
description: Internal skill for commands. Write a recovery-grade session snapshot (file-primary) for /lets:end and for the --session snapshot-only flag of /lets:end and /lets:note. Always writes a .lets/sessions/ file; adds a one-line task pointer only when a task is unambiguously active. Do not trigger on user conversation - only when those commands need the snapshot.
user-invocable: false

Session Snapshot

Shared snapshot primitive for `/lets:end` (settlement + snapshot) and for the `--session` snapshot-only flag on both `/lets:end` and `/lets:note`. **Single source of truth** - every caller delegates here so the template and file/pointer behavior never drift.

Goal: ONE recovery-grade `## RESUME` snapshot, **file-primary** - it ALWAYS lands in a `.lets/sessions/` file (the single trail `/lets:start` reads), regardless of task state (feature / trunk / --main / no-task). The active task gets only a ONE-LINE pointer to that file, and only when a task is unambiguously active (via detect-task, NEVER a `list-by-status | head -1` guess).

> **Contract - this skill ONLY writes the snapshot (file + optional pointer).** It does NOT end the session, push, merge, commit, or close anything. The caller decides what else to do.

Arguments (from the caller)

Passed via the `Skill` invocation's `args` string as space-separated `key=value` pairs (e.g. `args: "kind=end pointer=off task-id=lets-abc range=session: X..HEAD (3 commits)"`). Put `range=` / `task-id=` LAST when the value contains spaces - each consumes the rest of the string. Any omitted key falls to its default.

  • `kind` = `session` (default) | `end` - selects the `### Record` line ONLY; both resolve to the same `artifact-path` kind (`snapshot`), because a mid-session record and a session-end record are the same artifact written at different moments. There is no `precompact` kind: a snapshot taken before a `/compact` is just a mid-session one, and naming the caller's next intention in a permanent file is what made the old flag dishonest. Step 3 owns the filename, never build it here.
  • `pointer` = `off` (default) | `auto` - whether the skill writes the standalone one-line task pointer. `off` is the SAFE default (a caller that forgets never double-writes a task comment); a caller that wants the skill to write the pointer passes `auto` explicitly (both snapshot-only callers do). `/lets:end` default passes `off` when it folds the pointer into its own progress comment, `auto` otherwise.
  • `range` (optional) - a RANGE_DESC string (e.g. `session: <ref>..HEAD (N commits)`). A caller that passes one WINS - `/lets:end`'s default flow does, because it already read the boundary to gate its own offers. When it is absent, Step 2 resolves it through `session-boundary` rather than omitting the block, for EVERY kind. Every snapshot is read back by the same consumer, `/lets:start`, for the same purpose - where the work began - so none of them has a reason to be written without its range (lets-yprsv).
  • `task-id` (optional) - pre-resolved active task from the caller's own detect-task.

Step 1: Active task

If the caller passed `task-id`, use it. Else run `Skill(skill: "lets:detect-task")`. "Unambiguously active" = detect-task returns exactly one task. No task (or ambiguous) -> file only, no pointer, no prompt.

Step 2: Gather state

git branch --show-current
git log --oneline -5
git status --short          # uncommitted / untracked
git rev-parse --short HEAD

# Session id + transcript path - BOTH from the Bash-injected env var (ONE channel; matches take-task Step 5).
SID=$CLAUDE_CODE_SESSION_ID
TRANSCRIPT_PATH=$(find "$HOME/.claude/projects" -maxdepth 2 -name "${CLAUDE_CODE_SESSION_ID}.jsonl" 2>/dev/null | head -1)
TRANSCRIPT_PATH=${TRANSCRIPT_PATH:-"(not found)"}
# ECHO both - a bash var is invisible to the Write tool; the model needs the printed values for the template.
echo "SID=$SID"
echo "TRANSCRIPT_PATH=$TRANSCRIPT_PATH"
# Peers of this repo + this chat's own binding (roles / names only, never another session's full id).
command -v lets >/dev/null 2>&1 && lets peers who --json 2>/dev/null
sed -n 's/^orc: //p' "$(git rev-parse --show-toplevel)/.lets/sessions/.task-$(git branch --show-current | tr '/' '-')" 2>/dev/null | head -1

Range (when the caller passed none)

If `range` was NOT passed, invoke `Skill(skill: "lets:session-boundary")` and use its echoed `SESSION_RANGE_DESC` as the RANGE_DESC for Step 3's `### Range` block; surface its stderr NOTEs. Do NOT re-derive the boundary here - that ladder lives in `session-boundary` alone (lets-370mx). If the skill is unavailable, omit the `### Range` block rather than writing an unqualified number.

Step 3: Write the snapshot FILE (ALWAYS)

Resolve the path via `Skill(skill: "lets:artifact-path", args: "kind=snapshot ext=md")` for both kinds; pass `task=<id>` when the caller already resolved one. The echoed `ARTIFACT_FILE` is `$SNAP_FILE` and its basename is `$SNAP_BASENAME` - reuse both VERBATIM in Step 4 + the Return, never recompute (a second `date` drifts the pointer off the file actually written). Shape: `.lets/sessions/{date}-{HHMM}-{task-id|branch-slug-6hex}-snapshot[-vN].md` - task-scoped, `-vN` on collision, so parallel worktrees sharing `.lets/` never overwrite each other (lets-05c4s).

Write `$SNAP_FILE` (the echoed path) via the Write tool with the template below, substituting the bash-captured `$SID` / `$TRANSCRIPT_PATH` from Step 2 - and reuse `$SNAP_BASENAME` verbatim in Step 4 + the Return, never recomputing the minute-precise timestamp. Use ONLY that single bash session-id channel (`$CLAUDE_CODE_SESSION_ID`, captured as `$SID`) - do NOT use the command-load-time template channel (the `CLAUDE_SESSION_ID` template variable in `${...}` form), which is fragile inside a multiline Write arg (lets-bdkvd QA #13) and would itself be substituted here if written literally. English; one continuous line per paragraph - no hard wrap. For any sec

Read more
Ships withlets-workflow

A development workflow plugin for Claude Code Stop babysitting your AI. Start shipping with it.

Get the whole plugin

Other skills on lets-workflow.