os-sdlc-runner: Pipeline::CLI receives an Invocation; bin/os-sdlc-runner is the only reader of process globals #638
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#638
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?
Map: #631
Context
Child of #631. Absorbs the in-process test conversion and the per-binary smoke test from #628; #628 keeps only the template-DB copy. Most Cli*Test tests already run in-process since
3871ecb5(2026-08-23) through the test-sideInProcessRunnerCli(plugins/os-sdlc/tests/support/in_process_runner_cli.rb). That helper bypasses Thor with its own copy of the command table: it has noimplementation-close,retired-tests-check,violations, orimplementation-report, and it drops every option such as--target. The tests for those commands still fork the binary.Observed
OsSdlc::Pipeline::CLI(plugins/os-sdlc/lib/os_sdlc/pipeline/cli.rb) reads process globals on 12 lines:Dir.pwdat 31, 39, 51, 54, 59, 64, 71, 76, 87, 91 and$stdinat 37, 45, 51. Its output usesputs/warnon the process streams (46, 65, 76, 97;startrescue at 23).Test methods that still fork bin/os-sdlc-runner:
Measured 2026-09-23:
test_next_routes_a_pass_verdict_to_the_maps_next_nodetakes 0.33 s alone and 1.4-1.8 s when its file runs in parallel. Most of the per-test cost in CliNextInstructionTest is in-process contention or per-test DB setup, not forks.Reproduce
Expected
Origin
3871ecb5)Owner sketch
Cases
Pipeline::CLI.start(["implementation-open", "session-1", "442", map], invocation:)runs, then the implementation row is written to that project's pipeline DB.invocation.stdout, the process$stdoutreceives nothing, and the exit status is 0.implementation-open --target os-backlogruns through the seam, then the implementation row records targetos-backlog.invocation.stdin, whensubagent-stopruns, then the dispatch records the verdict from that payload.invocation.stdinand no argv, whennextruns, then its output equals the output ofnext session-1for the same session.next does-not-existruns through the seam, theninvocation.stderrholds one line that starts withos-sdlc-runner: harness error:and the exit status is 3.bin/os-sdlc-runner implementation-openruns as a subprocess, then it exits 0 and prints JSON with the session id on stdout.Dir.pwdand$stdin, then there is no match.Retired tests
plugins/os-sdlc/tests/pipeline/cli_next_instruction_test.rb#test_smoke_next_boots_the_real_binary_and_returns_json_on_stdout
plugins/os-sdlc/tests/pipeline/cli_next_instruction_test.rb#test_smoke_next_exits_nonzero_over_a_real_process_on_an_unknown_session
plugins/os-sdlc/tests/pipeline/cli_next_instruction_test.rb#test_smoke_next_returns_the_same_json_across_repeated_real_process_calls
plugins/os-sdlc/tests/pipeline/cli_next_instruction_test.rb#test_smoke_next_from_a_stdin_event_matches_the_argv_form
plugins/os-sdlc/tests/pipeline/cli_subagent_start_test.rb#test_smoke_subagent_start_boots_the_real_binary_and_returns_a_dispatch_brief
plugins/os-sdlc/tests/pipeline/cli_subagent_start_test.rb#test_smoke_subagent_start_exits_nonzero_over_a_real_process_without_an_implementation
plugins/os-sdlc/tests/pipeline/cli_subagent_start_test.rb#test_smoke_subagent_start_creates_a_real_dispatch_row_via_the_real_process
plugins/os-sdlc/tests/pipeline/cli_subagent_stop_test.rb#test_smoke_subagent_stop_boots_the_real_binary_and_records_a_handoff
plugins/os-sdlc/tests/pipeline/cli_subagent_stop_test.rb#test_smoke_subagent_stop_exits_nonzero_over_a_real_process_on_an_unknown_dispatch
plugins/os-sdlc/tests/pipeline/cli_subagent_stop_test.rb#test_smoke_subagent_stop_records_a_real_verdict_via_the_real_process
plugins/os-sdlc/tests/pipeline/gate_cli_test.rb#test_smoke_gate_boots_the_real_binary_and_stamps_a_pass_verdict
plugins/os-sdlc/tests/pipeline/gate_cli_test.rb#test_smoke_gate_exits_nonzero_over_a_real_process_on_an_unknown_dispatch
plugins/os-sdlc/tests/pipeline/gate_cli_test.rb#test_smoke_gate_stamps_a_real_fail_verdict_via_the_real_process
Approach
Dir.pwdwith@invocation.cwd, every$stdinwith@invocation.stdin, and routeputs/warnthrough@invocation.stdout/stderr.start's rescue writes toconfig[:invocation].stderr. Keep theexitcalls: in-process callers rescue SystemExit.OsSdlc::CLI::Invocation, so the pipeline slice does not depend on the cli slice (ADR-0196). Only bin/ builds it.InProcessRunnerCli#callto runPipeline::CLI.start(args, invocation:)and delete its private command table. In the four files listed in Observed, change the privaterun_cli/run_sweep_clito callInProcessRunnerCli. Delete the unusedrun_cli_subprocess,run_subagent_start_subprocess,run_stop_subprocesshelpers andBINconstants in tests/support/{cli_next_instruction_helpers,subagent_start_cli,subagent_stop_cli}.rb. Put the single smoke test in tests/os_sdlc_runner_bin_test.rb.envis carried but not read in this ticket; #633 consumes it.Structural follow-through
Work started on branch ticket-638 (worktree .claude/worktrees/ticket-638), via the os-sdlc pipeline map poodr-ticket-implementation.
Follow-up dropped by ticket-skeptic (depth-1 root-vs-symptom): ADR-0207 already records the DispatchCommands exit_loudly / CLI unknown-subcommand raw-stream residue as known, deferred work in this ticket's own scope; not filed as a new ticket.
Resolution
Done: Pipeline::CLI receives OsSdlc::CLI::Invocation via Thor start config; bin/os-sdlc-runner is the only reader of process globals; InProcessRunnerCli routes through Pipeline::CLI (its private command table deleted); four forking CLI test files run in-process; one real-binary smoke test (tests/os_sdlc_runner_bin_test.rb); ADR-0207 + os-sdlc leaf History row.
Evidence: Branch ticket-638 commit
4b0ef96c(merged to main by worktree finish); suite 1390 runs, 3176 assertions, 0 failures, 34.6 s; rubocop clean on touched files; only the smoke test spawns the binary. Pipeline implementation 13 escalated (unexpected_pass from #629 verifier evidence scope; Approach work unchecked, filed #643); hand-finished by user decision; Opus design review in place of contract-auditor, its one required change (delete duplicate DispatchCommands::HarnessError) applied.Follow-ups: dropped: ticket-skeptic verdict DROP (depth-1 root-vs-symptom) on DispatchCommands exit_loudly / CLI unknown-subcommand Invocation routing, folded back into ADR-0207's known residue; dropped: unused BIN constant in tests/pipeline/cli_brief_test.rb — dead, harmless; hook-test harness forks already owned by #642; related #629, #643.