name: context-save preamble-tier: 2 version: 1.0.0 description: | Save working context. Captures git state, decisions made, and remaining work so any future session can pick up without losing a beat. Use when asked to "save progress", "save state", "context save", or "save my work". Pair with /context-restore to resume later. Formerly /checkpoint — renamed because Claude Code treats /checkpoint as a native rewind alias in current environments, which was shadowing this skill. (gstack) allowed-tools:
- Bash
- Read
- Write
- Glob
- Grep
- AskUserQuestion triggers:
- save progress
- save state
- save my work
- context save
{{PREAMBLE}}
/context-save — Save Working Context
You are a Staff Engineer who keeps meticulous session notes. Your job is to
capture the full working context — what's being done, what decisions were made,
what's left — so that any future session (even on a different branch or workspace)
can resume without losing a beat via /context-restore.
HARD GATE: Do NOT implement code changes. This skill captures state only.
Detect command
Parse the user's input to determine the mode:
/context-saveor/context-save <title>→ Save/context-save list→ List
If the user provides a title after the command (e.g., /context-save auth refactor),
use it as the title. Otherwise, infer a title from the current work.
If the user types /context-save resume or /context-save restore, tell them:
"Use /context-restore instead — save and restore are separate skills now."
Save flow
Step 1: Gather state
{{SLUG_SETUP}}
Collect the current working state:
echo "=== BRANCH ==="
git rev-parse --abbrev-ref HEAD 2>/dev/null
echo "=== STATUS ==="
git status --short 2>/dev/null
echo "=== DIFF STAT ==="
git diff --stat 2>/dev/null
echo "=== STAGED DIFF STAT ==="
git diff --cached --stat 2>/dev/null
echo "=== RECENT LOG ==="
git log --oneline -10 2>/dev/null
Step 2: Summarize context
Using the gathered state plus your conversation history, produce a summary covering:
- What's being worked on — the high-level goal or feature
- Decisions made — architectural choices, trade-offs, approaches chosen and why
- Remaining work — concrete next steps, in priority order
- Notes — anything a future session needs to know (gotchas, blocked items, open questions, things that were tried and didn't work)
If the user provided a title, use it. Otherwise, infer a concise title (3-6 words) from the work being done.
Step 3: Compute session duration
Try to determine how long this session has been active:
if [ -n "$_TEL_START" ]; then
START_EPOCH="$_TEL_START"
elif [ -n "$PPID" ]; then
START_EPOCH=$(ps -o lstart= -p $PPID 2>/dev/null | xargs -I{} date -jf "%c" "{}" "+%s" 2>/dev/null || echo "")
fi
if [ -n "$START_EPOCH" ]; then
NOW=$(date +%s)
DURATION=$((NOW - START_EPOCH))
echo "SESSION_DURATION_S=$DURATION"
else
echo "SESSION_DURATION_S=unknown"
fi
If the duration cannot be determined, omit the session_duration_s field from the
saved file.
Step 4: Write saved-context file
Compute the path in bash (NOT in the LLM prompt) so user-supplied titles can't
inject shell metacharacters into any subsequent command. The sanitizer is an
allowlist: only a-z 0-9 - . survive.
{{SLUG_SETUP}}
CHECKPOINT_DIR="${GSTACK_HOME:-$HOME/.gstack}/projects/$SLUG/checkpoints"
mkdir -p "$CHECKPOINT_DIR"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
# Bash-side title sanitize. Pass the raw title as $1 when running this block.
# Example: TITLE_RAW="wintermute progress" bash -c '...'
RAW="${TITLE_RAW:-untitled}"
# Lowercase, collapse whitespace to hyphens, strip to allowlist, cap length.
TITLE_SLUG=$(printf '%s' "$RAW" | tr '[:upper:]' '[:lower:]' | tr -s ' \t' '-' | tr -cd 'a-z0-9.-' | cut -c1-60)
TITLE_SLUG="${TITLE_SLUG:-untitled}"
# Collision-safe filename: if ${TIMESTAMP}-${SLUG}.md already exists (same-second
# double save with same title), append a short random suffix. Filenames are
# append-only — never overwrite.
FILE="${CHECKPOINT_DIR}/${TIMESTAMP}-${TITLE_SLUG}.md"
if [ -e "$FILE" ]; then
SUFFIX=$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom 2>/dev/null | head -c 4 || printf '%04x' "$$")
FILE="${CHECKPOINT_DIR}/${TIMESTAMP}-${TITLE_SLUG}-${SUFFIX}.md"
fi
echo "CHECKPOINT_DIR=$CHECKPOINT_DIR"
echo "TIMESTAMP=$TIMESTAMP"
echo "FILE=$FILE"
The on-disk directory name is checkpoints/ (not contexts/) — this is a legacy
path kept so existing saved files remain loadable. Users never see it.
Write the file to the $FILE path printed above (use the exact string — do not
reconstruct it in the LLM layer).
The file format:
---
status: in-progress
branch: {current branch name}
timestamp: {ISO-8601 timestamp, e.g. 2026-04-18T14:30:00-07:00}
session_duration_s: {computed duration, omit if unknown}
files_modified:
- path/to/file1
- path/to/file2
---
## Working on: {title}
### Summary
{1-3 sentences describing the high-level goal and current progress}
### Decisions Made
{Bulleted list of architectural choices, trade-offs, and reasoning}
### Remaining Work
{Numbered list of concrete next steps, in priority order}
### Notes
{Gotchas, blocked items, open questions, things tried that didn't work}
The files_modified list comes from git status --short (both staged and unstaged
modified files). Use relative paths from the repo root.
After writing, confirm to the user:
CONTEXT SAVED
════════════════════════════════════════
Title: {title}
Branch: {branch}
File: {path to saved file}
Modified: {N} files
Duration: {duration or "unknown"}
════════════════════════════════════════
Restore later with /context-restore.
<<<<<<< HEAD:checkpoint/SKILL.md.tmpl
Resume flow
Step 1: Find checkpoints
{{SLUG_SETUP}}
CHECKPOINT_DIR="$HOME/.gstack/projects/$SLUG/checkpoints"
if [ -d "$CHECKPOINT_DIR" ]; then
find "$CHECKPOINT_DIR" -maxdepth 1 -name "*.md" -type f 2>/dev/null | xargs ls -1t 2>/dev/null | head -20
else
echo "NO_CHECKPOINTS"
fi
List checkpoints from all branches (checkpoint files contain the branch name in their frontmatter, so all files in the directory are candidates). This enables Conductor workspace handoff — a checkpoint saved on one branch can be resumed from another.
Step 1.5: Check for WIP commit context (continuous checkpoint mode)
If CHECKPOINT_MODE was "continuous" during prior work, the branch may have
WIP: commits with structured [gstack-context] blocks in their bodies. These
are a second recovery trail alongside the markdown checkpoint files.
_BRANCH=$(git branch --show-current 2>/dev/null)
# Detect if this branch has any WIP commits against the nearest remote ancestor
_BASE=$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD origin/master 2>/dev/null)
if [ -n "$_BASE" ]; then
WIP_COMMITS=$(git log "$_BASE"..HEAD --grep="^WIP:" --format="%H" 2>/dev/null | head -20)
if [ -n "$WIP_COMMITS" ]; then
echo "WIP_COMMITS_FOUND"
# Extract [gstack-context] blocks from each WIP commit body
for SHA in $WIP_COMMITS; do
echo "--- commit $SHA ---"
git log -1 "$SHA" --format="%s%n%n%b" 2>/dev/null | \
awk '/\[gstack-context\]/,/\[\/gstack-context\]/ { print }'
done
else
echo "NO_WIP_COMMITS"
fi
fi
If WIP_COMMITS_FOUND: Read the extracted [gstack-context] blocks. Each block
represents a logical unit of prior work with Decisions/Remaining/Tried/Skill.
Merge these with the markdown checkpoint file to reconstruct session state. The
git history shows the chronological arc; the markdown checkpoint shows the
intentional save points. Both matter.
Important: Do NOT delete WIP commits during resume. They remain the recovery trail until /ship squashes them into clean commits during PR creation.
Step 2: Load checkpoint
If the user specified a checkpoint (by number, title fragment, or date), find the matching file. Otherwise, load the most recent checkpoint.
Read the checkpoint file and present a summary:
RESUMING CHECKPOINT
════════════════════════════════════════
Title: {title}
Branch: {branch from checkpoint}
Saved: {timestamp, human-readable}
Duration: Last session was {formatted duration} (if available)
Status: {status}
════════════════════════════════════════
### Summary
{summary from checkpoint}
### Remaining Work
{remaining work items from checkpoint}
### Notes
{notes from checkpoint}
If the current branch differs from the checkpoint's branch, note this:
"This checkpoint was saved on branch {branch}. You are currently on
{current branch}. You may want to switch branches before continuing."
Step 3: Offer next steps
After presenting the checkpoint, ask via AskUserQuestion:
- A) Continue working on the remaining items
- B) Show the full checkpoint file
- C) Just needed the context, thanks
If A, summarize the first remaining work item and suggest starting there.
=======
origin/main:context-save/SKILL.md.tmpl
List flow
Step 1: Gather saved contexts
{{SLUG_SETUP}}
CHECKPOINT_DIR="${GSTACK_HOME:-$HOME/.gstack}/projects/$SLUG/checkpoints"
if [ -d "$CHECKPOINT_DIR" ]; then
echo "CHECKPOINT_DIR=$CHECKPOINT_DIR"
# Use find + sort instead of ls -1t: filename YYYYMMDD-HHMMSS prefix is the
# canonical order (stable across copies/rsync; mtime is not), and empty-result
# behavior is clean (no files → no output, no "lists cwd" fallback).
find "$CHECKPOINT_DIR" -maxdepth 1 -name "*.md" -type f 2>/dev/null | sort -r
else
echo "NO_CHECKPOINTS"
fi
Step 2: Display table
Default behavior: Show saved contexts for the current branch only.
If the user passes --all (e.g., /context-save list --all), show contexts
from all branches.
Read the frontmatter of each file to extract status, branch, and
timestamp. Parse the title from the filename (the part after the timestamp).
Present as a table:
SAVED CONTEXTS ({branch} branch)
════════════════════════════════════════
# Date Title Status
─ ────────── ─────────────────────── ───────────
1 2026-04-18 auth-refactor in-progress
2 2026-04-17 api-pagination completed
3 2026-04-15 db-migration-setup in-progress
════════════════════════════════════════
If --all is used, add a Branch column:
SAVED CONTEXTS (all branches)
════════════════════════════════════════
# Date Title Branch Status
─ ────────── ─────────────────────── ────────────────── ───────────
1 2026-04-18 auth-refactor feat/auth in-progress
2 2026-04-17 api-pagination main completed
3 2026-04-15 db-migration-setup feat/db-migration in-progress
════════════════════════════════════════
If there are no saved contexts, tell the user: "No saved contexts yet. Run
/context-save to save your current working state."
Important Rules
- Never modify code. This skill only reads state and writes the context file.
- Always include the branch name in frontmatter — critical for cross-branch
/context-restore. - Saved files are append-only. Never overwrite or delete existing files. Each save creates a new file.
- Infer, don't interrogate. Use git state and conversation context to fill in the file. Only use AskUserQuestion if the title genuinely cannot be inferred.
- This is a gstack skill, not a Claude Code built-in. When the user types
/context-save, invoke this skill via the Skill tool. The old/checkpointname collided with Claude Code's native/rewindalias — the rename fixed that.