fix: PR-badge refresh, cross-worktree sibling collaboration, retention sweep + start-script fixes - #179
Open
kovtcharov wants to merge 12 commits into
Open
fix: PR-badge refresh, cross-worktree sibling collaboration, retention sweep + start-script fixes#179kovtcharov wants to merge 12 commits into
kovtcharov wants to merge 12 commits into
Conversation
…trees Two gaps in workspace:reset: 1. The task filter was an exact workspaceId match, silently skipping every task running in the workspace's .claudia-worktrees/* children (fleet tasks — the majority). Reset now archives across the whole workspace family (root + worktree children). 2. Worktrees were never removed at all. Reset now force-removes each child worktree after its tasks are archived (primary-workspace guard kept), deletes the workspace record, and broadcasts the removal — matching the removal recipe in the archived-worktree retention spec. resetResult payload and the frontend toast report worktreesRemoved / worktreesFailed so partial failures are visible instead of silent.
The port check used lsof -ti:port, which matches ANY socket touching the port — a browser's CLOSED client connection to a dead backend produced false 'port in use' failures. Now checks LISTEN state only. When a port IS held, the script identifies the listener and: - claudia-owned (command line contains this checkout's path): offers to kill it (y/N prompt; --force skips; non-interactive never prompts). Backend gets plain SIGTERM with a 10s graceful-exit wait — its children are live Claude Code sessions and are NEVER tree-killed; only vite gets kill_tree (esbuild children). - anything else (Claude Code sessions, other projects): reported and refused — the script never touches non-claudia processes.
The new LISTEN-only check ran under the script's set -e: lsof exits 1 when a port has no listener, so checking a FREE port killed the script silently right after 'Checking ports...'. Guarded all failure-prone statements (lsof/ps substitutions, read, both kill sites) with || true or if-form. Verified: full check block runs green under set -e both with free ports and with a stale claudia listener present.
Only the 14px grip icon accepted drags; grabbing the workspace name — the natural gesture — did nothing, so reordering appeared broken. The full header is now the drag surface; interactive children (buttons/inputs/menus) and name-editing opt out; click still toggles expand. User-verified.
…tention spec)
Worktrees whose owning tasks were archived lingered forever — invisible
in the sidebar (no tasks) yet counted by the workspace badge, with no
cleanup mechanism. Implements the spec:
- worktree-reaper.ts: pure classifyWorktree decision fn (unit-tested,
8 cases) + force-remove with unlock retry ('--force --force')
- Hourly sweep + startup pass in server.ts: orphaned worktrees (no
owning task anywhere) removed immediately; archived-owner worktrees
removed after worktreeRetentionDays (default 30, 0 disables); any
worktree referenced by a live/disconnected task is NEVER touched;
every removal and skip logged with reason; archived-task metadata +
history deleted via the existing path; workspace records deleted and
broadcast so badges update live
- Settings: 'Archived Worktree Retention (days)' field; config read
each sweep run, changes apply without restart
The periodic refresh only re-polled workspaces with an active task (busy/starting/waiting_input) plus a one-time lazy fetch. A task that pushed, opened a PR, and went idle while CI ran had its badge frozen at the push-time state forever. Drive refresh by whether the PR can still change (CI running, or PR open/draft) instead, via a pure, unit-tested selectWorkspacesToRefresh. Also bound the pass to concurrency 4 so one slow gh call can't stall the whole pass, and skip terminal PRs.
…escriptions list_tasks already spans the workspace's worktree tree and the other inter-task tools take a raw taskId, but their descriptions framed them as self-only, so coordinator agents never discovered siblings. Describe the discover->read->message loop across list_tasks/get_task_output/send_input/ continue_task via a shared SIBLING_TASKS constant, fix list_tasks' suffix that contradicted the new text, and restore the state precondition (never send to a busy task) on send_input/continue_task.
Built from a mined catalog of 16 bug classes across main's history and all 56 PRs. Closes the three highest-severity zero-coverage gaps: 1. SESSION LIFECYCLE (the session-history-loss disaster class): session-lifecycle.test.ts mocks node-pty and drives real create/reconnect flows — asserts --resume pointer integrity, unambiguous-only orphan recovery (coordinator-trap declined), recovery-map one-shot retirement, and --system-prompt on reconnect. The systemPrompt test was written first and FAILED on the live bug (reconnect dropped the persisted prompt on every restart/sleep-wake); fix included: reconnect args now re-apply persisted.systemPrompt. 2. WS-HANDLER INTEGRATION: server-ws-integration.test.ts boots the real server via createApp(tmpDir) + shutdownForTests (new test-safe teardown), real temp git repo + worktree, seeded state, real WS client. Guards the workspace:reset family-archiving + worktree removal regressions and #100/#102 path validation. 3. TERMINAL INJECTION: terminal-injection.test.ts replays polluted history through @xterm/headless (new devDep) — raw history provably emits responses, stripped history emits ZERO bytes; split-boundary carry protocol proven end-to-end. 4. SCRIPTS: scripts/test-start-port-check.sh runs the REAL start.sh (check-only mode + env port overrides + overridable lock) — set -e survival, LISTEN-only matching, foreign-process refusal, --force cleanup. Wired into CI as a blocking step; shellcheck now BLOCKING (all findings fixed); drifted copy-paste dep-check removed. Backend: 22 files / 611 tests. All green.
…counting - fixtures/fake-claude.sh: PATH-injected fake CLI speaking the TUI contract (ready banner, input echo, session-file creation, argv/stdin logging, HUP/TERM traps) — makes the full task lifecycle testable without Anthropic: create → spawn → prompt delivery → session capture → follow-up input → archive kill, plus reconnect asserting --resume and --system-prompt at the true process boundary. 5 tests. - vitest coverage: all:true + include src/** in BOTH packages. Without it, files never imported by tests were absent from reports — frontend claimed 85% while its real number is ~8% (TerminalView/useWebSocket/ WorkspacePanel at 0% were invisible). Coverage now tells the truth.
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.
Stacks on #155 (
fix/reconnect-input-delivery) — set that as the base; retarget tomainonce #155 merges.What's here
Fleet task-management + a couple of standalone bug fixes accumulated on this branch.
Bug fixes
selectWorkspacesToRefresh. Bounded to concurrency 4 so one slowghcall can't stall the pass; terminal (merged/closed) PRs are skipped.list_tasks/get_task_output/send_input/continue_task, fixes a self-contradictinglist_taskssuffix, and restores the "never send to a busy task" state precondition.set -e.Features
Docs
Testing
npx tsc --noEmitclean;npx vitest rungreen (incl. newpr-refreshtests). Badge fix verified against live data (4 previously-frozen workspaces detected).