Flag exit, exit!, and abort outside a bin/ entry point #1402
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#1402
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?
Problem
An exit code is the boundary signal for a process. It is not a good interface. Ruby library code that calls
exitreduces what went wrong to one bit and forces the caller to be a shell. No custom cop flagsexit,exit!, oraborttoday.rubocop-rails(Rails/Exit) is not installed.Measured on 2026-10-26 with ast-grep and grep over
plugins/*/libandplugins/*/bin:exitcalls inplugins/*/bin: 51 lines in 46 files. All are entry points (a process boundary).exit,exit!,abort,Kernel.exit,Process.exitcalls inplugins/*/lib: 14. All are non-entry.abortcalls: 0.Kernel.exitandProcess.exitcalls: 0.plugins/os-sdlc/libnon-entry calls (3, in scope for this cop):plugins/os-sdlc/lib/os_sdlc/daemon/server.rb:48(exit!(0)inside a TERM trap)plugins/os-sdlc/lib/os_sdlc/evidence/collection.rb:31(exit 1 if report.all_phantom?)plugins/os-sdlc/lib/os_sdlc/lint/remediation.rb:66(exit 1inreport_failure)plugins/os-backlog/lib/backlog/cli_exit.rb:7,cli_startup.rb:11,decision_sweep/command.rb:21,cli/project_commands.rb:52,cli/issue_commands.rb:103plugins/os/lib/handoff/cli.rb:41,plugins/os/lib/worktree/cli.rb:24,plugins/os/lib/worktree/cli.rb:31plugins/os-me/lib/os_me/cli.rb:24,:27,:39The os-sdlc boot path already follows the rule. Each
plugins/os-sdlc/bin/*stub runsexit OsSdlc::Boot.run("<name>", OsSdlc::ProcessIO.from_process, ARGV).Boot.runreturns an integer and never exits.Approach
An entry point is a file whose path has a
bin/directory segment. Library code is every other file. A call is an offense when its receiver is absent,Kernel, orProcess, its method isexit,exit!, orabort, and its file is not an entry point. A call with another receiver (for example@walked.exit(name)inmap/check/cycle_walk.rb) is not an offense. Adef exitis not a call.Required work:
Sdlc/Structural/ExitOutsideEntryPoint— check: presentclass ExitOutsideEntryPointinplugins/os-sdlc/lib/os_sdlc/cops/structural/exit_outside_entry_point.rbExitOutsideEntryPointTestinplugins/os-sdlc/tests/cops/exit_outside_entry_point_test.rbSdlc/Structural/ExitOutsideEntryPointinplugins/os-sdlc/.rubocop.ymlplugins/os-sdlc/lib/os_sdlc/evidence/collection.rb:31andplugins/os-sdlc/lib/os_sdlc/lint/remediation.rb:66: return the status to the bin stub, which callsexit. — check: absentexit 1inplugins/os-sdlc/lib/os_sdlc/evidence/collection.rbplugins/os-sdlc/lib/os_sdlc/daemon/server.rb:48: a TERM trap in a forked daemon has no caller to return to. Replaceexit!(0)with a form the cop accepts, or record the reason in a one-line comment and arubocop:disablefor this cop on that line. — check: absentexit!(0)inplugins/os-sdlc/lib/os_sdlc/daemon/server.rbRejected: install
rubocop-railsforRails/Exit— the project is not Rails, and the stock cop does not knowbin/stubs.Acceptance criteria
lib/with a bareexit 1, when the cop runs, then it reports one offense on that call.lib/withexit!(0),abort("x"),Kernel.exit(1), orProcess.exit(1), when the cop runs, then it reports one offense for each call.bin/withexit Boot.run(...), when the cop runs, then it reports no offense.lib/with@walked.exit(name)ordef exit(name), when the cop runs, then it reports no offense.rubocopruns withplugins/os-sdlc/.rubocop.yml, then the cop reports no offense underplugins/os-sdlc/lib.bin/evidence collectfinds only phantom tokens, when it runs, then it still exits 1.Owner sketch
Non-goals
Offenses in
plugins/os,plugins/os-backlog, andplugins/os-me. Those plugins do not load this cop. The 11 calls above are a follow-up ticket per plugin.Origin
exit← no cop flags it ←rubocop-railsis not installed and no custom cop exists. Source:plugins/os-sdlc/lib/os_sdlc/cops/structural/(DESIGN).plugins/os-sdlc/lib/os_sdlc/cops/structural/,plugins/os-sdlc/.rubocop.ymlVERDICT: CREATE
Work started on branch ticket-1402 via /os-sdlc:implement.
Resolution
Done: Sdlc/Structural/ExitOutsideEntryPoint added and enabled in plugins/os-sdlc/.rubocop.yml: exit/exit!/abort (bare, Kernel., Process.) are offenses outside an entry point (a #! file or a file in a bin directory); exit! inside a fork block is allowed. Fixed os-sdlc sites: daemon/server.rb TERM trap (raise SystemExit), evidence/collection.rb (Thor::Error), lint/remediation.rb (returns 1), takedown prepare_cli.rb, tests/support/in_process_bin.rb. Also fixed a #1393 regression found by this ticket's gate: two copies of os_sdlc_cops_loader.rb in one RuboCop process (gate inherits main config then worktree config) kept main's cops; now the last copy unregisters and unloads the first and loads its own.
Evidence: main
367156a8(commits44899722,367156a8). os-sdlc suite 2719 runs 0 failures; takedown suite 615 runs 0 failures; new cop: 1825 files, no offenses; changed files lint clean; loader tests cover a new cop, an edited cop, and a deleted cop across two copies. Implementation 175 hand-finished after bound_exhausted escalation (lint gate blocked by the loader regression), then abandoned.Follow-ups: #1411 captured (skeptic CREATE): apply the cop to os, os-backlog, os-me (11 sites; Thor CLIs need a status-returning run).