Route the ruby-lsp daemon lifecycle through the procman gem (ADR-0219 slice 1) #712

Closed
opened 2026-09-25 15:00:53 +00:00 by jared · 2 comments
Owner

Problem

Every ticket against the lsp-probe daemon since 2026-09-16 (#606 review, #619, #651, #648, and the 2026-09-24 overnight hang) was a process-lifecycle defect. Five spawn sites each key and stop servers differently. ADR-0219 moves ownership to the procman gem at ~/dev/ruby-gems/procman.

Reproduce

n/a: design slice from ADR-0219

Approach

Required work:

  1. Add a path dependency on ~/dev/ruby-gems/procman in plugins/os-sdlc
  2. Implement OsSdlc::LspProbe::ProcmanKind inside os-sdlc against procman's pluggable Kind seam (command(root:, dir:), endpoint(dir:), ready?(dir:)): command is bin/lsp-probe serve --root R, endpoint is <dir>/daemon.sock. The procman gem stays generic and is not edited by this ticket.
  3. Replace the client-side find-or-start in Daemon (class methods .connect and .open, instance method #connect) with Procman.ensure and Procman.with using ProcmanKind; delete Spawner, Paths, Idle, and SpawnTimeout under lib/os_sdlc/lsp_probe/daemon/. Server stays as the body of bin/lsp-probe serve but loses its Idle self-exit; on TERM it stops its ruby-lsp session (OpenSession and Stopper stay, server-internal) and exits. shared_runner.rb and open_session.rb are unchanged: SharedRunner#session calls @spawner.open(root:) at shared_runner.rb:27 and OpenSession.open/@stdin.close at open_session.rb:13,82 are OpenSession's own ruby-lsp session, not LspDaemonLifecycle, and neither file references LspDaemonLifecycle.
  4. Delete Pipeline::LspDaemonLifecycle and its call sites in lib/os_sdlc/pipeline/open_implementation.rb and lib/os_sdlc/model/implementation/instance_methods.rb (complete! and close_lsp!), their tests (tests/pipeline/lsp_daemon_lifecycle_test.rb, tests/pipeline/open_implementation_warms_lsp_daemon_test.rb, tests/model/implementation_stops_lsp_daemon_on_terminal_stamp_test.rb), and the fake OsSdlc::Pipeline::LspDaemonLifecycle.open double with its comment block in tests/test_helper.rb (lines 40-52 at HEAD)
  5. Gate tests/lsp_probe_bin_test.rb and tests/evidence/probe/lsp_integration_test.rb behind PROCMAN_REAL=1 and call Procman.stop_all in Minitest.after_run
  6. In tests/lsp_probe/daemon/server_test.rb delete test_a_server_whose_socket_was_removed_underneath_it_exits (its unbounded thread.join hung session 0bed4a13 overnight) and every Idle-based test; delete the unit tests of Spawner, Paths, Idle, and SpawnTimeout with their subjects

Acceptance criteria

  1. Full os-sdlc suite green in under 60 seconds
  2. pgrep -f 'lsp-probe serve' is empty after the run
  3. No file under plugins/os-sdlc/lib references LspDaemonLifecycle

Owner sketch

OsSdlc::LspProbe::ProcmanKind (new, lib/os_sdlc/lsp_probe/procman_kind.rb) owns the argv, endpoint path, and readiness check for a ruby-lsp server under procman.
OsSdlc::LspProbe::Daemon keeps its public .connect, .open, and #connect names but delegates to Procman.ensure(ProcmanKind.new, root:, config:) and Procman.with; Spawner, Paths, Idle, SpawnTimeout under lib/os_sdlc/lsp_probe/daemon/ are deleted. Server stays as the serve body without Idle; Stopper and OpenSession stay for the server's own ruby-lsp child.
Pipeline::LspDaemonLifecycle is deleted; OpenImplementation#call, Implementation#complete! and #close_lsp!, SharedRunner, and OpenSession lose their open/close calls.
Gemfile in plugins/os-sdlc gains gem "procman", path: File.expand_path("~/dev/ruby-gems/procman").
Tests: tests/lsp_probe_bin_test.rb and tests/evidence/probe/lsp_integration_test.rb skip unless ENV["PROCMAN_REAL"] == "1" and register Procman.stop_all in Minitest.after_run; unit tests of the deleted classes go with them, and a fake Kind covers Daemon's delegation.

Retired tests

plugins/os-sdlc/tests/lsp_probe/daemon/server_test.rb
plugins/os-sdlc/tests/lsp_probe/daemon_test.rb
plugins/os-sdlc/tests/lsp_probe/daemon/fresh_by_connect_only_test.rb
plugins/os-sdlc/tests/lsp_probe/daemon/paths_test.rb
plugins/os-sdlc/tests/lsp_probe/daemon/paths_cleanup_removes_hash_dir_test.rb
plugins/os-sdlc/tests/pipeline/lsp_daemon_lifecycle_test.rb
plugins/os-sdlc/tests/pipeline/open_implementation_warms_lsp_daemon_test.rb
plugins/os-sdlc/tests/model/implementation_stops_lsp_daemon_on_terminal_stamp_test.rb

Follow-through outside the diff

On close, the orchestrator closes #619, #651, #648 against this ticket. Branch batch-lsp is abandoned, not merged. ADR-0216 is already marked superseded by ADR-0219. The rubocop --server gate in gate_commands.rb is #713, not this ticket.

Origin

Session: ecfe6e11-0810-4431-88e5-8e24d34a239e
Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/ecfe6e11-0810-4431-88e5-8e24d34a239e.jsonl
Where: plugins/os-sdlc/lib/os_sdlc/lsp_probe/daemon.rb
ADR: 0219

## Problem Every ticket against the lsp-probe daemon since 2026-09-16 (#606 review, #619, #651, #648, and the 2026-09-24 overnight hang) was a process-lifecycle defect. Five spawn sites each key and stop servers differently. ADR-0219 moves ownership to the procman gem at ~/dev/ruby-gems/procman. ## Reproduce n/a: design slice from ADR-0219 ## Approach Required work: 1. Add a path dependency on ~/dev/ruby-gems/procman in plugins/os-sdlc 2. Implement `OsSdlc::LspProbe::ProcmanKind` inside os-sdlc against procman's pluggable Kind seam (`command(root:, dir:)`, `endpoint(dir:)`, `ready?(dir:)`): command is `bin/lsp-probe serve --root R`, endpoint is `<dir>/daemon.sock`. The procman gem stays generic and is not edited by this ticket. 3. Replace the client-side find-or-start in Daemon (class methods .connect and .open, instance method #connect) with Procman.ensure and Procman.with using ProcmanKind; delete Spawner, Paths, Idle, and SpawnTimeout under lib/os_sdlc/lsp_probe/daemon/. Server stays as the body of `bin/lsp-probe serve` but loses its Idle self-exit; on TERM it stops its ruby-lsp session (OpenSession and Stopper stay, server-internal) and exits. shared_runner.rb and open_session.rb are unchanged: `SharedRunner#session` calls `@spawner.open(root:)` at shared_runner.rb:27 and `OpenSession.open`/`@stdin.close` at open_session.rb:13,82 are OpenSession's own ruby-lsp session, not LspDaemonLifecycle, and neither file references LspDaemonLifecycle. 4. Delete Pipeline::LspDaemonLifecycle and its call sites in lib/os_sdlc/pipeline/open_implementation.rb and lib/os_sdlc/model/implementation/instance_methods.rb (complete! and close_lsp!), their tests (tests/pipeline/lsp_daemon_lifecycle_test.rb, tests/pipeline/open_implementation_warms_lsp_daemon_test.rb, tests/model/implementation_stops_lsp_daemon_on_terminal_stamp_test.rb), and the fake `OsSdlc::Pipeline::LspDaemonLifecycle.open` double with its comment block in tests/test_helper.rb (lines 40-52 at HEAD) 5. Gate tests/lsp_probe_bin_test.rb and tests/evidence/probe/lsp_integration_test.rb behind PROCMAN_REAL=1 and call Procman.stop_all in Minitest.after_run 6. In tests/lsp_probe/daemon/server_test.rb delete test_a_server_whose_socket_was_removed_underneath_it_exits (its unbounded thread.join hung session 0bed4a13 overnight) and every Idle-based test; delete the unit tests of Spawner, Paths, Idle, and SpawnTimeout with their subjects ## Acceptance criteria 1. Full os-sdlc suite green in under 60 seconds 2. pgrep -f 'lsp-probe serve' is empty after the run 3. No file under plugins/os-sdlc/lib references LspDaemonLifecycle ## Owner sketch `OsSdlc::LspProbe::ProcmanKind` (new, lib/os_sdlc/lsp_probe/procman_kind.rb) owns the argv, endpoint path, and readiness check for a ruby-lsp server under procman. `OsSdlc::LspProbe::Daemon` keeps its public `.connect`, `.open`, and `#connect` names but delegates to `Procman.ensure(ProcmanKind.new, root:, config:)` and `Procman.with`; `Spawner`, `Paths`, `Idle`, `SpawnTimeout` under lib/os_sdlc/lsp_probe/daemon/ are deleted. `Server` stays as the `serve` body without Idle; `Stopper` and `OpenSession` stay for the server's own ruby-lsp child. `Pipeline::LspDaemonLifecycle` is deleted; `OpenImplementation#call`, `Implementation#complete!` and `#close_lsp!`, `SharedRunner`, and `OpenSession` lose their open/close calls. Gemfile in plugins/os-sdlc gains `gem "procman", path: File.expand_path("~/dev/ruby-gems/procman")`. Tests: tests/lsp_probe_bin_test.rb and tests/evidence/probe/lsp_integration_test.rb skip unless ENV["PROCMAN_REAL"] == "1" and register `Procman.stop_all` in `Minitest.after_run`; unit tests of the deleted classes go with them, and a fake Kind covers Daemon's delegation. ## Retired tests plugins/os-sdlc/tests/lsp_probe/daemon/server_test.rb plugins/os-sdlc/tests/lsp_probe/daemon_test.rb plugins/os-sdlc/tests/lsp_probe/daemon/fresh_by_connect_only_test.rb plugins/os-sdlc/tests/lsp_probe/daemon/paths_test.rb plugins/os-sdlc/tests/lsp_probe/daemon/paths_cleanup_removes_hash_dir_test.rb plugins/os-sdlc/tests/pipeline/lsp_daemon_lifecycle_test.rb plugins/os-sdlc/tests/pipeline/open_implementation_warms_lsp_daemon_test.rb plugins/os-sdlc/tests/model/implementation_stops_lsp_daemon_on_terminal_stamp_test.rb ## Follow-through outside the diff On close, the orchestrator closes #619, #651, #648 against this ticket. Branch batch-lsp is abandoned, not merged. ADR-0216 is already marked superseded by ADR-0219. The rubocop --server gate in gate_commands.rb is #713, not this ticket. ## Origin Session: ecfe6e11-0810-4431-88e5-8e24d34a239e Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/ecfe6e11-0810-4431-88e5-8e24d34a239e.jsonl Where: plugins/os-sdlc/lib/os_sdlc/lsp_probe/daemon.rb ADR: 0219
Author
Owner

Work started on branch ticket-712 (os-sdlc pipeline, session ecfe6e11).

Work started on branch ticket-712 (os-sdlc pipeline, session ecfe6e11).
Author
Owner

Resolution

Done: lsp-probe daemon lifecycle moved to the procman gem per ADR-0219: OsSdlc::LspProbe::ProcmanKind added; Daemon.connect/.open/#connect delegate to Procman.ensure and Procman.with; Spawner, Paths, Idle, SpawnTimeout and Pipeline::LspDaemonLifecycle deleted with their call sites; real-server tests gated behind PROCMAN_REAL=1 with Procman.stop_all in Minitest.after_run; eight retired tests removed.

Evidence: Commit cd265726 (branch ticket-712, merged to main by worktree finish). Suite: 1423 runs, 3239 assertions, 0 failures, 0 errors, 33.8s wall. pgrep -f 'lsp-probe serve' matched no server after the run. grep -rl LspDaemonLifecycle plugins/os-sdlc/lib returned nothing. Closes #619, #651, #648. Hand-finished after behavior-verifier could not see staged deletions (implementation 47, 11 dispatches).

Follow-ups: #713 rubocop through procman; #714 SessionStart cleanup hook; #726 runner diff facts omit deletions; routing case logged on #665; procman gem open questions (per-thread lease files, pid-reuse guard) tracked in the procman repo, not here.

## Resolution **Done:** lsp-probe daemon lifecycle moved to the procman gem per ADR-0219: OsSdlc::LspProbe::ProcmanKind added; Daemon.connect/.open/#connect delegate to Procman.ensure and Procman.with; Spawner, Paths, Idle, SpawnTimeout and Pipeline::LspDaemonLifecycle deleted with their call sites; real-server tests gated behind PROCMAN_REAL=1 with Procman.stop_all in Minitest.after_run; eight retired tests removed. **Evidence:** Commit cd265726 (branch ticket-712, merged to main by worktree finish). Suite: 1423 runs, 3239 assertions, 0 failures, 0 errors, 33.8s wall. pgrep -f 'lsp-probe serve' matched no server after the run. grep -rl LspDaemonLifecycle plugins/os-sdlc/lib returned nothing. Closes #619, #651, #648. Hand-finished after behavior-verifier could not see staged deletions (implementation 47, 11 dispatches). **Follow-ups:** #713 rubocop through procman; #714 SessionStart cleanup hook; #726 runner diff facts omit deletions; routing case logged on #665; procman gem open questions (per-thread lease files, pid-reuse guard) tracked in the procman repo, not here.
jared closed this issue 2026-09-25 16:58:44 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#712
No description provided.