handoffs — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited handoffs (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Browse handoff files written by /wrap-up (in ~/.claude/handoffs/) and pick one to resume. Companion to /wrap-up (the writer) and /landscape (the morning orienter).
/landscape flagged that recent handoffs exist for this repo.ls ~/.claude/handoffs/ is grep-friendly, but this skill renders the metadata table.Not sure whether to resume a handoff or start fresh work? That's a morning orientation call, not a handoffs call — run/landscapefirst. Its**Next:**line arbitrates resume-a-handoff vs/next safefrom the full picture (Jira, PRs, beads, working copy). Come here once you've decided to resume a thread.
cd if the recorded worktree differs from pwd./rename {slug} line (§5), but /rename is a built-in command — only you typing it triggers a rename.cd there and run /handoffs again.~/.claude/handoffs/archive/ — they stay on disk and greppable. Everything else you curate manually.MUST use the helper script. Never construct ad-hocls/greppipelines against~/.claude/handoffs/— they bypass the per-script permission allowlist and miss the repo-matching logic.
~/.claude/skills/handoffs/scripts/list.sh --check-branches--check-branches adds branch-liveness + PR classification for current-repo handoffs. The full flag semantics, the 21-field ---HANDOFFS--- line format, and every field's meaning (supersede, branch-state, pr-state, archive-class, beads-done, deliverable-field, beads-progress, needs-review) are specified once in `~/.claude/skills/handoffs/REFERENCE.md` — Read it. Both /handoffs and /handoffs-tidy read off the same definitions, so the two never drift on classification.
The sections you parse here: ---CURRENT-REPO--- / ---CURRENT-REPO-DISPLAY--- (identity + label, or NONE outside a repo), ---HANDOFFS--- (one row per handoff, newest first), ---SUMMARY--- (the counts, incl. current_repo_total, current_repo_superseded, current_repo_stale, unresolved), and ---OTHER-REPOS--- ({repo-key}|{count}|{display}, count desc). The ---CURRENT-REPO-LATEST--- / ---CURRENT-REPO-LIVE--- sections are for /landscape; this skill renders the full table instead and can ignore them.
See REFERENCE §Fields for repo-identity resolution (Repo root:→ cwd walk-up, origin-URL keying,.claude-symlink unification,UNRESOLVED), the supersede rules, and the branch-state / pr-state / archive-class semantics this skill renders below.
list.sh can read PR and bead state but cannot call the Jira MCP — so a handoff whose only "finished" signal is its ticket being closed in Jira still arrives with archive-class empty (🟢 live). Close that gap here, model-side, per REFERENCE §Jira-Done: same gate (only under --check-branches, only for still-live rows naming a ticket), same batched JQL query, same promote-to-safe rule. Skip silently if the Jira MCP errors or isn't configured.
If current_repo_total > 0:
### 🧷 Handoffs — this repo ({count})
| Date | Slug | Branch | Where | Worktree | Status | Archive |
|------|------|--------|-------|----------|--------|---------|{date} {time} (e.g. 2026-06-03 14:32) so same-day handoffs are distinguishable at a glance. Drop the time and show just {date} when {time} is ? (neither header time nor mtime was available).ab-1107-cta-event).? if unknown. (current) if it matches pwd. Special cases: empty cwd → —; cwd ending in / → use the next-up segment, e.g. worktrees/ → (worktrees root).exists=Y; ✂️ (icon only, no word) if exists=N. The ✂️ reads as "pruned" and the footnote legend below spells it out; everything in the current-repo table is pickable by definition (handoffs that couldn't be matched to a repo never reach this table), so no separate Pickable column.✂️ and ⚠️ (the wide colored forms, not the narrow text ✂︎/⚠︎). Mixing the two presentations across rows makes column widths jump — keep one form so the cells line up.safe → 🗄️ safe, keep → ⚠️ keep?, empty → — (a §1a Jira-Done promotion counts as safe).If current_repo_total == 0 and CURRENT-REPO != NONE: _No handoffs for this repo ({CURRENT-REPO-DISPLAY})._
If CURRENT-REPO == NONE: _Not in a git repo — handoffs cannot be matched to a current repo. Showing global counts only._
Render this section exactly once, in this position. Do not repeat the "No handoffs for this repo" sentence later (e.g. after §3's other-repos table) — once is enough. If current_repo_total == 0 but other repos exist, the next section's table provides the rest of the context.
If other_repos > 0:
### 🗂️ Handoffs — other repos ({other_repos})
| Repo | Handoffs |
|------|----------|{display} field from each ---OTHER-REPOS--- line (the script has already stripped .git and taken the basename).Otherwise skip the whole section.
If unresolved > 0, surface them. When unresolved ≤ 5, list each one by filename so the user can cat it directly — a bare count is unactionable. When unresolved > 5, fall back to a count footnote to avoid clutter.
unresolved ≤ 5:
_⚠️ {unresolved} handoff(s) could not be matched to any repo (recorded path is invalid or its parent tree is gone):_
- `~/.claude/handoffs/{filename}`
- `~/.claude/handoffs/{filename}`Pull the filenames straight from the ---HANDOFFS--- section where repo-key=UNRESOLVED. Render in the same newest-first order the script emits.
unresolved > 5:
_⚠️ {unresolved} handoff(s) could not be matched to any repo (recorded path is invalid or its parent tree is gone). Run `grep -L "Repo root:" ~/.claude/handoffs/*.md` to find them._Do not add a global pruned-count footnote. The Worktree column in the current-repo table already makes pruning visible per-row. Add a one-time pickability hint immediately under the current-repo table (when current_repo_pruned > 0):
_✂️ = original worktree was pruned (no longer exists); still pickable — you'll resume in your current checkout or a fresh worktree._This is a tooltip-style explainer keyed to the column legend, not a count. Skip it when current_repo_pruned == 0.
Run the archive flow exactly as REFERENCE §Archive-flow specifies: skip if current_repo_superseded == 0 and current_repo_stale == 0; otherwise present the candidates in the three regret-ordered groups (Superseded / Done / Stale), prompt with AskUserQuestion (multiSelect, labelled and described per that section), archive the selected filenames in one archive.sh call, and parse ---ARCHIVED--- / ---SKIPPED---. Never offer a 🟢 live, 🟠 PR open, or unknown row; archive.sh only moves, never deletes.
When the flow says to drop archived rows from "any subsequent listing or picker", that is this skill's current-repo table and §4 picker — remove the archived rows and subtract them from current_repo_total so §4 doesn't offer them. If the user selects none, render nothing and continue to §4.
(/handoffs-tidy is the standalone twin of this step: same flow, no table or picker around it.)
If any current-repo row has needs-review=Y, run the assisted prompt per REFERENCE §Trunk-review after §3b — a separate, clearly-labelled prompt for legacy trunk-parked handoffs the script couldn't auto-classify (partial bead closure, no **Deliverable:** marker). Drop any the user archives from the table and §4 picker, same as §3b. Skip the step entirely when no row is flagged.
If current_repo_total == 0, skip this step.
If current_repo_total is between 1 and 4, use AskUserQuestion:
{date} {slug} (truncate slug if needed to stay under the chip width).{time} · Branch: {branch} | Where: {basename of cwd} (lead with the HH:MM so several same-day handoffs are distinguishable here; omit the {time} · prefix when {time} is ?).If current_repo_total > 4, do not force the picker (the option cap is 4). Instead, print:
**Pick one to load:** reply with the slug or filename (e.g. `ab-1344-login-state-decision` or `2026-05-21-ab-1344-login-state-decision.md`).Only pickable rows (✅) are valid choices. Pruned-worktree handoffs are pickable — they just resume in a different checkout. If the user picks an unresolved one, point them at cat ~/.claude/handoffs/{filename} for read-only access.
Use the Read tool on the absolute path {HANDOFFS-DIR}/{filename}.
Render the file content verbatim inside a fenced block so the rest of the session treats it as resume context:
### 📥 Loaded: `{filename}`
{file contents}
Then surface a paste-ready rename so the resumed session is legible in the session list. Derive the slug from the filename (strip the YYYY-MM-DD- prefix and .md suffix):
**Rename this session to match:**
/rename {slug}
(Paste it — `/rename` only fires when you run it.)Skip the rename line only if the current session is already named for this slug.
Then offer the right follow-up based on exists:
#### exists=Y — worktree still on disk
If recorded cwd differs from pwd:
**Switch directory:** `cd {cwd}` _(handoff was recorded in a different worktree of this repo)_If recorded cwd matches pwd: no extra note.
(Don't run cd yourself — shell state doesn't persist across Bash calls.)
#### exists=N — worktree was pruned
First, check whether this handoff is superseded. If the picked row's superseded-by field is non-empty, a newer handoff already continues this thread (same branch, topic, or same-day re-wrap — see supersede-reason). Look up that newer row by filename; if it has exists=Y, the work likely moved to a live worktree — surface it as the recommended option before asking about recreation:
Original worktree{cwd}is gone. A newer handoff{newer-slug}({humanised reason}) continues this thread in{newer-where}(still on disk). What would you like to do?
Options (use AskUserQuestion):
Open {newer-filename} from {newer-where} (recommended). Treat as if the user had picked that filename in §4 — go back to the top of §5 with the new file.If superseded-by is empty (or the newer handoff is itself pruned, exists=N), skip this prompt.
Second, assess this checkout as a landing spot. The original worktree is gone, so you'll resume either here (the current checkout) or in a fresh worktree. Gather the facts in one call:
git rev-parse --abbrev-ref HEAD && git status --porcelain && git branch --list "{branch}"Read four signals from the output:
HEAD (line 1) already equals {branch}.git status --porcelain printed nothing (no uncommitted work to disrupt by switching branches).git branch --list printed a line for {branch}.HEAD matches worktree-*, the auto-generated name claude - gives a throwaway worktree. This is the strong signal that the user spun up this checkout specifically to host the resume.Then branch on them:
on-branch → already on a viable spot. Render and skip to §6, no prompt:
**Already on `{branch}` here** (`{pwd}`). No worktree action needed — resume from this checkout.clean + fresh worktree (the claude - case) → the user opened this worktree for this handoff, so adopting the branch in place is the whole point — don't push a nested worktree. The adopt command depends on branch-exists-locally:
git checkout -b {branch} (create it here — the usual case when wrap-up predated the branch or it was deleted).git checkout {branch}.Ask via AskUserQuestion:
Original worktree{cwd}is gone, but{pwd}looks like a fresh worktree ({HEAD}) you opened to resume here. Adopt{branch}in this checkout?
Options:
git checkout -b {branch} (or git checkout {branch} if it already exists). No second worktree.On Adopt here, run the adopt command. On success render and skip to §6:
✅ Now on `{branch}` in `{pwd}`. Resume from this checkout.If it fails (e.g. the branch is checked out in another live worktree), surface the stderr and offer the Separate worktree flow instead.
otherwise (dirty tree, or a real branch you'd disrupt by switching) → adopting in place would clobber existing work, so offer a fresh worktree. Fall through to the worktree-creation flow below.
Offer to recreate the worktree. Compute the proposed values:
{worktree-path} = the recorded cwd if its parent directory still exists on disk; otherwise {pwd}/../worktrees/{basename of recorded cwd}. If the basename is empty or generic (worktrees, .), use {topic-slug} from the filename instead.{branch} = the branch parsed from the handoff (Branch column).git worktree add form depends on branch-exists-locally (from the assessment above): an existing branch is checked out into the new worktree (git worktree add {path} {branch}); a missing branch must be created with it (git worktree add -b {branch} {path}). Plain git worktree add {path} {missing-branch} errors — that's the failure to avoid.Ask via AskUserQuestion:
Original worktree {cwd} is gone. Recreate it?Options:
git worktree add form matching the branch's existence (see above) in the current repo.{pwd}); user can adopt the branch themselves (git checkout -b {branch} if it doesn't exist yet, else git checkout {branch}).git worktree add invocation without running.On Create worktree, pick the form by branch-exists-locally:
git worktree add "{worktree-path}" "{branch}" # branch exists locally — check it out
git worktree add -b "{branch}" "{worktree-path}" # branch missing locally — create it with the worktreeIf the command succeeds, render:
✅ Worktree created at `{worktree-path}`.
**Switch directory:** `cd {worktree-path}`If it fails (path already exists, dirty index, …), surface the stderr and fall back to Show command behaviour. Do not retry, do not delete anything, do not --force.
On Resume here:
**Resuming in current checkout** (`{pwd}`). If you need the branch, run `git checkout -b {branch}` (or `git checkout {branch}` if it already exists) — the pruned worktree's commits are still in the repo.On Show command, print both forms and say which to run:
**Worktree pruned.** Original location `{cwd}` no longer exists. To recreate it yourself:
git worktree add -b {branch} {worktree-path} # if {branch} doesn't exist locally yet — create it git worktree add {worktree-path} {branch} # if {branch} already exists — check it out
---
**Next:** act on the resume block's *Suggested next step*. Run `/landscape` for a fresh orientation if more than a day has passed since the handoff was written.Each step is independent — a failure in one should not block the others.
_No handoffs directory yet. Run /wrap-up at the end of a session to create one._ and stop._No handoffs saved yet._ and stop.cat — don't fabricate content.git ls-remote fails or times out): branch-state degrades to local-only — merged is still detected against the local default tip, but branches with no local ref report unknown rather than a false gone. The skill keeps working; the stale group in §3b just shrinks. Don't retry the network call.pr-state reports unknown and the Status/Archive columns fall back to branch-state, plus beads-done (which is local and unaffected). No error, no retry. The cost: squash-merged branches reappear as ⚪ branch gone (keep?) rather than ✅ merged (safe), unless a closed bead or §1a Jira-Done still marks them done.beads-done is always empty — no error. Finished work then relies on PR/branch/supersede (and §1a Jira) signals alone.🟢 live — acceptable degradation, never a failure./wrap-up. If a session ends without /wrap-up, there is nothing here to recover. That's intentional — the index lists /wrap-up next to /handoffs for a reason.~/.claude/handoffs/YYYY-MM-DD-{slug}.md. Collision suffixes from wrap-up (-2, -3, …) are preserved as part of the slug.main), all referenced beads closed (local bd, works even on trunk repos with no PR), and a Jira ticket in the Done category (§1a, skill-resolved). Any one is enough; together they cover the cases — trunk-based completion, post-merge wrap-ups on main, ticket-only closure — that the old branch/PR-only check reported as 🟢 live.list.sh, not the model — same source /wrap-up uses for its at-save archive offer, so both skills agree on what supersedes what. Reasons: branch > slug > collision; ticket/cwd overlap is intentionally excluded.--check-branches — the queries run in pwd, so other repos always report unknown. PR state (from gh, auto-enabled when present) is ground truth and overrides the local branch-state heuristic; crucially it's the only signal that catches a squash-merge, where the feature branch is never an ancestor of the default tip. Liveness is deliberately separate from supersede: superseded = "a newer handoff continues this" (low-regret); merged-PR = "the work shipped" (low-regret); stale = "the branch is dead/abandoned and nothing supersedes it" (may be the only record — higher regret).remote.origin.url first, then realpath of git-common-dir. Linked worktrees of one repo share the same key. Two independent clones with the same origin URL collapse to one row..claude symlinks to B's .claude subdir. A defers to B's identity. Display is B's basename..claude symlinks to B's repo root itself (B is just a scratch state-holder, not a working project). A keeps its own identity (and display); when B resolves on its own (e.g. via a handoff whose cwd lands inside B), a one-level sibling scan finds A's bare link pointing at B and defers up. Net effect: both ends group under A, the "real" repo. The follow is one-hop in either direction, so reciprocal links don't cycle.git worktree remove the handoff still groups correctly. When picked, the user resumes from their current checkout (or creates a fresh worktree)./next and /start-ticket, not by this skill) emit an extra ---MATCHED-HANDOFFS--- section: current-repo, non-stale handoffs whose **Beads:** / **Jira:** header field contains that exact token. It reuses the same supersede + bead-closure + (with --check-branches) PR liveness machinery, so a bead/ticket resume surfaces only the live tip, never a superseded or shipped handoff — and because bead-closure is local, a handoff whose beads are all closed is filtered out even without --check-branches. Matching is exact-token and case-insensitive (bd-12 never matches bd-123). The flags leave every other section byte-identical, so this skill, /wrap-up, and /landscape are unaffected.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.