feat: allow session-only delete on the main worktree - #23

Merged
xico42 merged 1 commit into
mainfrom
feat/delete-main-session
Jul 18, 2026
Merged

feat: allow session-only delete on the main worktree#23
xico42 merged 1 commit into
mainfrom
feat/delete-main-session

Conversation

@xico42

Copy link
Copy Markdown
Owner

Summary

The dashboard hard-blocked d on the main worktree with a flat "cannot delete the main worktree", so the only way to kill that worktree's agent or shell sessions was to leave the TUI and run ch delete session by hand. On the CLI, ch delete worktree <project> main fell through to git, which failed removing the clone directory and surfaced a raw porcelain error.

This lets the TUI's d key kill the main worktree's sessions (leaving the worktree in place) and gives ch delete worktree main a clean refusal.

What changed

  • Domain guard. The main worktree is the clone directory itself, not a checkout under __worktrees/, so tearing it down never makes sense. Teardown now refuses it with a new herd.ErrMainWorktree sentinel, checked the moment the clone dir is known — before any session is stopped or any git command runs.
  • One error vocabulary. Both front ends translate the sentinel: the CLI names the worktree and points at ch delete session; the TUI shows a concise line.
  • TUI.d no longer refuses the main worktree outright — it only declines when there are no sessions. With a session present it opens the confirm menu in a main-worktree mode that offers session kills only and never lists worktree removal, so the destructive option is unreachable by construction. A new deleteAllSessions action stops every session via StopOpts{All: true} and leaves the worktree in place.

Verification

make check green — 628 unit tests, integration tests, lint (0 issues), build; coverage 85.4% (≥ 80%). Includes a new integration test asserting Teardown refuses the main worktree for both --force values and leaves the clone dir on disk.

🤖 Generated with Claude Code

The dashboard hard-blocked `d` on the main worktree with a flat "cannot
delete the main worktree", so the only way to kill that worktree's agent
or shell sessions was to leave the TUI and run `ch delete session` by
hand. On the CLI, `ch delete worktree <project> main` fell straight
through to git, which failed trying to remove the clone directory and
surfaced a raw porcelain error instead of an explanation.
The main worktree is the clone directory itself, not a checkout under
__worktrees/, so tearing it down never makes sense. Teardown now refuses
it with a new ErrMainWorktree sentinel, checked the moment the clone dir
is known — before any session is stopped or any git command runs — so a
refusal can never leave sessions half-killed or the tree half-removed.
Both front ends translate the sentinel through the one error vocabulary:
the CLI names the worktree and points the user at `ch delete session`,
and the TUI shows a concise line.
In the dashboard, `d` no longer refuses the main worktree outright — it
only declines when there are no sessions to act on. With a session
present it opens the confirm menu in a main-worktree mode that offers
session kills only and never lists worktree removal, so the destructive
option is unreachable by construction rather than guarded after the
fact. A new deleteAllSessions action stops every session for the ref via
StopOpts{All: true} and leaves the worktree in place.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xico42
xico42 merged commit 9ee9111 into mainJul 18, 2026
7 checks passed
@xico42
xico42 deleted the feat/delete-main-session branch July 18, 2026 18:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@xico42
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat: allow session-only delete on the main worktree - #23

Merged
xico42 merged 1 commit into
mainfrom
feat/delete-main-session
Jul 18, 2026
Merged

feat: allow session-only delete on the main worktree#23
xico42 merged 1 commit into
mainfrom
feat/delete-main-session

Conversation

@xico42

Copy link
Copy Markdown
Owner

Summary

The dashboard hard-blocked d on the main worktree with a flat "cannot delete the main worktree", so the only way to kill that worktree's agent or shell sessions was to leave the TUI and run ch delete session by hand. On the CLI, ch delete worktree <project> main fell through to git, which failed removing the clone directory and surfaced a raw porcelain error.

This lets the TUI's d key kill the main worktree's sessions (leaving the worktree in place) and gives ch delete worktree main a clean refusal.

What changed

  • Domain guard. The main worktree is the clone directory itself, not a checkout under __worktrees/, so tearing it down never makes sense. Teardown now refuses it with a new herd.ErrMainWorktree sentinel, checked the moment the clone dir is known — before any session is stopped or any git command runs.
  • One error vocabulary. Both front ends translate the sentinel: the CLI names the worktree and points at ch delete session; the TUI shows a concise line.
  • TUI.d no longer refuses the main worktree outright — it only declines when there are no sessions. With a session present it opens the confirm menu in a main-worktree mode that offers session kills only and never lists worktree removal, so the destructive option is unreachable by construction. A new deleteAllSessions action stops every session via StopOpts{All: true} and leaves the worktree in place.

Verification

make check green — 628 unit tests, integration tests, lint (0 issues), build; coverage 85.4% (≥ 80%). Includes a new integration test asserting Teardown refuses the main worktree for both --force values and leaves the clone dir on disk.

🤖 Generated with Claude Code

The dashboard hard-blocked `d` on the main worktree with a flat "cannot
delete the main worktree", so the only way to kill that worktree's agent
or shell sessions was to leave the TUI and run `ch delete session` by
hand. On the CLI, `ch delete worktree <project> main` fell straight
through to git, which failed trying to remove the clone directory and
surfaced a raw porcelain error instead of an explanation.
The main worktree is the clone directory itself, not a checkout under
__worktrees/, so tearing it down never makes sense. Teardown now refuses
it with a new ErrMainWorktree sentinel, checked the moment the clone dir
is known — before any session is stopped or any git command runs — so a
refusal can never leave sessions half-killed or the tree half-removed.
Both front ends translate the sentinel through the one error vocabulary:
the CLI names the worktree and points the user at `ch delete session`,
and the TUI shows a concise line.
In the dashboard, `d` no longer refuses the main worktree outright — it
only declines when there are no sessions to act on. With a session
present it opens the confirm menu in a main-worktree mode that offers
session kills only and never lists worktree removal, so the destructive
option is unreachable by construction rather than guarded after the
fact. A new deleteAllSessions action stops every session for the ref via
StopOpts{All: true} and leaves the worktree in place.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xico42
xico42 merged commit 9ee9111 into mainJul 18, 2026
7 checks passed
@xico42
xico42 deleted the feat/delete-main-session branch July 18, 2026 18:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@xico42
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: allow session-only delete on the main worktree - #23

Merged
xico42 merged 1 commit into
mainfrom
feat/delete-main-session
Jul 18, 2026
Merged

feat: allow session-only delete on the main worktree#23
xico42 merged 1 commit into
mainfrom
feat/delete-main-session

Conversation

@xico42

Copy link
Copy Markdown
Owner

Summary

The dashboard hard-blocked d on the main worktree with a flat "cannot delete the main worktree", so the only way to kill that worktree's agent or shell sessions was to leave the TUI and run ch delete session by hand. On the CLI, ch delete worktree <project> main fell through to git, which failed removing the clone directory and surfaced a raw porcelain error.

This lets the TUI's d key kill the main worktree's sessions (leaving the worktree in place) and gives ch delete worktree main a clean refusal.

What changed

  • Domain guard. The main worktree is the clone directory itself, not a checkout under __worktrees/, so tearing it down never makes sense. Teardown now refuses it with a new herd.ErrMainWorktree sentinel, checked the moment the clone dir is known — before any session is stopped or any git command runs.
  • One error vocabulary. Both front ends translate the sentinel: the CLI names the worktree and points at ch delete session; the TUI shows a concise line.
  • TUI.d no longer refuses the main worktree outright — it only declines when there are no sessions. With a session present it opens the confirm menu in a main-worktree mode that offers session kills only and never lists worktree removal, so the destructive option is unreachable by construction. A new deleteAllSessions action stops every session via StopOpts{All: true} and leaves the worktree in place.

Verification

make check green — 628 unit tests, integration tests, lint (0 issues), build; coverage 85.4% (≥ 80%). Includes a new integration test asserting Teardown refuses the main worktree for both --force values and leaves the clone dir on disk.

🤖 Generated with Claude Code

The dashboard hard-blocked `d` on the main worktree with a flat "cannot
delete the main worktree", so the only way to kill that worktree's agent
or shell sessions was to leave the TUI and run `ch delete session` by
hand. On the CLI, `ch delete worktree <project> main` fell straight
through to git, which failed trying to remove the clone directory and
surfaced a raw porcelain error instead of an explanation.
The main worktree is the clone directory itself, not a checkout under
__worktrees/, so tearing it down never makes sense. Teardown now refuses
it with a new ErrMainWorktree sentinel, checked the moment the clone dir
is known — before any session is stopped or any git command runs — so a
refusal can never leave sessions half-killed or the tree half-removed.
Both front ends translate the sentinel through the one error vocabulary:
the CLI names the worktree and points the user at `ch delete session`,
and the TUI shows a concise line.
In the dashboard, `d` no longer refuses the main worktree outright — it
only declines when there are no sessions to act on. With a session
present it opens the confirm menu in a main-worktree mode that offers
session kills only and never lists worktree removal, so the destructive
option is unreachable by construction rather than guarded after the
fact. A new deleteAllSessions action stops every session for the ref via
StopOpts{All: true} and leaves the worktree in place.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xico42
xico42 merged commit 9ee9111 into mainJul 18, 2026
7 checks passed
@xico42
xico42 deleted the feat/delete-main-session branch July 18, 2026 18:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@xico42
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: allow session-only delete on the main worktree - #23

Merged
xico42 merged 1 commit into
mainfrom
feat/delete-main-session
Jul 18, 2026
Merged

feat: allow session-only delete on the main worktree#23
xico42 merged 1 commit into
mainfrom
feat/delete-main-session

Conversation

@xico42

Copy link
Copy Markdown
Owner

Summary

The dashboard hard-blocked d on the main worktree with a flat "cannot delete the main worktree", so the only way to kill that worktree's agent or shell sessions was to leave the TUI and run ch delete session by hand. On the CLI, ch delete worktree <project> main fell through to git, which failed removing the clone directory and surfaced a raw porcelain error.

This lets the TUI's d key kill the main worktree's sessions (leaving the worktree in place) and gives ch delete worktree main a clean refusal.

What changed

  • Domain guard. The main worktree is the clone directory itself, not a checkout under __worktrees/, so tearing it down never makes sense. Teardown now refuses it with a new herd.ErrMainWorktree sentinel, checked the moment the clone dir is known — before any session is stopped or any git command runs.
  • One error vocabulary. Both front ends translate the sentinel: the CLI names the worktree and points at ch delete session; the TUI shows a concise line.
  • TUI.d no longer refuses the main worktree outright — it only declines when there are no sessions. With a session present it opens the confirm menu in a main-worktree mode that offers session kills only and never lists worktree removal, so the destructive option is unreachable by construction. A new deleteAllSessions action stops every session via StopOpts{All: true} and leaves the worktree in place.

Verification

make check green — 628 unit tests, integration tests, lint (0 issues), build; coverage 85.4% (≥ 80%). Includes a new integration test asserting Teardown refuses the main worktree for both --force values and leaves the clone dir on disk.

🤖 Generated with Claude Code

The dashboard hard-blocked `d` on the main worktree with a flat "cannot
delete the main worktree", so the only way to kill that worktree's agent
or shell sessions was to leave the TUI and run `ch delete session` by
hand. On the CLI, `ch delete worktree <project> main` fell straight
through to git, which failed trying to remove the clone directory and
surfaced a raw porcelain error instead of an explanation.
The main worktree is the clone directory itself, not a checkout under
__worktrees/, so tearing it down never makes sense. Teardown now refuses
it with a new ErrMainWorktree sentinel, checked the moment the clone dir
is known — before any session is stopped or any git command runs — so a
refusal can never leave sessions half-killed or the tree half-removed.
Both front ends translate the sentinel through the one error vocabulary:
the CLI names the worktree and points the user at `ch delete session`,
and the TUI shows a concise line.
In the dashboard, `d` no longer refuses the main worktree outright — it
only declines when there are no sessions to act on. With a session
present it opens the confirm menu in a main-worktree mode that offers
session kills only and never lists worktree removal, so the destructive
option is unreachable by construction rather than guarded after the
fact. A new deleteAllSessions action stops every session for the ref via
StopOpts{All: true} and leaves the worktree in place.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xico42
xico42 merged commit 9ee9111 into mainJul 18, 2026
7 checks passed
@xico42
xico42 deleted the feat/delete-main-session branch July 18, 2026 18:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@xico42
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat: allow session-only delete on the main worktree - #23

Merged
xico42 merged 1 commit into
mainfrom
feat/delete-main-session
Jul 18, 2026
Merged

feat: allow session-only delete on the main worktree#23
xico42 merged 1 commit into
mainfrom
feat/delete-main-session

Conversation

@xico42

Copy link
Copy Markdown
Owner

Summary

The dashboard hard-blocked d on the main worktree with a flat "cannot delete the main worktree", so the only way to kill that worktree's agent or shell sessions was to leave the TUI and run ch delete session by hand. On the CLI, ch delete worktree <project> main fell through to git, which failed removing the clone directory and surfaced a raw porcelain error.

This lets the TUI's d key kill the main worktree's sessions (leaving the worktree in place) and gives ch delete worktree main a clean refusal.

What changed

  • Domain guard. The main worktree is the clone directory itself, not a checkout under __worktrees/, so tearing it down never makes sense. Teardown now refuses it with a new herd.ErrMainWorktree sentinel, checked the moment the clone dir is known — before any session is stopped or any git command runs.
  • One error vocabulary. Both front ends translate the sentinel: the CLI names the worktree and points at ch delete session; the TUI shows a concise line.
  • TUI.d no longer refuses the main worktree outright — it only declines when there are no sessions. With a session present it opens the confirm menu in a main-worktree mode that offers session kills only and never lists worktree removal, so the destructive option is unreachable by construction. A new deleteAllSessions action stops every session via StopOpts{All: true} and leaves the worktree in place.

Verification

make check green — 628 unit tests, integration tests, lint (0 issues), build; coverage 85.4% (≥ 80%). Includes a new integration test asserting Teardown refuses the main worktree for both --force values and leaves the clone dir on disk.

🤖 Generated with Claude Code

The dashboard hard-blocked `d` on the main worktree with a flat "cannot
delete the main worktree", so the only way to kill that worktree's agent
or shell sessions was to leave the TUI and run `ch delete session` by
hand. On the CLI, `ch delete worktree <project> main` fell straight
through to git, which failed trying to remove the clone directory and
surfaced a raw porcelain error instead of an explanation.
The main worktree is the clone directory itself, not a checkout under
__worktrees/, so tearing it down never makes sense. Teardown now refuses
it with a new ErrMainWorktree sentinel, checked the moment the clone dir
is known — before any session is stopped or any git command runs — so a
refusal can never leave sessions half-killed or the tree half-removed.
Both front ends translate the sentinel through the one error vocabulary:
the CLI names the worktree and points the user at `ch delete session`,
and the TUI shows a concise line.
In the dashboard, `d` no longer refuses the main worktree outright — it
only declines when there are no sessions to act on. With a session
present it opens the confirm menu in a main-worktree mode that offers
session kills only and never lists worktree removal, so the destructive
option is unreachable by construction rather than guarded after the
fact. A new deleteAllSessions action stops every session for the ref via
StopOpts{All: true} and leaves the worktree in place.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xico42
xico42 merged commit 9ee9111 into mainJul 18, 2026
7 checks passed
@xico42
xico42 deleted the feat/delete-main-session branch July 18, 2026 18:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@xico42
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: allow session-only delete on the main worktree - #23

Merged
xico42 merged 1 commit into
mainfrom
feat/delete-main-session
Jul 18, 2026
Merged

feat: allow session-only delete on the main worktree#23
xico42 merged 1 commit into
mainfrom
feat/delete-main-session

Conversation

@xico42

Copy link
Copy Markdown
Owner

Summary

The dashboard hard-blocked d on the main worktree with a flat "cannot delete the main worktree", so the only way to kill that worktree's agent or shell sessions was to leave the TUI and run ch delete session by hand. On the CLI, ch delete worktree <project> main fell through to git, which failed removing the clone directory and surfaced a raw porcelain error.

This lets the TUI's d key kill the main worktree's sessions (leaving the worktree in place) and gives ch delete worktree main a clean refusal.

What changed

  • Domain guard. The main worktree is the clone directory itself, not a checkout under __worktrees/, so tearing it down never makes sense. Teardown now refuses it with a new herd.ErrMainWorktree sentinel, checked the moment the clone dir is known — before any session is stopped or any git command runs.
  • One error vocabulary. Both front ends translate the sentinel: the CLI names the worktree and points at ch delete session; the TUI shows a concise line.
  • TUI.d no longer refuses the main worktree outright — it only declines when there are no sessions. With a session present it opens the confirm menu in a main-worktree mode that offers session kills only and never lists worktree removal, so the destructive option is unreachable by construction. A new deleteAllSessions action stops every session via StopOpts{All: true} and leaves the worktree in place.

Verification

make check green — 628 unit tests, integration tests, lint (0 issues), build; coverage 85.4% (≥ 80%). Includes a new integration test asserting Teardown refuses the main worktree for both --force values and leaves the clone dir on disk.

🤖 Generated with Claude Code

The dashboard hard-blocked `d` on the main worktree with a flat "cannot
delete the main worktree", so the only way to kill that worktree's agent
or shell sessions was to leave the TUI and run `ch delete session` by
hand. On the CLI, `ch delete worktree <project> main` fell straight
through to git, which failed trying to remove the clone directory and
surfaced a raw porcelain error instead of an explanation.
The main worktree is the clone directory itself, not a checkout under
__worktrees/, so tearing it down never makes sense. Teardown now refuses
it with a new ErrMainWorktree sentinel, checked the moment the clone dir
is known — before any session is stopped or any git command runs — so a
refusal can never leave sessions half-killed or the tree half-removed.
Both front ends translate the sentinel through the one error vocabulary:
the CLI names the worktree and points the user at `ch delete session`,
and the TUI shows a concise line.
In the dashboard, `d` no longer refuses the main worktree outright — it
only declines when there are no sessions to act on. With a session
present it opens the confirm menu in a main-worktree mode that offers
session kills only and never lists worktree removal, so the destructive
option is unreachable by construction rather than guarded after the
fact. A new deleteAllSessions action stops every session for the ref via
StopOpts{All: true} and leaves the worktree in place.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xico42
xico42 merged commit 9ee9111 into mainJul 18, 2026
7 checks passed
@xico42
xico42 deleted the feat/delete-main-session branch July 18, 2026 18:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@xico42
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: allow session-only delete on the main worktree - #23

Merged
xico42 merged 1 commit into
mainfrom
feat/delete-main-session
Jul 18, 2026
Merged

feat: allow session-only delete on the main worktree#23
xico42 merged 1 commit into
mainfrom
feat/delete-main-session

Conversation

@xico42

Copy link
Copy Markdown
Owner

Summary

The dashboard hard-blocked d on the main worktree with a flat "cannot delete the main worktree", so the only way to kill that worktree's agent or shell sessions was to leave the TUI and run ch delete session by hand. On the CLI, ch delete worktree <project> main fell through to git, which failed removing the clone directory and surfaced a raw porcelain error.

This lets the TUI's d key kill the main worktree's sessions (leaving the worktree in place) and gives ch delete worktree main a clean refusal.

What changed

  • Domain guard. The main worktree is the clone directory itself, not a checkout under __worktrees/, so tearing it down never makes sense. Teardown now refuses it with a new herd.ErrMainWorktree sentinel, checked the moment the clone dir is known — before any session is stopped or any git command runs.
  • One error vocabulary. Both front ends translate the sentinel: the CLI names the worktree and points at ch delete session; the TUI shows a concise line.
  • TUI.d no longer refuses the main worktree outright — it only declines when there are no sessions. With a session present it opens the confirm menu in a main-worktree mode that offers session kills only and never lists worktree removal, so the destructive option is unreachable by construction. A new deleteAllSessions action stops every session via StopOpts{All: true} and leaves the worktree in place.

Verification

make check green — 628 unit tests, integration tests, lint (0 issues), build; coverage 85.4% (≥ 80%). Includes a new integration test asserting Teardown refuses the main worktree for both --force values and leaves the clone dir on disk.

🤖 Generated with Claude Code

The dashboard hard-blocked `d` on the main worktree with a flat "cannot
delete the main worktree", so the only way to kill that worktree's agent
or shell sessions was to leave the TUI and run `ch delete session` by
hand. On the CLI, `ch delete worktree <project> main` fell straight
through to git, which failed trying to remove the clone directory and
surfaced a raw porcelain error instead of an explanation.
The main worktree is the clone directory itself, not a checkout under
__worktrees/, so tearing it down never makes sense. Teardown now refuses
it with a new ErrMainWorktree sentinel, checked the moment the clone dir
is known — before any session is stopped or any git command runs — so a
refusal can never leave sessions half-killed or the tree half-removed.
Both front ends translate the sentinel through the one error vocabulary:
the CLI names the worktree and points the user at `ch delete session`,
and the TUI shows a concise line.
In the dashboard, `d` no longer refuses the main worktree outright — it
only declines when there are no sessions to act on. With a session
present it opens the confirm menu in a main-worktree mode that offers
session kills only and never lists worktree removal, so the destructive
option is unreachable by construction rather than guarded after the
fact. A new deleteAllSessions action stops every session for the ref via
StopOpts{All: true} and leaves the worktree in place.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xico42
xico42 merged commit 9ee9111 into mainJul 18, 2026
7 checks passed
@xico42
xico42 deleted the feat/delete-main-session branch July 18, 2026 18:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@xico42
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat: allow session-only delete on the main worktree - #23

Merged
xico42 merged 1 commit into
mainfrom
feat/delete-main-session
Jul 18, 2026
Merged

feat: allow session-only delete on the main worktree#23
xico42 merged 1 commit into
mainfrom
feat/delete-main-session

Conversation

@xico42

Copy link
Copy Markdown
Owner

Summary

The dashboard hard-blocked d on the main worktree with a flat "cannot delete the main worktree", so the only way to kill that worktree's agent or shell sessions was to leave the TUI and run ch delete session by hand. On the CLI, ch delete worktree <project> main fell through to git, which failed removing the clone directory and surfaced a raw porcelain error.

This lets the TUI's d key kill the main worktree's sessions (leaving the worktree in place) and gives ch delete worktree main a clean refusal.

What changed

  • Domain guard. The main worktree is the clone directory itself, not a checkout under __worktrees/, so tearing it down never makes sense. Teardown now refuses it with a new herd.ErrMainWorktree sentinel, checked the moment the clone dir is known — before any session is stopped or any git command runs.
  • One error vocabulary. Both front ends translate the sentinel: the CLI names the worktree and points at ch delete session; the TUI shows a concise line.
  • TUI.d no longer refuses the main worktree outright — it only declines when there are no sessions. With a session present it opens the confirm menu in a main-worktree mode that offers session kills only and never lists worktree removal, so the destructive option is unreachable by construction. A new deleteAllSessions action stops every session via StopOpts{All: true} and leaves the worktree in place.

Verification

make check green — 628 unit tests, integration tests, lint (0 issues), build; coverage 85.4% (≥ 80%). Includes a new integration test asserting Teardown refuses the main worktree for both --force values and leaves the clone dir on disk.

🤖 Generated with Claude Code

The dashboard hard-blocked `d` on the main worktree with a flat "cannot
delete the main worktree", so the only way to kill that worktree's agent
or shell sessions was to leave the TUI and run `ch delete session` by
hand. On the CLI, `ch delete worktree <project> main` fell straight
through to git, which failed trying to remove the clone directory and
surfaced a raw porcelain error instead of an explanation.
The main worktree is the clone directory itself, not a checkout under
__worktrees/, so tearing it down never makes sense. Teardown now refuses
it with a new ErrMainWorktree sentinel, checked the moment the clone dir
is known — before any session is stopped or any git command runs — so a
refusal can never leave sessions half-killed or the tree half-removed.
Both front ends translate the sentinel through the one error vocabulary:
the CLI names the worktree and points the user at `ch delete session`,
and the TUI shows a concise line.
In the dashboard, `d` no longer refuses the main worktree outright — it
only declines when there are no sessions to act on. With a session
present it opens the confirm menu in a main-worktree mode that offers
session kills only and never lists worktree removal, so the destructive
option is unreachable by construction rather than guarded after the
fact. A new deleteAllSessions action stops every session for the ref via
StopOpts{All: true} and leaves the worktree in place.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xico42
xico42 merged commit 9ee9111 into mainJul 18, 2026
7 checks passed
@xico42
xico42 deleted the feat/delete-main-session branch July 18, 2026 18:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@xico42