Evaluate in-process RuboCop (measure first; ADR-0191 applies) #636

Closed
opened 2026-09-23 12:05:10 +00:00 by jared · 1 comment
Owner

Context

User requested during the shell-call/ENV audit in session 16bf37b3-8cff-4121-896c-4a5fc61f2e9e. This is a measure-then-decide spike, not a committed change: ADR-0191 (Accepted, 2026-09-15) already classifies rubocop invocation as an "os-sdlc-internal spawn" via the rbenv shim, verified indifferent to bundled ENV, and deliberately not routed through ProjectShell. Moving to RuboCop::Runner/RuboCop::CLI in-process would reverse that premise and needs a superseding/amending ADR if adopted -- per this repo's rule that an Accepted ADR is never silently reversed. /os-adr:find on both files below confirms ADR-0191 is the relevant record; ADR-0066 (rubocop config generation) and ADR-0205 (env hashes over process-global writes) are adjacent background.

Observed

Both call sites shell out to the rubocop binary once per invocation:

plugins/os-sdlc/lib/os_sdlc/lint/rubocop_report_fetcher.rb:17
Open3.capture3("rubocop", "--format", "json", "--config", config_path, *Array(scan_paths))

plugins/os-sdlc/lib/os_sdlc/hook/lint_changed/autocorrect.rb:15
Open3.capture3("rubocop", "-a", *@paths, chdir: @root)

rubocop is a bundled gem, so RuboCop::Runner/RuboCop::CLI could run in the same process, saving a Ruby boot. Caveat: an in-process call still loads all cops; stop_rubocop_server in plugins/os/lib/worktree/finish_command.rb:111 already stops the rubocop server at worktree finish, which suggests the rubocop server (not a bare CLI boot) already removes most of the cop-loading cost the in-process change would target.

Reproduce

cd plugins/os-sdlc && time ruby -e 'require "open3"; Open3.capture3("rubocop", "--format", "json", "--config", "...", "lib")'
cd plugins/os-sdlc && time ruby -e 'require "rubocop"; RuboCop::CLI.new.run(["--format", "json", "--config", "...", "lib"])'

Expected

One prose sentence: the ticket measures wall time for both the subprocess path and an in-process RuboCop::Runner/RuboCop::CLI path (with and without the rubocop server running) and changes the implementation only if the in-process gain is real; either way the measurement is recorded on the ticket.

Origin

  • Trigger: shell-call/ENV audit, session 16bf37b3-8cff-4121-896c-4a5fc61f2e9e (2026-09-23), user-requested.
  • Improvised this session: none
  • Chain: possible extra Ruby-boot cost in lint gates <- Open3.capture3 shells out to the rubocop binary <- DESIGN, ADR-0191 chose the rbenv-shim subprocess path deliberately; unmeasured whether in-process saves anything once the rubocop server is already running.
  • Root candidate: this ticket
  • Where: plugins/os-sdlc/lib/os_sdlc/lint/rubocop_report_fetcher.rb:17; plugins/os-sdlc/lib/os_sdlc/hook/lint_changed/autocorrect.rb:15
  • Session: 16bf37b3-8cff-4121-896c-4a5fc61f2e9e
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/16bf37b3-8cff-4121-896c-4a5fc61f2e9e.jsonl

Acceptance criteria

  • Read ADR-0191 in full and confirm whether it applies before changing anything.
  • Wall-time measurement recorded for the subprocess path and an in-process RuboCop::Runner/RuboCop::CLI path, both with and without a warm rubocop server.
  • If the in-process gain is real and adopted, an ADR is created via /os-adr:create that supersedes or amends ADR-0191.
  • If the gain is not real, the ticket closes with the measurement recorded and no code change.
## Context User requested during the shell-call/ENV audit in session 16bf37b3-8cff-4121-896c-4a5fc61f2e9e. This is a measure-then-decide spike, not a committed change: ADR-0191 (Accepted, 2026-09-15) already classifies rubocop invocation as an "os-sdlc-internal spawn" via the rbenv shim, verified indifferent to bundled ENV, and deliberately not routed through ProjectShell. Moving to `RuboCop::Runner`/`RuboCop::CLI` in-process would reverse that premise and needs a superseding/amending ADR if adopted -- per this repo's rule that an Accepted ADR is never silently reversed. /os-adr:find on both files below confirms ADR-0191 is the relevant record; ADR-0066 (rubocop config generation) and ADR-0205 (env hashes over process-global writes) are adjacent background. ## Observed Both call sites shell out to the rubocop binary once per invocation: ``` plugins/os-sdlc/lib/os_sdlc/lint/rubocop_report_fetcher.rb:17 Open3.capture3("rubocop", "--format", "json", "--config", config_path, *Array(scan_paths)) plugins/os-sdlc/lib/os_sdlc/hook/lint_changed/autocorrect.rb:15 Open3.capture3("rubocop", "-a", *@paths, chdir: @root) ``` rubocop is a bundled gem, so RuboCop::Runner/RuboCop::CLI could run in the same process, saving a Ruby boot. Caveat: an in-process call still loads all cops; `stop_rubocop_server` in plugins/os/lib/worktree/finish_command.rb:111 already stops the rubocop server at worktree finish, which suggests the rubocop server (not a bare CLI boot) already removes most of the cop-loading cost the in-process change would target. ## Reproduce ``` cd plugins/os-sdlc && time ruby -e 'require "open3"; Open3.capture3("rubocop", "--format", "json", "--config", "...", "lib")' cd plugins/os-sdlc && time ruby -e 'require "rubocop"; RuboCop::CLI.new.run(["--format", "json", "--config", "...", "lib"])' ``` ## Expected One prose sentence: the ticket measures wall time for both the subprocess path and an in-process RuboCop::Runner/RuboCop::CLI path (with and without the rubocop server running) and changes the implementation only if the in-process gain is real; either way the measurement is recorded on the ticket. ## Origin - Trigger: shell-call/ENV audit, session 16bf37b3-8cff-4121-896c-4a5fc61f2e9e (2026-09-23), user-requested. - Improvised this session: none - Chain: possible extra Ruby-boot cost in lint gates <- Open3.capture3 shells out to the rubocop binary <- DESIGN, ADR-0191 chose the rbenv-shim subprocess path deliberately; unmeasured whether in-process saves anything once the rubocop server is already running. - Root candidate: this ticket - Where: plugins/os-sdlc/lib/os_sdlc/lint/rubocop_report_fetcher.rb:17; plugins/os-sdlc/lib/os_sdlc/hook/lint_changed/autocorrect.rb:15 - Session: 16bf37b3-8cff-4121-896c-4a5fc61f2e9e - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/16bf37b3-8cff-4121-896c-4a5fc61f2e9e.jsonl ## Acceptance criteria - [ ] Read ADR-0191 in full and confirm whether it applies before changing anything. - [ ] Wall-time measurement recorded for the subprocess path and an in-process RuboCop::Runner/RuboCop::CLI path, both with and without a warm rubocop server. - [ ] If the in-process gain is real and adopted, an ADR is created via /os-adr:create that supersedes or amends ADR-0191. - [ ] If the gain is not real, the ticket closes with the measurement recorded and no code change.
Author
Owner

Resolution

Done: Measured on 2026-09-24 in plugins/os-sdlc against lib/, 3 runs each, medians in seconds: subprocess bundle exec rubocop --format json cold 0.92 / warm server 0.73; in-process RuboCop::CLI.new.run cold 0.98 / warm server 0.94. In-process is never faster. The warm server only helps the rubocop client binary; the Ruby API does not use it. No code change; ADR-0191 stands.

Evidence: Timing table above, plus the in-process script shape (require "rubocop"; $stdout = StringIO.new; RuboCop::CLI.new.run(["--format","json","--config",".rubocop.yml","lib"])), and the note that an adopted in-process path would also need $stdout redirection, which ADR-0205 forbids for production code.

Follow-ups: none

## Resolution **Done:** Measured on 2026-09-24 in plugins/os-sdlc against lib/, 3 runs each, medians in seconds: subprocess `bundle exec rubocop --format json` cold 0.92 / warm server 0.73; in-process `RuboCop::CLI.new.run` cold 0.98 / warm server 0.94. In-process is never faster. The warm server only helps the `rubocop` client binary; the Ruby API does not use it. No code change; ADR-0191 stands. **Evidence:** Timing table above, plus the in-process script shape (`require "rubocop"; $stdout = StringIO.new; RuboCop::CLI.new.run(["--format","json","--config",".rubocop.yml","lib"])`), and the note that an adopted in-process path would also need $stdout redirection, which ADR-0205 forbids for production code. **Follow-ups:** none
jared closed this issue 2026-09-24 20:06:32 +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#636
No description provided.