| name | pickup |
| description | Load a previous session's HANDOFF brief into context to resume work — distinct from Claude Code's `/resume` which restores the full conversation; this loads only the lightweight YAML brief emitted by /handoff. Default state at session start is clean (no auto-load). Use when builder invokes /pickup, /pickup <N>, /pickup <keyword>, or says 接上次 / 续上次 / 接活. Skip if not explicitly requested, pure Q&A, or builder already started a different task. |
/pickup — Session resume skill
How the candidate table reaches the user (three layers, only one talks to AI)
Claude Code v2.1+ does not print SessionStart hook stdout to the user's terminal — hook output is injected only as a system-reminder into the AI's conversation context. The /pickup design splits "human visibility" from "AI context loading" so neither bloats the other:
-
Shell wrapper (cc() function in ~/.zshrc) — runs session-resume-prompt.sh --list synchronously before launching claude. The boxed table prints in the user's terminal so builder sees the candidates immediately at session start. This is the human-visible layer; AI never sees it.
-
SessionStart hook (registered in the host's .claude/settings.json for Claude Code, with CLI fallback elsewhere) — runs the same script with no args, which emits exactly one line into the AI's context: 💡 N active handoff(s). Latest: <topic> (<project>, <when>). Type /pickup to see the list, or /pickup N to load row N. No mapping, no row paths, no [FOR AI] instructions. This is the lightweight awareness layer — AI knows handoffs exist but does not stand by waiting for a digit input.
-
/pickup skill (this file) — the only path that loads candidate paths into AI context. Triggered by builder typing /pickup (with optional argument). The skill itself runs session-resume-prompt.sh --mapping to fetch the row → path mapping, then resolves builder's input (digit / keyword) and Reads the chosen handoff file. Mapping enters context only when builder has explicitly asked to pick up.
Mandatory AI behavior when the SessionStart hint is in context:
- If builder types
/pickup (or /pickup 5, /pickup <keyword>) → invoke this skill. The skill internally fetches the mapping; do not ask builder for paths or row counts.
- If builder types anything else → proceed with that request normally. Do not assume bare numbers or keywords are pickup commands. Do not nag builder about the pending handoffs.
- If no hint is present (empty handoff dir or hook errored), there is nothing to mention.
Contract
One /pickup invocation produces:
- Resolve input — three forms (see § Input handling): no arg → render boxed table, wait for next message;
/pickup <N> → load row N directly; /pickup <keyword> → substring match.
- Loaded handoff — once a row is chosen, Read the chosen HANDOFF file so its session brief enters context.
- Verbal report (4 lines, printed to chat) — confirmation of what was loaded and the next-step pointer.
If builder invokes /pickup then hits Enter on the candidate table (no-load), the skill exits without loading anything.
Phase 1 — Pick the candidate
When /pickup is invoked:
-
At the start of every /pickup invocation, run bash ${BDC_HOME:-$HOME/.bdc}/scripts/session-resume-prompt.sh --mapping first via the Bash tool. Because: this emits the [resume-candidate-mapping] block (rows 1–8 with path, topic, project), which is the source of truth for resolving digits and keywords; do not guess paths from memory.
-
Branch on the argument builder passed to /pickup:
- No argument → also run
--list to render the boxed table in chat so builder can see the candidates, then wait for the next user message and apply § Input handling rules.
- Digit (
1–N) → look up that row in the mapping, jump to Phase 2.
- Keyword (any non-digit text) → apply § Input handling rule 5 (substring filter on
topic + project).
Render rules: see § Candidate list rendering below.
Input rules: see § Input handling below.
Phase 2 — Load the handoff
Once a candidate is chosen (Phase 1 returned a path):
- Read the full HANDOFF.md file (Schema A: project repo's
HANDOFF.md; Schema B: ${BDC_HOME:-$HOME/.bdc}/handoff/<topic-slug>.md).
- For Schema A, also identify the chosen branch's
## {branch} section (the candidate-list row carries the branch name).
- The brief block (under
## Context for Schema A or ## Last session brief for Schema B) is now in context. Parse next_step, why_stopped, and plan_progress fields for the verbal report.
Phase 3 — Verbal report
See § Verbal report below.
Candidate list rendering
Sources (merge into one list):
-
Schema B (shared task cards): every file matching ${BDC_HOME:-$HOME/.bdc}/handoff/*.md whose frontmatter has kind: handoff and status ≠ superseded. Both active and done rows show — handoff is not "random scratchpad" but real session output, so finishing a topic shouldn't make it instantly invisible. Only superseded (explicitly archived/replaced) hides. One row per file. The row's last_session comes from the frontmatter last_session: field; topic = frontmatter title or the filename slug; project = first entry of prs: array (e.g. example/repo#199 → ExampleProject), or shared if prs: is empty.
-
Schema A (project-local HANDOFF.md): every ## {branch} (updated YYYY-MM-DD) section across ~/Projects/*/HANDOFF.md. One row per branch section. The row's last_session is parsed from (updated YYYY-MM-DD); topic = first line of **What**:; project = directory name under ~/Projects/.
Sort: descending by last_session (most recent first). Ties broken by file mtime.
Top 8: cap the list at 8 rows. If fewer than 8 candidates exist, show all of them; do not pad with empty rows.
Row format (fixed-width columns, plus badge column on the right):
# topic project last badges
─── ──────────────────────────────── ───────────── ───────────── ──────
1 cn-en-language-cleanup example-search 3h ago 🏠
2 handoff-skill-upgrade ${BDC_HOME:-$HOME/.bdc} yesterday
...
8 global-cn-en-rule shared 1w ago
topic truncated to 32 chars with … suffix when needed.
last is humanized relative time: Nh ago (< 24h), yesterday, Nd ago (< 7d), Nw ago (< 28d), Nmo ago thereafter.
- ★ marker rules in § Badges below.
Empty list: if 0 handoffs found, print one line (no handoffs — clean session) and exit without a prompt — the AI continues with no loaded context.
Badges
Badges are visual hints attached to candidate rows. They do not affect sort order, only render in the rightmost column. Each row may carry zero, one, or multiple badges.
-
★ — most recent: marker on row 1 (the candidate with the latest last_session). Purely a visual hint that "this is where you stopped most recently." It is not the default action — pressing Enter still loads nothing. Render the marker in front of the row number: ★1 instead of 1.
-
🏠 — current cwd matches the candidate's project: when pwd (or its parent path) starts with the same project root as the candidate's project field. For Schema A this means pwd -P startsWith ~/Projects/<project>. For Schema B this means pwd -P startsWith ~/Projects/<project-from-prs-array>. Helps builder spot "the handoff for the project I'm sitting in."
-
🟡 — open PR has unresolved comments: for Schema B candidates whose prs: frontmatter contains entries, run gh pr view <repo>#<num> --json reviewDecision,reviewThreads for each entry; if any thread has isResolved: false, mark 🟡. For Schema A candidates, derive PR set from the per-branch section's **Plan**: linked PRs (if any).
Badge computation cost: 🟡 requires a network call per PR. Cache results for 5 minutes (the candidate list is short-lived per invocation; one cache cycle covers re-renders during filter input). On gh failure (network down, auth missing) silently omit 🟡 — never block the candidate list on a network error.
Multiple badges on one row: render with single space separator, e.g. 🏠 🟡. Order: 🏠 then 🟡 (cwd before PR).
Input handling
After the candidate list is rendered, builder's next chat input is interpreted by these rules (precedence top-to-bottom):
-
Empty / whitespace-only / Enter → no-load. Skill exits cleanly. AI continues with a clean session, no handoff loaded. (Default action — protects daily flow.)
-
Word none or skip (case-insensitive) → no-load. Same as Enter; explicit verbalization for builder who prefers typing over hitting return.
-
A bare digit 1–8 (or 1–N if fewer candidates than 8) → load that row. Resolve the row's underlying handoff path (Schema A: <project>/HANDOFF.md plus the branch section name; Schema B: ${BDC_HOME:-$HOME/.bdc}/handoff/<slug>.md) and proceed to Phase 2.
-
Out-of-range digit (0, 9+) → reprint the candidate list with a one-line warning (no row N — pick 1–<max>). Do not exit, do not load.
-
Any other text → keyword filter. Treat the input as a substring match against the union of topic and project columns (case-insensitive). If 1 candidate matches uniquely → load it; if multiple match → re-render the list filtered to those rows; if 0 match → reprint the full list with (no match for "<input>" — pick a row, hit Enter, or refine).
No timeout, no auto-load. The skill stays in the input loop until builder explicitly chooses (load by number/keyword) or skips (Enter / none / skip).
Mid-session re-invocation: when /pickup is invoked while a handoff is already loaded, render the candidate list as usual; on load, the previously loaded brief stays in context (LLM context cannot be physically cleared). The skill prints a one-line note (previous handoff X stays in context; loading Y on top) so builder knows the conversational state.
Verbal report
After Phase 2 loads the chosen handoff, print exactly 4 lines to chat so builder has a quick read of what just landed in context:
📁 Loaded: <handoff path>
🎯 Last stop: <why_stopped from brief — strip the `inferred:` prefix if present>
⚠️ Since last session: <git/PR diff summary — see below>
✅ Next step: <next_step from brief>
Field sources:
📁 — the path of the loaded HANDOFF file. For Schema A include the branch name in parentheses: ~/Projects/foo/HANDOFF.md (branch: feature/x).
🎯 — the brief's why_stopped value, with the inferred: prefix stripped if present so the line reads naturally. If the field was empty in the brief, print (not recorded).
⚠️ — current state since the brief was written: count of new commits on the branch (git log <last-handoff-hash>..HEAD --oneline | wc -l), open PR count for the project, and any PR with new comments since brief's date. Format: 3 new commits, PR <id> has 2 new comments. If nothing changed, print no change since last session.
✅ — the brief's next_step value, verbatim.
Why these 4 lines: they collapse the brief's most load-bearing fields into one screen so builder can decide in 5 seconds whether to continue this thread or switch with /pickup. The full brief is already in context; the 4-liner is for the human reader, not the AI.
Switch invitation: after printing the 4 lines, render one trailing line: (continue here, or /pickup to switch). Do not auto-prompt — let builder drive.
Standards
- Default action is no-load: when in doubt, render the list and let builder choose. Never auto-load on session start.
- Do not auto-commit / push / open PRs / open issues — this skill only Reads files and prints chat output.
- Do not modify the loaded HANDOFF file during /pickup. Updates to HANDOFF happen via /handoff at session end, not /pickup at session start.
- Source of truth for the brief schema:
${BDC_HOME:-$HOME/.bdc}/standards/ai-friendly-artifacts.md § 1.4. Do not duplicate the schema here.