os-sdlc owns the lsp-probe daemon lifecycle per implementation; worktree plugin stops managing it #606
Labels
No labels
P0
P1
P2
P3
bug
category/code-change
category/cross-repo
category/eval-run
category/investigation
category/no-test-target
category/owner-ruling
category/prose-artifact
category/recurring-chore
category/structure
category/umbrella
crc-refactor
create
delete
enhancement
filed-by/agent
filed-by/user
frozen
lint-rule
needs-info
needs-triage
next
plugin/cc-architect
plugin/os
plugin/os-adr
plugin/os-aidd-lint
plugin/os-backlog
plugin/os-context
plugin/os-doc-hygiene
plugin/os-sdlc
plugin/os-vault
project/cc-os
ready-for-agent
ready-for-human
recurring
review
rule-proposal
update
waiting
wayfinder:grilling
wayfinder:map
wayfinder:map
wayfinder:research
wayfinder:task
wayfinder:task
wontfix
worklist/deviations
worklist/lint-rule
worklist/new-implement-build
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jared/cc-os#606
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Session 01153d94 (2026-09-16 to 2026-09-17) shipped ADR-0198: a per-root ruby-lsp daemon over a UNIX socket, warmed by
os worktree startand stopped byos worktree finish(#600, #601). A review on 2026-09-17 found five defects.Observed
The daemon key is
sha1(expand_path(--root))and--rootdefaults to".", so a client run from a subdirectory spawns a second daemon and the warm is wasted.fresh?trusts a pid file, so a reused pid stalls every call for 15 s.The socket lives under the shared
.cc-ossymlink target, so deleting a worktree does not stop its daemon.A ruby-lsp spawned by
OpenSession.openoutside the daemon has no process group and cannot be reaped; one orphan on a deleted worktree was killed by hand.Paths#cleanup!leaves one empty hash directory per worktree.The
osplugin warms on every worktree whether or not the task reads Ruby symbols, and it is the only placeosreferences an os-sdlc binary.Reproduce
n/a: design change, not a bug reproduction
Expected
Project.root, detached; intake does not wait on the cold start.completed_at,failed_at, orescalated_atstops that implementation's daemon.lsp-probe defs|refs|symbols|outlinerun from a subdirectory of a checkout connects to the daemon keyed onModel::Project.load(Dir.pwd).root; no second daemon spawns.--rootremains only onserve.Daemon#fresh?returns true only when a connect todaemon.socksucceeds; the pid file and thedaemon.sinceclient-side idle check are removed.Daemon::Serverexits within one idle pulse when its root directory no longer exists;Paths#cleanup!removes the hash directory.OpenSession.openspawns ruby-lsp withpgroup: true;Stopper#stopsends KILL to the group after the wait without checking the leader.os worktree startandfinishno longer warm or stop the daemon;Worktree::LspWarmerand its tests are deleted.Origin
Skeptic verdict
VERDICT: CREATE. User-filed ticket with a real review of five defects and eight concrete acceptance items (ticket-skeptic, 2026-09-17).
Scope
In scope for this ticket (os-sdlc target): items 1 to 6 of Expected, plus the ADR amendment as structural follow-through.
Out of scope, moved to the child ticket "os worktree start/finish stop managing the lsp-probe daemon; delete Worktree::LspWarmer" (os target): item 7 of Expected. Blocked by this ticket.
Owner sketch
```ruby
module OsSdlc::Pipeline
class LspDaemonLifecycle
def open(root)
def close(root)
end
class OpenImplementation
def call
end
end
module OsSdlc::Model::Implementation::InstanceMethods
def complete!(position: nil)
def fail!(position:, reason:)
def escalate!(position:, reason:)
end
module OsSdlc::LspProbe
class CLI < Thor
def defs / refs / symbols / outline
def serve
def stop
end
class Daemon
def fresh?
def connect
def stop
end
class Daemon::Paths
def cleanup!
end
class Daemon::Server
def waiting?
end
class Daemon::Stopper
def stop(pid)
end
class OpenSession
def self.spawn(root_path)
end
end
```
Cases
Structural follow-through
Work started via /os-sdlc:implement on branch ticket-606 (worktree .claude/worktrees/ticket-606).
Decomposed: os-sdlc scope enriched in place (Owner sketch + Cases); os plugin removal split to #607.
Implementation 27 (branch ticket-606) ended implementation_failed: bound_exhausted at test-reviewer/fail. Failed step: test-reviewer rejected the test-writer's red round three times (dispatches 334, 336, 338); the test-writer's own gates (diff-test red, test-lint green) passed each round. Gate details are in the handoff records for implementation 27 in .sdlc/pipeline.db. Worktree ticket-606 kept with the uncommitted test files for inspection. Recovery: a new implementation-open.
Work started via /os-sdlc:implement on branch ticket-606 (fresh worktree from main
b1a10cdc, session 67967f79).Implementation 1 (session 67967f79) FAILED at the programmer node (dispatch 9), reason verdict:error.
Gate details: the programmer's handoff disclosed the gate red as a pre-existing test-mapping gap for lib/os_sdlc/model/implementation/instance_methods.rb. The full os-sdlc suite in worktree ticket-606 shows 14 failures + 3 errors; unmodified main is 0/0. So the increment itself is red, not only the map.
Failure classes:
Worktree ticket-606 keeps the 7 new test files and 7 modified lib files, uncommitted. Test files: tests/lsp_probe/{cli_resolves_root_from_subdirectory,open_session_spawns_with_pgroup}_test.rb, tests/lsp_probe/daemon/{fresh_by_connect_only,paths_cleanup_removes_hash_dir,stopper_kills_process_group_without_leader_check}_test.rb, tests/model/implementation_stops_lsp_daemon_on_terminal_stamp_test.rb, tests/pipeline/open_implementation_warms_lsp_daemon_test.rb.
Recovery needs a decision: either the ticket lists the existing tests to retire/rewrite (so a node can own that edit), or the change is hand-finished from the worktree. Labelled waiting.
Workflow defect captured as #610 (map has no node that may edit existing tests the ticket invalidates; verdict:error pre-empted the suite-check route).
Resolution
Done: Implementation owns the lsp-probe daemon lifecycle: Pipeline::LspDaemonLifecycle opens at implementation-open and closes on complete!/fail!/escalate! keyed on the project root; Daemon#fresh? is a socket connect (pid/activity files removed); query subcommands resolve the root via RootSearch with --root only on serve/stop/warm; ruby-lsp spawns with pgroup: true; Stopper sends group KILL after the wait without a leader check; server exits within one idle pulse when its socket or root is gone and cleanup! removes the hash dir. ADR-0201 amends ADR-0198. Cases 1-8 covered; expected item 7 (os worktree hooks) stays with the child ticket #607.
Evidence: Commit
418804d9on branch ticket-606 (merged to main by worktree finish). Suite: 1341 runs, 3043 assertions, 0 failures, 0 errors (run twice by the worker, once by the orchestrator); main baseline 1331 runs 0/0. rubocop on touched files: 83 files, no offenses. Pipeline impl 1 failed at programmer (verdict:error); hand-finished by user decision.Follow-ups: #610 map gap (no node may edit existing tests a ticket invalidates; verdict:error skipped suite-check); #607 os worktree start/finish stop managing the daemon. Unticketed observations: 34 pre-existing rubocop offenses in untouched os-sdlc files; daemon dir .cc-os/lsp// still inside the project root (per-user runtime dir would end fixture teardown races); autocorrect_prepass_test bare Dir.chdir races sibling tests, now stay_serial!.