Stop leaked rubocop --server processes from os-sdlc lint runs #1453

Closed
opened 2026-10-28 06:14:17 +00:00 by jared · 2 comments
Owner

Problem

Rubocop server processes from os-sdlc runs are never stopped. On 2026-10-28, 456 processes matched rubocop --server /tmp/os-sdlc-test, some 2.4 days old. Four more servers run for removed worktrees (ticket-1337, ticket-1357, ticket-1370, ticket-1402). Each server holds memory for a dead directory.

Observed

ps -eo pid,etimes,args | grep -c 'rubocop --server /tmp/os-sdlc-test'
457 (includes the grep itself; 456 servers)

OsSdlc::Lint::Run#arguments adds --server when server: true. OsSdlc::FastGate::NewOffenseReport.run passes server: true. No code path found that calls rubocop --stop-server.

Cause, proven by probe on 2026-10-28 (rubocop 1.92.1): the gate runs procman with rubocop --root R -- os-sdlc-lint .... procman starts rubocop --start-server --no-detach in R with no --config. os-sdlc-lint then runs rubocop --server --config <generated lint-worklist.rubocop.yml>. rubocop keys a server by a hash of the config content, so the client sees procman's server as incompatible, stops it, and starts a detached daemon (ppid systemd). procman does not own that daemon, so nothing stops it.

Second cause, found at implementation 214 escalation 96 (2026-10-28): exe/rubocop forwards a plain call with no --server to any server that runs for the project directory (RuboCop::Server.forward_to_server?), and the client restarts that server detached when the config key differs. So removing --server alone still leaks under procman. --no-server stops procman's server on each run. RuboCop::CLI never forwards.

Reproduce

n/a: the leak shows in the host process table; run the ps command above.

Approach

os-sdlc starts no rubocop server of its own; procman stays the sole owner of server lifetime (ADR-0219). Remove server: true from NewOffenseReport, and remove the server: option that then has no caller from Lint::Run and FastGate::Rubocop. Lint::Run::Invocation#capture starts ruby -rrubocop -e 'exit RuboCop::CLI.new.run(ARGV)', not exe/rubocop, so rubocop runs in-process and leaves procman's server alone. Measured: one in-process rubocop call 0.68 s; a warm gate LINT run 1.04 s, because it pays the server restart on every run today.

The procman with rubocop wrapper in gate/commands.rb stays unchanged; its server is then unused. Whether to drop the wrapper or start procman's server with the generated config is a separate decision (it touches ADR-0219 and the procman gem).

Required work:

  1. Stop passing server: true — check: absent server: true in plugins/os-sdlc/lib/os_sdlc/fast_gate/new_offense_report.rb
  2. Remove the --server argument from the lint run — check: absent --server in plugins/os-sdlc/lib/os_sdlc/lint/run.rb
  3. Remove the pass-through option — check: absent server: in plugins/os-sdlc/lib/os_sdlc/fast_gate/rubocop.rb

Orchestrator work: after merge, run bin/refresh-plugins and stop the already-leaked /tmp/os-sdlc-test* servers once.

Acceptance criteria

  1. Given os-sdlc-lint runs over a root, when it calls rubocop, then the rubocop arguments contain no --server.
  2. Given the gate LINT command runs over a temporary root under procman with rubocop, when the command ends, then no detached rubocop --server process for that root exists (procman's own server retires when the root is deleted).

Owner sketch

module OsSdlc
  module Lint
    class Run
      def initialize(lint_config: nil, layers: [], only: []); end
    end
  end

  module FastGate
    class Rubocop
      def initialize(root, lint_config: nil, runner: Lint::Run); end
      def offenses(files:, stdin: nil, only: [], layers: []); end
    end
  end
end

Non-goals

  • Dropping the procman with rubocop wrapper, or starting procman's server with the generated config.
  • Cleanup of stale ~/.cache/rubocop_cache/server/*os-sdlc-test* directories.

Origin

  • Trigger: ps count during an AFK lint-cops session, 2026-10-28
  • Improvised this session: none (no process killed)
  • Chain: leaked servers ← Lint::Run starts --server via FastGate::NewOffenseReport ← no stop-server call found (DESIGN: plugins/os-sdlc/lib/os_sdlc/lint/run.rb)
  • Root candidate: none found; searched rubocop, --server, stop-server, leak
  • Where: plugins/os-sdlc/lib/os_sdlc/lint/run.rb, plugins/os-sdlc/lib/os_sdlc/fast_gate/new_offense_report.rb
  • Session: 9f415790-a6bd-4850-8d70-3dd45004ce2b
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/9f415790-a6bd-4850-8d70-3dd45004ce2b.jsonl

VERDICT: CREATE

## Problem Rubocop server processes from os-sdlc runs are never stopped. On 2026-10-28, 456 processes matched `rubocop --server /tmp/os-sdlc-test`, some 2.4 days old. Four more servers run for removed worktrees (ticket-1337, ticket-1357, ticket-1370, ticket-1402). Each server holds memory for a dead directory. ## Observed ``` ps -eo pid,etimes,args | grep -c 'rubocop --server /tmp/os-sdlc-test' ``` ``` 457 (includes the grep itself; 456 servers) ``` `OsSdlc::Lint::Run#arguments` adds `--server` when `server: true`. `OsSdlc::FastGate::NewOffenseReport.run` passes `server: true`. No code path found that calls `rubocop --stop-server`. Cause, proven by probe on 2026-10-28 (rubocop 1.92.1): the gate runs `procman with rubocop --root R -- os-sdlc-lint ...`. procman starts `rubocop --start-server --no-detach` in R with no `--config`. `os-sdlc-lint` then runs `rubocop --server --config <generated lint-worklist.rubocop.yml>`. rubocop keys a server by a hash of the config content, so the client sees procman's server as incompatible, stops it, and starts a detached daemon (ppid systemd). procman does not own that daemon, so nothing stops it. Second cause, found at implementation 214 escalation 96 (2026-10-28): `exe/rubocop` forwards a plain call with no `--server` to any server that runs for the project directory (`RuboCop::Server.forward_to_server?`), and the client restarts that server detached when the config key differs. So removing `--server` alone still leaks under procman. `--no-server` stops procman's server on each run. `RuboCop::CLI` never forwards. ## Reproduce n/a: the leak shows in the host process table; run the ps command above. ## Approach os-sdlc starts no rubocop server of its own; procman stays the sole owner of server lifetime (ADR-0219). Remove `server: true` from `NewOffenseReport`, and remove the `server:` option that then has no caller from `Lint::Run` and `FastGate::Rubocop`. `Lint::Run::Invocation#capture` starts `ruby -rrubocop -e 'exit RuboCop::CLI.new.run(ARGV)'`, not `exe/rubocop`, so rubocop runs in-process and leaves procman's server alone. Measured: one in-process rubocop call 0.68 s; a warm gate LINT run 1.04 s, because it pays the server restart on every run today. The `procman with rubocop` wrapper in `gate/commands.rb` stays unchanged; its server is then unused. Whether to drop the wrapper or start procman's server with the generated config is a separate decision (it touches ADR-0219 and the procman gem). Required work: 1. Stop passing `server: true` — check: absent `server: true` in `plugins/os-sdlc/lib/os_sdlc/fast_gate/new_offense_report.rb` 2. Remove the `--server` argument from the lint run — check: absent `--server` in `plugins/os-sdlc/lib/os_sdlc/lint/run.rb` 3. Remove the pass-through option — check: absent `server:` in `plugins/os-sdlc/lib/os_sdlc/fast_gate/rubocop.rb` Orchestrator work: after merge, run `bin/refresh-plugins` and stop the already-leaked `/tmp/os-sdlc-test*` servers once. ## Acceptance criteria 1. Given `os-sdlc-lint` runs over a root, when it calls rubocop, then the rubocop arguments contain no `--server`. 2. Given the gate LINT command runs over a temporary root under `procman with rubocop`, when the command ends, then no detached `rubocop --server` process for that root exists (procman's own server retires when the root is deleted). ## Owner sketch ```ruby module OsSdlc module Lint class Run def initialize(lint_config: nil, layers: [], only: []); end end end module FastGate class Rubocop def initialize(root, lint_config: nil, runner: Lint::Run); end def offenses(files:, stdin: nil, only: [], layers: []); end end end end ``` ## Non-goals - Dropping the `procman with rubocop` wrapper, or starting procman's server with the generated config. - Cleanup of stale `~/.cache/rubocop_cache/server/*os-sdlc-test*` directories. ## Origin - Trigger: ps count during an AFK lint-cops session, 2026-10-28 - Improvised this session: none (no process killed) - Chain: leaked servers ← Lint::Run starts --server via FastGate::NewOffenseReport ← no stop-server call found (DESIGN: plugins/os-sdlc/lib/os_sdlc/lint/run.rb) - Root candidate: none found; searched rubocop, --server, stop-server, leak - Where: plugins/os-sdlc/lib/os_sdlc/lint/run.rb, plugins/os-sdlc/lib/os_sdlc/fast_gate/new_offense_report.rb - Session: 9f415790-a6bd-4850-8d70-3dd45004ce2b - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/9f415790-a6bd-4850-8d70-3dd45004ce2b.jsonl VERDICT: CREATE
Author
Owner

Work started on branch ticket-1453 (os-sdlc implement).

Work started on branch ticket-1453 (os-sdlc implement).
Author
Owner

Resolution

Done: os-sdlc lint starts no rubocop server. server: option removed from FastGate::NewOffenseReport, FastGate::Rubocop and Lint::Run. Lint::Run::Invocation#capture runs RbConfig.ruby -rrubocop -e 'exit RuboCop::CLI.new.run(ARGV)', because exe/rubocop forwards even a plain call to a running server for the root and restarts it detached on a config mismatch. procman stays the sole server owner (ADR-0219). Test fakes moved to FakeRubocopLibrary on RUBYLIB. 45 leaked /tmp/os-sdlc-test servers stopped once.

Evidence: main b347878a (commit 7246f609). New test tests/gate/lint_command_leaves_no_rubocop_server_test.rb is red on the old code (detached server, ppid 4617) and green on the fix. Suite on merged branch: 2904 runs, 0 failures, 45.2s. rubocop clean on changed files. os-sdlc-required-work-check 214 exit 0. Implementation 214 escalated (96) on a test defect; hand-finished with one design audit, all SHOULD findings fixed or answered; run abandoned.

Follow-ups: none. The procman rubocop wrapper's server is now unused by os-sdlc lint; whether to drop the wrapper or start procman's server with the generated config is a ruling for Jared, reported in the session summary.

## Resolution **Done:** os-sdlc lint starts no rubocop server. server: option removed from FastGate::NewOffenseReport, FastGate::Rubocop and Lint::Run. Lint::Run::Invocation#capture runs RbConfig.ruby -rrubocop -e 'exit RuboCop::CLI.new.run(ARGV)', because exe/rubocop forwards even a plain call to a running server for the root and restarts it detached on a config mismatch. procman stays the sole server owner (ADR-0219). Test fakes moved to FakeRubocopLibrary on RUBYLIB. 45 leaked /tmp/os-sdlc-test servers stopped once. **Evidence:** main b347878a (commit 7246f609). New test tests/gate/lint_command_leaves_no_rubocop_server_test.rb is red on the old code (detached server, ppid 4617) and green on the fix. Suite on merged branch: 2904 runs, 0 failures, 45.2s. rubocop clean on changed files. os-sdlc-required-work-check 214 exit 0. Implementation 214 escalated (96) on a test defect; hand-finished with one design audit, all SHOULD findings fixed or answered; run abandoned. **Follow-ups:** none. The procman rubocop wrapper's server is now unused by os-sdlc lint; whether to drop the wrapper or start procman's server with the generated config is a ruling for Jared, reported in the session summary.
jared closed this issue 2026-10-28 17:06:12 +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#1453
No description provided.