Replace the which lookup in os-backlog inspect with a PATH check #852

Closed
opened 2026-09-30 17:23:55 +00:00 by jared · 3 comments
Owner

Problem

InspectCommand#available? starts a which process to find out if tea or gh is installed. It uses only the success flag. Ruby can do the same check by reading PATH, without a process and without a dependency on which itself.

plugins/os-backlog/lib/backlog/inspect_command.rb:37-40
  def available?(tool)
    _out, ok = @runner.capture("which", tool)
    ok
  end

Callers: tea_issues (line 77) and gh_issues (line 102). Each returns an "unavailable" result when available? is false.

Reproduce

n/a: behavior change with no defect to reproduce.

Approach

A small value object answers "is this executable on this PATH?". InspectCommand receives it through its constructor, so tests give it a fixed PATH in place of a fake which reply.

Required work: none beyond the acceptance criteria.

Rejected: keep which behind the runner — it is a process for a lookup that File.executable? does.

Acceptance criteria

  1. Given a PATH with a directory that holds an executable tea, when inspect runs, then the findings report tea as available.
  2. Given a PATH with no tea executable, when inspect runs, then the findings report tea as unavailable.
  3. Given a PATH with a file named tea that is not executable, when inspect runs, then the findings report tea as unavailable.
  4. Given any inspect run, when it completes, then the runner received no which command.

Owner sketch

class Backlog::ExecutablePath
  def initialize(path = ENV.fetch("PATH", ""))
    @dirs = path.split(File::PATH_SEPARATOR)
  end

  def include?(tool)
    @dirs.any? { |dir| File.executable?(File.join(dir, tool)) }
  end
end

class Backlog::InspectCommand
  def initialize(repo_root:, runner:, executables: ExecutablePath.new)
  def available?(tool) = @executables.include?(tool)
end

Non-goals

  • The git remote -v call in the same file. It has its own ticket (git gem).
  • Replacing tea/gh with a REST client.

Origin

  • Trigger: #845 shell-call audit, 2026-09-30.
  • Improvised this session: none
  • Chain: a process starts for a PATH lookup ← available? shells out to which (inspect_command.rb:38) ← IMPROVISATION: no recorded reason to use which.
  • Root candidate: this ticket
  • Where: plugins/os-backlog/lib/backlog/inspect_command.rb
  • Session: f090dd39-8ad6-4f4e-8743-7bbedb8f08d4
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/f090dd39-8ad6-4f4e-8743-7bbedb8f08d4.jsonl
## Problem `InspectCommand#available?` starts a `which` process to find out if `tea` or `gh` is installed. It uses only the success flag. Ruby can do the same check by reading `PATH`, without a process and without a dependency on `which` itself. ``` plugins/os-backlog/lib/backlog/inspect_command.rb:37-40 def available?(tool) _out, ok = @runner.capture("which", tool) ok end ``` Callers: `tea_issues` (line 77) and `gh_issues` (line 102). Each returns an "unavailable" result when `available?` is false. ## Reproduce n/a: behavior change with no defect to reproduce. ## Approach A small value object answers "is this executable on this PATH?". `InspectCommand` receives it through its constructor, so tests give it a fixed PATH in place of a fake `which` reply. Required work: none beyond the acceptance criteria. Rejected: keep `which` behind the runner — it is a process for a lookup that `File.executable?` does. ## Acceptance criteria 1. Given a PATH with a directory that holds an executable `tea`, when inspect runs, then the findings report tea as available. 2. Given a PATH with no `tea` executable, when inspect runs, then the findings report tea as unavailable. 3. Given a PATH with a file named `tea` that is not executable, when inspect runs, then the findings report tea as unavailable. 4. Given any inspect run, when it completes, then the runner received no `which` command. ## Owner sketch ```ruby class Backlog::ExecutablePath def initialize(path = ENV.fetch("PATH", "")) @dirs = path.split(File::PATH_SEPARATOR) end def include?(tool) @dirs.any? { |dir| File.executable?(File.join(dir, tool)) } end end class Backlog::InspectCommand def initialize(repo_root:, runner:, executables: ExecutablePath.new) def available?(tool) = @executables.include?(tool) end ``` ## Non-goals - The `git remote -v` call in the same file. It has its own ticket (git gem). - Replacing tea/gh with a REST client. ## Origin - Trigger: #845 shell-call audit, 2026-09-30. - Improvised this session: none - Chain: a process starts for a PATH lookup ← `available?` shells out to `which` (inspect_command.rb:38) ← IMPROVISATION: no recorded reason to use `which`. - Root candidate: this ticket - Where: plugins/os-backlog/lib/backlog/inspect_command.rb - Session: f090dd39-8ad6-4f4e-8743-7bbedb8f08d4 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/f090dd39-8ad6-4f4e-8743-7bbedb8f08d4.jsonl
Author
Owner

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

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

Skeptic DROP (one observed case): the ticket map has no node to fix a pre-existing test that a correct change exposes; contract-auditor returned error and implementation 16 failed. Hand-fixed in 677a7824. Reopen as a ticket if the pattern recurs.

Skeptic DROP (one observed case): the ticket map has no node to fix a pre-existing test that a correct change exposes; contract-auditor returned error and implementation 16 failed. Hand-fixed in 677a7824. Reopen as a ticket if the pattern recurs.
Author
Owner

Resolution

Done: InspectCommand finds tea/gh through an injected Backlog::ExecutablePath (PATH lookup, File.executable?); no which process starts. Existing inspect tests build a fixed ExecutablePath, so none depends on the host PATH.

Evidence: Main b44adce8 (commit 677a7824). Implementation 16: all gates passed through suite-check; contract-auditor returned error because the remaining fix was test-only; hand-finished. os-backlog suite 275 runs, 0 failures, with normal PATH and with a PATH that has no tea; rubocop clean on changed files.

Follow-ups: #854 (worktree guard refuses stop payloads that contain the word git). Exposed-test map gap: skeptic DROP, noted in a comment on this ticket.

## Resolution **Done:** InspectCommand finds tea/gh through an injected Backlog::ExecutablePath (PATH lookup, File.executable?); no which process starts. Existing inspect tests build a fixed ExecutablePath, so none depends on the host PATH. **Evidence:** Main b44adce8 (commit 677a7824). Implementation 16: all gates passed through suite-check; contract-auditor returned error because the remaining fix was test-only; hand-finished. os-backlog suite 275 runs, 0 failures, with normal PATH and with a PATH that has no tea; rubocop clean on changed files. **Follow-ups:** #854 (worktree guard refuses stop payloads that contain the word git). Exposed-test map gap: skeptic DROP, noted in a comment on this ticket.
jared closed this issue 2026-09-30 17:59: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#852
No description provided.