Add /continue reopen mode to recover /exit-closed herdr tabs - #22
Merged
Conversation
A kickoff tab runs Claude as its root pane, so a bare /exit (even to just restart Claude Code) closes the whole tab. Recover it with `/continue <task>` from the main session. - herdr-launch.sh: split into `launch` (kickoff, argv-exec, unchanged) and `resume` (tab create + `pane run "claude -c"` inside a SHELL pane, then focus). Same pane=/tab=/moved= contract. Because resume runs Claude in a shell pane, a later /exit drops back to the shell and the tab survives. - continue/SKILL.md: branch by location — worktree = in-session resume (unchanged); main session + <task> = reopen via the helper; outside herdr = manual `cd <worktree> && claude -c` block. Update description. - kickoff/SKILL.md: call the helper's new `launch` subcommand. - Decisions: keep kickoff argv (race-freedom + /close teardown depend on root-pane Claude); resume uses `claude -c` (worktree cwd is 1:1 with the task session, so no session-id stash needed). - Bump work-system 1.5.1 -> 1.6.0; update READMEs + knowledge entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AQaQpf2Y8hn2aUV6SuLfFF
- Guard against a duplicate session: resume now reuses herdr-teardown.sh's worktree-tab (cwd→tab) lookup and, if a tab is already open at the worktree, just focuses it (reused=yes) instead of starting a second `claude -c` on the same working tree. - Report resumed=no when the `pane run "claude -c"` send fails, so the skill never claims a resume that didn't happen; SKILL branches on reused/resumed and softens the success wording (adopted worktrees may have no session to resume). - Route `/continue <other-task>` from inside a worktree to reopen mode for that task instead of silently ignoring the argument. - Parse the tab-create response in one python3 pass (was two spawns). - Restore the "pick up where I left off" trigger phrase; tighten the description to stay within budget. - Trim the over-long knowledge index line back to a terse pointer. - Document the resume-vs-/close teardown asymmetry (shell-launched Claude may not be a registered agent; /close falls back to its manual-close line) as a known, gracefully-degrading limitation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AQaQpf2Y8hn2aUV6SuLfFF
Address xhigh review findings on the duplicate-session guard added last round: - Retry the worktree-tab lookup (~3x): it returns empty for both "no tab" and a transiently empty `herdr pane list` (e.g. just after a herdr restart), so a single empty read failed open and could still duplicate. - Don't assert a live resume on reuse: a cwd match can't tell a live Claude from a bare shell that survived a prior /exit, so the reuse branch emits `resumed=` (empty) and the skill tells the user to run `claude -c` if the focused tab is just a shell — no false "resumed". - Parse the tab-create response pipe-delimited (`<pane>|<tab>`) so an empty pane id can't be mis-read as the tab id (would have targeted a tab id as a pane id). - Emit `focused=<yes|no>`; skill no longer claims a focus that failed, and reorders its branches so an empty `tab=` with resumed=yes still gets the manual-focus hint. - Document the workspace-scoped lookup as an accepted limitation (a same-worktree tab in another herdr workspace is out of scope), consistent with /kickoff and /close. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AQaQpf2Y8hn2aUV6SuLfFF
Address xhigh round-2 findings; the duplicate-session guard is redesigned to fail closed instead of open: - Add `herdr-teardown.sh worktree-tab-state`: a tri-state cwd->tab lookup returning <tab>|none|unverified. resume now creates only on `none` (populated pane list, no match), FAILS CLOSED on `unverified` (herdr unreachable or an empty/repopulating pane list) by exiting 1 so the skill shows the manual block — a plain empty-return guard fails open and could still duplicate. This also drops the retry-with-sleep, so the common no-tab path pays no latency. - Search all workspaces (empty workspace arg) so a still-live tab for the same worktree in a different herdr workspace is found; the new tab is still created in $HERDR_WORKSPACE_ID. - Fix a dispatch regression: inside a worktree, an argument that doesn't resolve to another task's worktree (typo/alias) now resumes the current task in-session instead of refusing with "no worktree — /kickoff". - Header said "two keys", lists three (reused/resumed/focused). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AQaQpf2Y8hn2aUV6SuLfFF
Round-3 review returned only PLAUSIBLE findings; two were already- documented, gracefully-degrading tradeoffs (the shell-startup keystroke race and reopened tabs not being registered agents). Addressed: - Remove the duplicated norm()/cwd-match logic: worktree-tab-state now reuses the canonical extract_tab matcher plus a trivial extract_panes_populated check, so path/realpath matching is defined once and the reopen guard and /close's worktree-tab lookup can't drift. Guard an empty target as unverified (fail closed). - Word the reopen success message as "Sent `claude -c`", not "is running" — the helper delivers the keystrokes but can't confirm Claude came up (shell-startup race); tell the user to re-run it if they land on a bare shell. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AQaQpf2Y8hn2aUV6SuLfFF
Round-4 findings centered on the mode-selection prose (which had drifted across edits). Make it mechanical: - Normalize the argument through task-status.sh before comparing/looking up the worktree, so a `task/`-prefixed or aliased name matches the prefix-stripped worktree dir name instead of misrouting to in-session resume of the wrong task. - Compare the resolved arg name to the current worktree's task name first, so `/continue <current-task>` from its own worktree resumes in place rather than no-op-focusing the tab you're already in. - Mode-aware usage string: resume no longer advertises [session-name] (only launch takes one). - Knowledge: resume is called from continue's reopen path (main session, OR a different task's name from inside a worktree) — not "main-session only"; document the exact-cwd match gap (a shell wandered into a subdir isn't matched) as an accepted limit of the shared matcher. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AQaQpf2Y8hn2aUV6SuLfFF
Final review-loop round. Fixes the four surviving guard-path findings:
- unverified now surfaces as `blocked=unverified` (exit 0), and the skill
tells the user to CHECK herdr for an already-open tab before reopening
by hand. Previously the fail-closed exit fell through to the plain
manual block ("cd && claude -c") with no cue, which could recreate the
duplicate the guard exists to prevent.
- Collapse worktree-tab-state into ONE python pass (was two: populated
check + extract_tab), removing the double parse.
- The single tri-state extractor now fails closed (`unverified`) on a
malformed/errored pane list AND when a pane sits in a SUBDIRECTORY of
the worktree (a tab may have wandered in) — both previously read as
`none` and could duplicate. norm() mirrors extract_tab (kept in sync by
comment) since /close's exact-match matcher must not inherit the
subtree/tri-state behavior.
Unit-tested the extractor across exact/none/subtree/empty/malformed/
sibling cwd cases.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQaQpf2Y8hn2aUV6SuLfFF
Round 6 verified the round-5 fixes and caught a regression they introduced. Corrections: - Revert the subtree-cwd matching: it was deterministically failing closed whenever ANY unrelated pane sat in a worktree subdir (e.g. a shell in <worktree>/logs), permanently blocking auto-reopen. Back to exact-match; the wandered-own-tab gap it targeted is re-documented as an accepted narrow limitation. - Fail closed on a tab pane whose cwd is empty/unreadable: it was being skipped and falling through to `none` (fail open -> possible duplicate claude -c). Now yields `unverified`. - blocked outcome now prints a lone `blocked=unverified`, matching the header contract (was three keys). - Factor norm() into a shared prelude string concatenated into both extract_tab and extract_tab_state, so path normalization is defined once (the two can no longer drift). - Document that reopening a different task from inside a worktree can place its tab in the current workspace, which a later /close may not locate (falls back to its manual-close line; no data loss). Unit-tested both extractors across exact/none/subtree/empty/null/ non-string/no-tab cwd cases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AQaQpf2Y8hn2aUV6SuLfFF
Round 7 (verification of round 6). Its top finding was self-inflicted: the round-6 subtree revert changed the code but left comments claiming subdir -> unverified. Fixes: - Sweep the stale subtree=fail-closed comments (herdr-launch header + teardown) to match the code: subdir cwd -> `none` (accepted narrow gap), documented as such. - extract_tab_state: check cwd BEFORE tab_id, so a worktree-cwd pane momentarily missing its tab_id fails closed (`unverified`) instead of being skipped -> `none` (a fail-open duplicate risk). - resume sends `cd <worktree> && claude -c` (shell-quoted): the pane is created with --cwd, but an rc that drifts the cwd (direnv/zoxide) would make `claude -c` resume the wrong task's session. Re-anchor first. - Factor the herdr-down/empty-list guard chain into a `pane_query` helper shared by tab_status() and worktree-tab-state (were duplicated). - /continue reopen step 1 reuses the ARG_TASK the dispatcher already resolved instead of re-running task-status.sh. - Correct the "all workspaces ... globally unique" overclaim: the search spans the current herdr SERVER only (a separate server is invisible); document that and the unreadable-cwd over-block as accepted fail-safe. Re-unit-tested both extractors (8 + 3 cases); structure check green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AQaQpf2Y8hn2aUV6SuLfFF
- continue/SKILL.md: correct the blocked=unverified cause list — a subdir pane now yields `none` (creates), not `unverified` (a stale claim the R7 sweep missed in the skill, matching the exact-match/subtree-revert design) - continue/SKILL.md: the resumed=yes template no longer tells a never-hosted worktree to re-run `claude -c` (which errors); start fresh `claude` instead - continue/SKILL.md: on reuse focused=no, cue reopening by hand if the tab closed between snapshot and focus (no auto-create — that risks a duplicate) - continue/SKILL.md: defer MAIN_REPO off the hot in-session-resume path; resolve it only in the linked+switch branch that needs it - herdr-launch.sh: close an orphaned tab (parsed tab id, no pane id) before bailing, so a schema-drift response can't leak a blank tab on every resume - Knowledge: note the unscoped-pane-list-spans-workspaces assumption + the orphan-close; bash -n + structure check green Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AQaQpf2Y8hn2aUV6SuLfFF
gering
added a commit
that referenced
this pull request
Jul 16, 2026
Second dogfooding round at the deepest profile (/swarm:review --max --fix: per-lens split live — gate pruned style, 10 xhigh finders + codex gpt-5.6-sol@xhigh + grok-4.5 + composer; 24 findings, 10 consensus). Fixes the agreed findings: - Never auto-accept an all-untagged consensus cluster: no tagged lens backs it, so it is verified like a solo. Verify/auto-accept now derive from ONE needsVerify predicate (structural exactly-once partition); kind vote in clearer every-form (#1, consensus critical; #21, #22) - Design verifier sees the finding's recommendation — the proposal the applicability rubric actually tests — and carries an escape hatch: a genuine defect mis-filed under a design lens is not refuted away (#12, #3); "solo" dropped from verifier prompts (#4) - Untagged findings from multi-lens cluster finders fall back to 'unspecified' (safe defect bucket), not lenses[0] (#7); merge-agent free-text lens validated, majority-member fallback (#9) - Improvement invitation scoped to design finder units — defect-lens finders stay defect-only (#8); merge prompt clusters by issue, not only defect (#13); schema descriptions generalized for design findings (#14) - LENS_BRIEF startup assertion (#6); gate prompt interpolates LENS_CLUSTERS.design (#19); new test_lens_sync.py guards all lens mirrors: SKILL HDR prompt, LENS_BRIEF, pr-post DESIGN_LENSES (#5) - Workflow assigns stable finding num (defects first, shared sequence); presenter/pr-post render it verbatim (#20) - pr-post.py: design lens is the backup kind signal when the handoff drops kind (explicit defect still wins); single-pass partition (#17, #24) - Balance: REFUTED is its own segment (refuted ⊄ solo since design consensus can be refuted) (#2); LOCKED design-table column precedence in --loop rounds clarified (#11); "verifies solos" doc sweep across manifests/README/knowledge (#4); cluster failure-isolation trade-off documented (#10); knowledge index line trimmed (#15) Declined: #16 (JS test harness for sandbox code; lens-sync test covers the drift class), #18 (gate-fail under --max runs all lenses — the documented never-silently-narrower degrade), #23 (micro-opt vs readability). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126cGxsBYxqgEFH4AcpriNk
gering
added a commit
that referenced
this pull request
Jul 17, 2026
Second dogfooding round at the deepest profile (/swarm:review --max --fix: per-lens split live — gate pruned style, 10 xhigh finders + codex gpt-5.6-sol@xhigh + grok-4.5 + composer; 24 findings, 10 consensus). Fixes the agreed findings: - Never auto-accept an all-untagged consensus cluster: no tagged lens backs it, so it is verified like a solo. Verify/auto-accept now derive from ONE needsVerify predicate (structural exactly-once partition); kind vote in clearer every-form (#1, consensus critical; #21, #22) - Design verifier sees the finding's recommendation — the proposal the applicability rubric actually tests — and carries an escape hatch: a genuine defect mis-filed under a design lens is not refuted away (#12, #3); "solo" dropped from verifier prompts (#4) - Untagged findings from multi-lens cluster finders fall back to 'unspecified' (safe defect bucket), not lenses[0] (#7); merge-agent free-text lens validated, majority-member fallback (#9) - Improvement invitation scoped to design finder units — defect-lens finders stay defect-only (#8); merge prompt clusters by issue, not only defect (#13); schema descriptions generalized for design findings (#14) - LENS_BRIEF startup assertion (#6); gate prompt interpolates LENS_CLUSTERS.design (#19); new test_lens_sync.py guards all lens mirrors: SKILL HDR prompt, LENS_BRIEF, pr-post DESIGN_LENSES (#5) - Workflow assigns stable finding num (defects first, shared sequence); presenter/pr-post render it verbatim (#20) - pr-post.py: design lens is the backup kind signal when the handoff drops kind (explicit defect still wins); single-pass partition (#17, #24) - Balance: REFUTED is its own segment (refuted ⊄ solo since design consensus can be refuted) (#2); LOCKED design-table column precedence in --loop rounds clarified (#11); "verifies solos" doc sweep across manifests/README/knowledge (#4); cluster failure-isolation trade-off documented (#10); knowledge index line trimmed (#15) Declined: #16 (JS test harness for sandbox code; lens-sync test covers the drift class), #18 (gate-fail under --max runs all lenses — the documented never-silently-narrower degrade), #23 (micro-opt vs readability). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126cGxsBYxqgEFH4AcpriNk
gering
added a commit
that referenced
this pull request
Jul 17, 2026
Second dogfooding round at the deepest profile (/swarm:review --max --fix: per-lens split live — gate pruned style, 10 xhigh finders + codex gpt-5.6-sol@xhigh + grok-4.5 + composer; 24 findings, 10 consensus). Fixes the agreed findings: - Never auto-accept an all-untagged consensus cluster: no tagged lens backs it, so it is verified like a solo. Verify/auto-accept now derive from ONE needsVerify predicate (structural exactly-once partition); kind vote in clearer every-form (#1, consensus critical; #21, #22) - Design verifier sees the finding's recommendation — the proposal the applicability rubric actually tests — and carries an escape hatch: a genuine defect mis-filed under a design lens is not refuted away (#12, #3); "solo" dropped from verifier prompts (#4) - Untagged findings from multi-lens cluster finders fall back to 'unspecified' (safe defect bucket), not lenses[0] (#7); merge-agent free-text lens validated, majority-member fallback (#9) - Improvement invitation scoped to design finder units — defect-lens finders stay defect-only (#8); merge prompt clusters by issue, not only defect (#13); schema descriptions generalized for design findings (#14) - LENS_BRIEF startup assertion (#6); gate prompt interpolates LENS_CLUSTERS.design (#19); new test_lens_sync.py guards all lens mirrors: SKILL HDR prompt, LENS_BRIEF, pr-post DESIGN_LENSES (#5) - Workflow assigns stable finding num (defects first, shared sequence); presenter/pr-post render it verbatim (#20) - pr-post.py: design lens is the backup kind signal when the handoff drops kind (explicit defect still wins); single-pass partition (#17, #24) - Balance: REFUTED is its own segment (refuted ⊄ solo since design consensus can be refuted) (#2); LOCKED design-table column precedence in --loop rounds clarified (#11); "verifies solos" doc sweep across manifests/README/knowledge (#4); cluster failure-isolation trade-off documented (#10); knowledge index line trimmed (#15) Declined: #16 (JS test harness for sandbox code; lens-sync test covers the drift class), #18 (gate-fail under --max runs all lenses — the documented never-silently-narrower degrade), #23 (micro-opt vs readability). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126cGxsBYxqgEFH4AcpriNk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/exit(even one only meant to restart Claude Code) closes the whole tab — the worktree and resumable session persist, but you lose your place./continue <task>from the main session reopens the task's herdr tab at its worktree and resumes the existing Claude session (claude -c), then focuses it./exit: Claude runs inside a shell pane, so a later/exitdrops back to the shell and the tab survives.Changes
scripts/herdr-launch.sh— split into two subcommands sharing one precondition/parse/output contract:launch(kickoff, argv-exec, root-pane, unchanged) andresume(tab create+pane run "claude -c"in a shell pane, thentab focus). Samepane=/tab=/moved=contract.skills/continue/SKILL.md— branch by location: inside a worktree = in-session resume (unchanged); main session +<task>= reopen via the helper; outside herdr = manualcd <worktree> && claude -cblock. Description updated for the new mode.skills/kickoff/SKILL.md— call the helper's newlaunchsubcommand./close's teardown depends on the root-pane model); resume usesclaude -c(worktree cwd is a 1:1 proxy for the task session, so no session-id stash is needed). A race-free prevention variant is documented as deferred pending live herdr verification.Readiness
bash -n, JSON, version sync, refs, description budget)mainTest plan
/exita kickoff tab, then/continue <task>from the main session → tab reopens at the worktree, session resumes, tab is focused./exitagain → drops to the shell, tab survives./continue <task>prints the manualcd <worktree> && claude -cblock./continuestill does in-session resume unchanged.🤖 Generated with Claude Code