Automate /close herdr tab teardown - #18
Merged
Merged
Conversation
- Inside a herdr session, /close now closes the finished task's herdr tab on top of the worktree/branch/task-file cleanup; outside herdr it is unchanged. The tab is found by cwd before the worktree is removed. - Scenario A (run from the main session): close the worktree tab directly. - Scenario B (run from inside the worktree tab): focus the main tab, then self-exit via a detached, delayed /exit that lands on the idle prompt after the turn ends, so Claude exits cleanly and its root-pane tab auto-closes. Falls back to a Ctrl+D prompt when injection is unavailable. - Ship a conditional SessionEnd hook (hooks/hooks.json) that closes only a per-pane-marked tab, as the backup for shell-hosted sessions. - Keep all herdr logic in the tested scripts/herdr-teardown.sh. - Verified live: `pane run "/exit"` and `send-keys ctrl+d` do not exit Claude's TUI; `send-text "/exit"` + Return does, and a root-pane exit auto-closes the tab. - Bump work-system to 1.4.1; document in the work-system + root READMEs and curate herdr-close-automation knowledge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TnbQUD8guQaB8UoX52iwiK
Address the xhigh code-review findings on the Scenario-B self-close path:
- Decide self-close by pane id (own-tab), not the possibly-empty $HERDR_TAB_ID,
so an unset tab id can no longer misclassify a self-close into a mid-turn
`close-tab` against the live session's own tab. If the own tab can't be
resolved, skip the auto teardown instead of guessing.
- Match the worktree/main cwd by realpath on both sides, so a symlinked path
(macOS /tmp→/private/tmp, symlinked /Users) no longer silently skips teardown.
- main-tab excludes the dying tab from its --first fallback, so focus never
lands on the tab that is about to self-close.
- self-exit polls the pane until it leaves `working` (turn ended) before
injecting, replacing the fixed 4s timer that could fire /exit mid-turn; it
passes args positionally to an internal handler (no bash -c double-eval).
- inject-exit dismisses the slash-command menu if Return fails after the text
was typed, so a half-delivered exit doesn't leave the TUI stuck.
- Marker: fixed $HOME/.cache (not $XDG_CACHE_HOME, which can diverge between the
/close shell and the hook env), sanitized pane id, and a timestamp + TTL so a
stale marker is dropped without closing an unrelated reused tab.
- check-structure.py also scans hooks/*.json for ${CLAUDE_PLUGIN_ROOT} refs, so
renaming the script can't break the SessionEnd hook with CI still green.
- Gate step 12 on completed cleanup; fix the README "no state file" wording;
refresh the knowledge entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TnbQUD8guQaB8UoX52iwiK
Tighten the Scenario-B self-close path against the failure modes the first
hardening pass introduced or left open:
- __delayed-inject now injects ONLY on a confirmed idle/done status. A transient
`herdr pane list` failure yields empty output, which is retried, not mistaken
for idle (which would have injected /exit mid-turn — the exact invariant the
path promises). A vanished pane or a never-idle timeout injects nothing.
- extract_tab no longer false-matches: an empty/whitespace target (realpath("")
resolves to the process cwd → would match the main-repo pane) never matches,
and an all-slashes path stays root instead of collapsing to "". lookup_tab
rejects an empty target. --exclude is honored on the primary cwd match too, so
main-tab can't return the dying tab when its cwd matches the main repo.
- on-session-end closes only a verifiably fresh marker: a stamp/now of 0 (date
unavailable) or a negative/over-TTL age is dropped WITHOUT closing, instead of
being treated as permanently fresh. Close now runs before the marker rm, with
one retry, so a transient hiccup doesn't silently leak the tab.
- marker_dir fails closed when $HOME is empty; empty-pid guards in the pane
matchers; arm-self-close creates the marker dir from the resolved path.
- /close reports when it's in herdr but couldn't locate the tab by cwd, instead
of silently implying the tab was handled.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TnbQUD8guQaB8UoX52iwiK
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
Swarm loop round 3 — 3 findings, all in the probe. Rather than patch a probe that had a security jail bolted on (rounds 1-3 all fixed jail-adjacent bugs), this removes the jail: a readiness check passes no untrusted diff, so it never needed one — the sibling `codex login status` in ready_check is unjailed too. - Run `grok models` directly, not through sandboxed(). This roots out the round-3 CRITICAL (#18): the jail's `_init_sandbox` builds its deny profile with python3, so routing the probe through it made python3 a hard dependency of the formerly-local `ready`/`list` paths — and a missing python3 then misreported as "grok models failed (rc=65)". No jail, no python3 dep, honest reasons. - Bound with `timeout -k 3` (#17): plain `timeout` only SIGTERMs, so a grok that ignores SIGTERM (or forks a stdout-inheriting child) kept the command substitution blocking past the deadline — the "must never hang" hole. `-k` SIGKILLs after a grace period. rc 124 (SIGTERM at the deadline) reports as "timed out"; rc 137 (SIGKILL) reports as "killed" — almost always our own `-k`, but an OOM/external kill shares the code, so the message doesn't assert a timeout that may not have happened. - Take only the FIRST id per bullet line (#19): scanning the whole line also matched a grok-4.5 mentioned in prose on another model's line ("* grok-5 (successor to grok-4.5)"), reporting a retired model as offered. - Fold _build_jail back into sandboxed(). It existed ONLY to share the jail with the probe; with the probe unjailed, the split — and the shared-warning (#12) and per-backend-memo (#15) fixes it forced last round — are moot. sandboxed() is back to its pre-split shape; _init_sandbox keeps the backend key (harmless, strictly more correct). - Fix a stale swarm-review.js input-contract comment ("subset of the three" → codex, grok). - Docs (header/README/CHANGELOG/knowledge) drop the jail claim. The knowledge entry records the real lesson: five rounds of patching a jailed probe ended the moment the jail was deleted — a feature added to be safe that generates every round's bugs is a shape problem; cut it, don't harden it. The composer removal (the PR's subject) drew zero findings across all five rounds. Verified: live grok ready; SIGTERM-ignoring grok bounded by -k in ~5s, reported as killed; SIGTERM-obeying grok reported as timed out; model-gone → honest hint, no warning; probe rc=1 → warn + trust-auth; NO python3 invoked on the ready path; review path still fully jailed (sandbox-exec + AWS/GH secret stripping); end-to-end review returns a schema-valid finding. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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
/closenow tears down the finished task's herdr tab on top of the usual worktree/branch/task-file cleanup. Outside herdr it is unchanged./kickoffherdr automation (Automate /kickoff worktree launch inside herdr #17): kickoff opens the tab, close tears it down — sameHERDR_*env + cwd-matching conventions.scripts/herdr-teardown.sh) plus a plugin-shippedSessionEndhook; the SKILL.md stays thin.Changes
skills/close/SKILL.md— step 7 captures the worktree tab by cwd before removal (+ aSELFdiscriminator); new step 12 runs Scenario A (close the worktree tab directly from the main session) or Scenario B (self-close from inside the tab).scripts/herdr-teardown.sh(new) —worktree-tab/main-tab(cwd lookup),close-tab/focus-tab,inject-exit,self-exit(detached delayed/exit),arm-self-close/on-session-end(marker + hook handler).hooks/hooks.json(new) — conditionalSessionEndhook closing only a per-pane-marked tab; ships with the plugin (no settings.json injection).features/herdr-close-automation.mdcurated.1.4.0 → 1.4.1(plugin.json + marketplace.json).Readiness
herdr-close-automation.md+ index curatedcheck-structure.py: 0 errorsTest plan
send-text "/exit"+Returncleanly exits Claude's TUI;pane run "/exit"andsend-keys ctrl+ddo not; a root-pane clean exit auto-closes the tab; detachedself-exitexits the idle session after the turn.SessionEndhook firing is provable only after installing work-system 1.4.1 (cache currently has 1.3.1 withouthooks.json); the hook handler is unit-tested./closefrom the main session closes a different worktree tab (Scenario A)./closeoutside herdr behaves exactly as before.🤖 Generated with Claude Code