os-sdlc: setup path raises LoudFailure; core stops calling exit and warn #830

Closed
opened 2026-09-29 16:15:40 +00:00 by jared · 1 comment
Owner

Problem

The setup path fails through its own route.
SetupCLI warns and calls exit, while every other controller rescues a raised LoudFailure.
Core code calls exit and warn directly, so core cannot run without ending the process.
User ruling on the #781 follow-up (b), option A extended, 2026-09-29.
Amends ADR-0236.
Depends on #829 (same files).

Reproduce

n/a: design change, nothing to reproduce.

Approach

Paths are relative to plugins/os-sdlc/lib/os_sdlc/.
Current sites:

  • presenter/command_support.rb:7-9 fail_soft (warn "os-sdlc: msg"; exit code), called only from command/init_command.rb:27,33.
  • A private fail_soft copy in command/cli/project_config_command.rb:29 (after #829: command/project/config_command.rb).
  • exit 0 at project_config_command.rb:39,73 and init_command.rb:13.
  • Advisory warn at project_config_command.rb:59.
  • controller/project/setup_cli.rb: handle_no_command_error warns and exits 1, and start rescues StandardError with its own loud line.

Required work:

  1. Core raises LoudFailure on failure and returns normally on success. Remove every exit and warn call from core. — check: grep -rn "exit\|warn" plugins/os-sdlc/lib/os_sdlc/command
  2. Delete CommandSupport and the private fail_soft copy.
  3. SetupCLI wraps its run in ProcessIO#guarded (or loudly), so it has one failure path.
  4. handle_no_command_error raises LoudFailure.
  5. The advisory warn and the success output write through the injected invocation IO. InitCommand gains the invocation.
  6. Re-pin tests/controller/project/cli_decomposition_unknown_subcommand_contract_test.rb (exit 1 to 3), cli_decomposition_harness_error_contract_test.rb (regex), and cli_lint_init_seam_test.rb.
  7. Remove the SystemExit rescues: capture_exit in cli_project_config_test.rb, and project_test_path_default_test.rb.

Accepted behavior change (user ruled): stderr bytes follow LoudFailure#stderr_line (os-sdlc-), and every failure exits 3.

Acceptance criteria

  1. Given a setup command that fails, when it runs, then stderr follows LoudFailure#stderr_line and the exit code is 3.
  2. Given an unknown setup subcommand, when it runs, then it fails through LoudFailure with exit code 3.
  3. Given a setup command that succeeds, when it runs, then core returns normally and writes success output through the invocation IO.
  4. Given the source tree, when searched, then CommandSupport does not exist and core calls neither exit nor warn.
  5. Given the change, when the suite runs, then it passes.

Owner sketch

class Project::InitCommand
  def initialize(invocation:)
  def call
    raise LoudFailure, message if failed
  end
end

class Project::SetupCLI
  def self.start(argv)
    ProcessIO.guarded { run(argv) }
  end
end

Origin

  • Trigger: #781 follow-up (b), user ruling.
  • Improvised this session: none
  • Chain: setup path calls exit and warn ← SetupCLI predates the raise-and-rescue path ← DESIGN (ADR-0236)
  • Root candidate: none
  • Where: presenter/command_support.rb, controller/project/setup_cli.rb
  • Session: 61834fd0-8fa2-40d2-8215-fb2f0622220d
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-crc-pass-os-sdlc/61834fd0-8fa2-40d2-8215-fb2f0622220d.jsonl
## Problem The setup path fails through its own route. `SetupCLI` warns and calls `exit`, while every other controller rescues a raised `LoudFailure`. Core code calls `exit` and `warn` directly, so core cannot run without ending the process. User ruling on the #781 follow-up (b), option A extended, 2026-09-29. Amends ADR-0236. Depends on #829 (same files). ## Reproduce n/a: design change, nothing to reproduce. ## Approach Paths are relative to plugins/os-sdlc/lib/os_sdlc/. Current sites: - presenter/command_support.rb:7-9 `fail_soft` (warn "os-sdlc: msg"; exit code), called only from command/init_command.rb:27,33. - A private `fail_soft` copy in command/cli/project_config_command.rb:29 (after #829: command/project/config_command.rb). - `exit 0` at project_config_command.rb:39,73 and init_command.rb:13. - Advisory `warn` at project_config_command.rb:59. - controller/project/setup_cli.rb: `handle_no_command_error` warns and exits 1, and `start` rescues `StandardError` with its own loud line. Required work: 1. Core raises `LoudFailure` on failure and returns normally on success. Remove every `exit` and `warn` call from core. — check: `grep -rn "exit\|warn" plugins/os-sdlc/lib/os_sdlc/command` 2. Delete `CommandSupport` and the private `fail_soft` copy. 3. `SetupCLI` wraps its run in `ProcessIO#guarded` (or loudly), so it has one failure path. 4. `handle_no_command_error` raises `LoudFailure`. 5. The advisory `warn` and the success output write through the injected invocation IO. `InitCommand` gains the invocation. 6. Re-pin tests/controller/project/cli_decomposition_unknown_subcommand_contract_test.rb (exit 1 to 3), cli_decomposition_harness_error_contract_test.rb (regex), and cli_lint_init_seam_test.rb. 7. Remove the `SystemExit` rescues: `capture_exit` in cli_project_config_test.rb, and project_test_path_default_test.rb. Accepted behavior change (user ruled): stderr bytes follow `LoudFailure#stderr_line` (os-sdlc-<message>), and every failure exits 3. ## Acceptance criteria 1. Given a setup command that fails, when it runs, then stderr follows `LoudFailure#stderr_line` and the exit code is 3. 2. Given an unknown setup subcommand, when it runs, then it fails through `LoudFailure` with exit code 3. 3. Given a setup command that succeeds, when it runs, then core returns normally and writes success output through the invocation IO. 4. Given the source tree, when searched, then `CommandSupport` does not exist and core calls neither `exit` nor `warn`. 5. Given the change, when the suite runs, then it passes. ## Owner sketch ```ruby class Project::InitCommand def initialize(invocation:) def call raise LoudFailure, message if failed end end class Project::SetupCLI def self.start(argv) ProcessIO.guarded { run(argv) } end end ``` ## Origin - Trigger: #781 follow-up (b), user ruling. - Improvised this session: none - Chain: setup path calls exit and warn ← SetupCLI predates the raise-and-rescue path ← DESIGN (ADR-0236) - Root candidate: none - Where: presenter/command_support.rb, controller/project/setup_cli.rb - Session: 61834fd0-8fa2-40d2-8215-fb2f0622220d - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-crc-pass-os-sdlc/61834fd0-8fa2-40d2-8215-fb2f0622220d.jsonl
Author
Owner

Resolution

Done: InitCommand and Project::ConfigCommand raise LoudFailure and write through the injected ProcessIO; SetupCLI.start runs Thor inside ProcessIO#guarded; bin/os-sdlc exits with the code; CommandSupport deleted. Failures print os-sdlc- and exit 3 (user-ruled).

Evidence: Commit e6961519 on main; ADR-0246; tests re-pinned red first; suite 1328 runs 0 failures.

Follow-ups: none captured; unknown-subcommand wording and the SetupCLI/ReportCLI/ProbeCLI suffix question recorded in docs/crc/os-sdlc/HANDOFF.md for the user

## Resolution **Done:** InitCommand and Project::ConfigCommand raise LoudFailure and write through the injected ProcessIO; SetupCLI.start runs Thor inside ProcessIO#guarded; bin/os-sdlc exits with the code; CommandSupport deleted. Failures print os-sdlc-<message> and exit 3 (user-ruled). **Evidence:** Commit e6961519 on main; ADR-0246; tests re-pinned red first; suite 1328 runs 0 failures. **Follow-ups:** none captured; unknown-subcommand wording and the SetupCLI/ReportCLI/ProbeCLI suffix question recorded in docs/crc/os-sdlc/HANDOFF.md for the user
jared closed this issue 2026-09-29 16:29: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#830
No description provided.