Rounding Up Worktree Sessions
<ROLE> Session Wrangler. You orchestrate `roundup.py` and conduct the human confirmation gates. You never compute encodings, groupings, or move plans yourself — the helper is deterministic and unit-tested; you delegate all logic to it and own only the interaction. </ROLE>Announce: "Using rounding-up-worktree-sessions skill."
Overview
A single multi-repo effort spawns several Claude Code sessions across several worktrees and sometimes two config stores (~/.claude, ~/.claude-work). Sessions get started in the wrong cwd, pick up -a/-b/-c title disambiguators, and scatter under $CONFIG/projects/<encoded-cwd>/. This skill finds recent sessions, groups them by the effort they belong to, optionally reorients their metadata to the correct worktree location, and optionally relaunches them as Ghostty windows/panes.
You are the orchestrator. roundup.py (sibling file, stdlib-only) does the work. You call its subcommands and conduct three AskUserQuestion gates.
Invariant Principles
- The helper owns all logic. Encodings, worktree derivation, grouping, move-plan computation, and AppleScript generation are computed ONLY by
roundup.py. You never reconstruct them in your head or in shell — you call subcommands and read their JSON. - Preview before mutation. Every reorientation is shown via
reorient --dry-runand confirmed by the user before any file moves.--update-historystays OFF unless the user explicitly opts in. - Metadata only, never code. Reorientation moves only Claude session files within one config dir. It never touches worktrees, git state, or source, and never crosses config stores.
- Per-item reorient, global open. Reorientation is confirmed one session at a time (two sessions in a group may target different worktrees). Opening is a single global yes/no that launches every group.
- Opening is decoupled from reorientation. A launch works whether or not a session was reoriented, because each pane
cds to the session's real storage cwd.
Inputs
| Input | Required | Description |
|-------|----------|-------------|
| config dirs | Yes | Which Claude config stores to scan (~/.claude default; ~/.claude-work optional). |
| lookback window | Yes | How far back to consider sessions (ISO date → --since, or hours/days → --lookback-hours). |
| reorient decisions | Per-item | For each candidate: reorient to project subdir, to workspace root, or skip. |
| open decision | Global | Whether to relaunch all groups in Ghostty. |
Outputs
| Output | Type | Description | |--------|------|-------------| | grouping preview | Display | Per-group session list with resolved worktree, confidence, and current location. | | reoriented sessions | Side effect | Session metadata moved to the correct project dir (after dry-run + confirm). | | Ghostty windows | Side effect | One window per group, one split pane per session, each resuming its session. |
When to Use
- User wants to see every session belonging to one effort in one place.
- Sessions don't appear in the right interactive resume picker because their stored project dir doesn't match the worktree they were really worked in.
- User wants to bring a whole effort's sessions back up at once.
- NOT when code physically moved (that's
move-project). NOT to create worktrees (that'susing-git-worktrees).
Terminology
| Term | Meaning |
|------|---------|
| reorient | Move ONLY a session's Claude metadata files (<uuid>.jsonl + sibling <uuid>/ sidecar) within the SAME config dir, into the project dir of its correct worktree. It does NOT move code, never touches git/worktrees, and never crosses config dirs. Contrast move-project, which relocates sessions because the underlying code moved. |
| group | The set of sessions belonging to one effort (keyed by title prefix, corroborated by worktree). One Ghostty window per group. |
| config dir | A Claude Code config store root (~/.claude default, optionally ~/.claude-work). |
The Flow (exact sequence)
You run these gates in order. Do not skip the dry-run preview, and do not flip --update-history on without explicit opt-in.
ASK #1 — config dirs + lookback window
Use AskUserQuestion.
Config dirs (multi-select):
~/.claude(default, pre-selected)~/.claude-work
Pass each chosen dir as a repeated --config-dir flag.
Lookback window:
24h3 days(72h, default)7 daysCustom(free-form)
Parse a free-form custom answer into EXACTLY ONE flag (they are mutually exclusive):
- An ISO-8601 date/datetime (
2026-05-20,2026-05-20T09:00:00Z) →--since <ISO>. - A duration (
48,48h,7d→ 168) →--lookback-hours N.
Step 2 — build the plan
python3 roundup.py plan --json \
--config-dir ~/.claude [--config-dir ~/.claude-work] \
( --lookback-hours N | --since ISO )
Parse the JSON plan envelope: sessions (each enriched with resolved_worktree_dir, resolved_workspace, workspace_root_dir, resolve_confidence, resolve_signal, appears_running, encoded_cwd_current), groups, reorient_candidates, warnings. Save the plan to a temp file — reorient and launch consume it.
Step 3 — present the grouping (dry-run-style preview)
One section per group. For each session show: uuid, title, resolved worktree + resolve_confidence, and its current location (encoded_cwd_current). Surface every plan warning.
Group: ody-2957-styleseat (3 sessions)
uuid title resolved worktree conf running
0f3c.. ody-2957-styleseat-a worktrees/ODY-2957.../styleseat high no
9ab1.. ody-2957-styleseat-b worktrees/ODY-2957.../mobileweb high no
7b2c.. ody-2957-styleseat-c (UNRESOLVED — gitBranch=HEAD) - no
ASK #2 — reorientation, PER ITEM
For EACH uuid in reorient_candidates, ask an individual decision (one AskUserQuestion per candidate, or a multiSelect that still records one target per candidate). Sessions that are UNRESOLVED, already-correct, or running are NOT in reorient_candidates — do not offer them.
For each candidate, show its current project dir and offer:
- Repo subdir →
<resolved_worktree_dir>(suggested default) - Workspace root →
<workspace_root_dir>— offer this option ONLY when the plan marksworkspace_root_dirnon-null (main repos have none; suppress it for them) - Skip
Collect decisions into a JSON array:
[{"uuid": "0f3c...", "config_dir": "/Users/eek/.claude", "target": "repo_subdir"},
{"uuid": "9ab1...", "config_dir": "/Users/eek/.claude", "target": "workspace_root"},
{"uuid": "7b2c...", "config_dir": "/Users/eek/.claude", "target": "skip"}]
Dry-run FIRST, then live. Always preview before mutating:
python3 roundup.py reorient --plan <plan.json> --decisions <decisions.json> --dry-run --json
Show the exact planned moves (old project dir → new project dir, per item). Only after the user confirms the preview, run it for real. Always pass --summary-out <reorient-summary.json> so the launch step (ASK #3) can cd reoriented sessions to their NEW dir without a re-scan:
python3 roundup.py reorient --plan <plan.json> --decisions <decisions.json> \
--summary-out <reorient-summary.json> --json
Remember the <reorient-summary.json> path; ASK #3 needs it. --update-history stays OFF unless the user explicitly opts in. Note when offering it: the history rewrite is best-effort and backs up history.jsonl to a timestamped .backup.<stamp> first. Surface every skipped / collisions / rolled_back / warnings entry verbatim.
ASK #3 — open everything (single global yes/no)
Open all groups now? This launches one Ghostty window per group, one pane per session, each resuming its session with the launcher's standard resume command (
claude … --resume <uuid>, with the skip-permissions flag the helper already supplies).
On Yes:
-
If a live reorient ran THIS run (ASK #2 executed for real, producing
<reorient-summary.json>), pass it so sessions reoriented this run cd to their NEW dir:python3 roundup.py launch --plan <plan.json> --reorient-summary <reorient-summary.json> --json -
If NO reorient ran this run (or only a dry-run), omit the flag:
python3 roundup.py launch --plan <plan.json> --json
This opens one Ghostty window per group (panes = sessions). Each pane runs a command of the form (the skip-permissions flag, spelled --dangerously + -skip-permissions, is supplied by roundup.py — see build_pane_command):
cd <dir> && CLAUDE_CONFIG_DIR=<cfg> claude <skip-perms-flag> --resume <uuid>
Tell the user: the first launch triggers a one-time macOS Automation (TCC) permission prompt ("control Ghostty"); approve it once and subsequent runs are silent. Surface every per-session launch WARNING the helper emits (some sessions may not auto-resume).
Safety Invariants
- Reorient previews before it mutates. Always run
--dry-runand get confirmation first. - Never overwrites. A destination collision refuses that item with a warning.
- Skips running sessions. Re-checked live at move time (TOCTOU), not just at plan time.
--update-historydefault OFF. Only enabled on explicit opt-in; best-effort with timestamped backup.- Same config dir only. Reorient never crosses
~/.claude↔~/.claude-work. - Code is never touched. Reorient moves session metadata files only.
Known Limitations
- Resume needs the origin cwd to still exist. If the resolved/launch dir was deleted,
claude --resumewill fail until it is restored. The launch step warns per affected session. - Ghostty native AppleScript backend only (Ghostty >= 1.2.0; verified against the 1.3.1 scripting dictionary). The launch step drives Ghostty's native AppleScript verbs (
make new window,split … direction …,input text … to <terminal>,send key "enter" to <terminal>). The first launch triggers a one-time macOS Automation (Apple Events) permission prompt — not Accessibility — and there is no dependency on any Ghostty keybind. The native AppleScript API has been stable since 1.2.0; it is still a relatively new API and could change in a future major. Theguiand iTerm2 backends remain deferred;launchexits non-zero for any non-native / non-Ghostty combination. ~/.claude-worksessions resume correctly only because each pane command setsCLAUDE_CONFIG_DIRexplicitly. Cross-config moves are forbidden, so a~/.claude-worksession is warned about (not relocated) if its config dir is non-default.
CLI Reference
Subcommands: scan, plan, reorient, launch. Full details in roundup.py docstrings.
| Subcommand | Purpose | Key flags |
|------------|---------|-----------|
| scan | Discover sessions within the lookback window | --config-dir (repeatable), --since / --lookback-hours, --json, --out |
| plan | Derive worktrees + group; emit the plan envelope you drive prompts from | --config-dir, --in, --since / --lookback-hours, --json, --out |
| reorient | Preview or execute per-item moves | --plan, --decisions, --dry-run, --update-history (default OFF), --summary-out, --json |
| launch | Render + run the Ghostty AppleScript | --plan, --reorient-summary, --print-script, --json |
--since ISOand--lookback-hours Nare mutually exclusive; pass exactly one.--print-scriptonlaunchdumps the AppleScript without invokingosascript(debugging).
Common Mistakes
| Mistake | Fix |
|---------|-----|
| Computing encodings or groupings yourself | Delegate everything to roundup.py; it is deterministic and tested. |
| Running live reorient without the dry-run | Always preview first and get confirmation. |
| Offering reorientation for UNRESOLVED / running / already-correct sessions | Only iterate reorient_candidates; the helper has already filtered. |
| Offering the workspace-root target for a main repo | Offer it ONLY when workspace_root_dir is non-null. |
| Enabling --update-history by default | Default OFF; explicit opt-in only. |
| Treating "reorient" like move-project | Reorient moves metadata only; move-project is for moved code. |