body_fetch.rb resolves os-backlog via PATH, fails naming paths tried (#635a) #702

Closed
opened 2026-09-24 20:10:10 +00:00 by jared · 2 comments
Owner

Problem

body_fetch.rb resolves the os-backlog binary by a hardcoded relative path. If that path is missing, it silently falls back to the bare string "os-backlog". Plugins install to ~/.claude/plugins/<name>/bin, and Claude Code puts those directories on PATH. The hardcoded sibling path is the path that fires in a source checkout, not an installed plugin. ADR-0213 forbids any new ENV read in plugins/os-sdlc/lib, so the fix must resolve os-backlog through PATH only, not a new environment-variable override.

Observed

Verified on main today (2026-09-24).

plugins/os-sdlc/lib/os_sdlc/adapter/tea_tracker/body_fetch.rb:22-25 os_backlog_bin hardcodes File.expand_path("../../../../os-backlog/bin/os-backlog", __dir__) and silently falls back to the bare string "os-backlog" if that path is missing. Sole caller is TeaTracker#fetch_body (tea_tracker.rb:37), which wraps any StandardError into HarnessError. No behavior test pins the current fallback.

Reproduce

grep -n 'expand_path("../../../../' plugins/os-sdlc/lib

Approach

Add OsSdlc::Adapter::BinaryLocator. It searches PATH entries for os-backlog; on a miss it raises a named error listing every PATH entry it tried, plus the sibling-checkout candidate tried after PATH.

Required work:

  1. Add OsSdlc::Adapter::BinaryLocator and use it in body_fetch.rb in place of the hardcoded expand_path plus silent string fallback — check: grep -n 'expand_path("../../../../' plugins/os-sdlc/lib returns nothing.
  2. On locator miss, raise a named error listing every PATH entry tried plus the sibling-checkout candidate — check: a forced-miss test asserts the error message names the tried paths.

Rejected: an ENV variable override such as OS_BACKLOG_BIN (forbidden by ADR-0213). Merging the plugins' load paths (this is a deliberate process boundary, not an accident to fix by removing it).

Non-goals: changing what happens when body-fetch legitimately fails for a reason other than a missing binary. No ADR change — this is a defect fix within a boundary ADR-0213 already governs.

Constraint: ADR-0213 forbids any new ENV read in plugins/os-sdlc/lib.

VERDICT: CREATE

Acceptance criteria

  1. Given os-backlog is on PATH, when body_fetch.rb resolves its binary, then it uses the PATH match, not the hardcoded relative path.
  2. Given os-backlog is on neither PATH nor the sibling checkout location, when body_fetch.rb resolves its binary, then it raises an error naming every location it tried, rather than silently falling back to a bare unresolved string.

Owner sketch

module OsSdlc
  module Adapter
    class BinaryLocator
      def self.resolve(name, fallback_paths: [])
        # search ENV["PATH"] entries, then fallback_paths, else raise NotFound listing all tried
      end
    end
  end
end

Non-goals

  • Merging plugin load paths.
  • Any environment-variable override for binary location (forbidden by ADR-0213).

Origin

  • Trigger: shell-call/ENV audit, session 16bf37b3-8cff-4121-896c-4a5fc61f2e9e (2026-09-23), user-requested.
  • Improvised this session: none.
  • Chain: fragile cross-plugin binary lookup ← hardcoded relative path instead of a resolved, injectable path ← DESIGN, plugins are separate load paths by design (not a bug to fix by merging them).
  • Root candidate: #635 (split 2026-09-24, session 082c3e8b).
  • Where: plugins/os-sdlc/lib/os_sdlc/adapter/tea_tracker/body_fetch.rb:24
  • Session: 082c3e8b-5598-4c69-9ea6-b717e9c8afb2
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/082c3e8b-5598-4c69-9ea6-b717e9c8afb2.jsonl
## Problem `body_fetch.rb` resolves the `os-backlog` binary by a hardcoded relative path. If that path is missing, it silently falls back to the bare string `"os-backlog"`. Plugins install to `~/.claude/plugins/<name>/bin`, and Claude Code puts those directories on PATH. The hardcoded sibling path is the path that fires in a source checkout, not an installed plugin. ADR-0213 forbids any new ENV read in `plugins/os-sdlc/lib`, so the fix must resolve `os-backlog` through PATH only, not a new environment-variable override. ## Observed Verified on main today (2026-09-24). `plugins/os-sdlc/lib/os_sdlc/adapter/tea_tracker/body_fetch.rb:22-25` `os_backlog_bin` hardcodes `File.expand_path("../../../../os-backlog/bin/os-backlog", __dir__)` and silently falls back to the bare string `"os-backlog"` if that path is missing. Sole caller is `TeaTracker#fetch_body` (`tea_tracker.rb:37`), which wraps any `StandardError` into `HarnessError`. No behavior test pins the current fallback. ## Reproduce ``` grep -n 'expand_path("../../../../' plugins/os-sdlc/lib ``` ## Approach Add `OsSdlc::Adapter::BinaryLocator`. It searches PATH entries for `os-backlog`; on a miss it raises a named error listing every PATH entry it tried, plus the sibling-checkout candidate tried after PATH. Required work: 1. Add `OsSdlc::Adapter::BinaryLocator` and use it in `body_fetch.rb` in place of the hardcoded `expand_path` plus silent string fallback — check: `grep -n 'expand_path("../../../../' plugins/os-sdlc/lib` returns nothing. 2. On locator miss, raise a named error listing every PATH entry tried plus the sibling-checkout candidate — check: a forced-miss test asserts the error message names the tried paths. Rejected: an ENV variable override such as `OS_BACKLOG_BIN` (forbidden by ADR-0213). Merging the plugins' load paths (this is a deliberate process boundary, not an accident to fix by removing it). Non-goals: changing what happens when body-fetch legitimately fails for a reason other than a missing binary. No ADR change — this is a defect fix within a boundary ADR-0213 already governs. Constraint: ADR-0213 forbids any new ENV read in `plugins/os-sdlc/lib`. VERDICT: CREATE ## Acceptance criteria 1. Given os-backlog is on PATH, when `body_fetch.rb` resolves its binary, then it uses the PATH match, not the hardcoded relative path. 2. Given os-backlog is on neither PATH nor the sibling checkout location, when `body_fetch.rb` resolves its binary, then it raises an error naming every location it tried, rather than silently falling back to a bare unresolved string. ## Owner sketch ```ruby module OsSdlc module Adapter class BinaryLocator def self.resolve(name, fallback_paths: []) # search ENV["PATH"] entries, then fallback_paths, else raise NotFound listing all tried end end end end ``` ## Non-goals - Merging plugin load paths. - Any environment-variable override for binary location (forbidden by ADR-0213). ## Origin - Trigger: shell-call/ENV audit, session 16bf37b3-8cff-4121-896c-4a5fc61f2e9e (2026-09-23), user-requested. - Improvised this session: none. - Chain: fragile cross-plugin binary lookup ← hardcoded relative path instead of a resolved, injectable path ← DESIGN, plugins are separate load paths by design (not a bug to fix by merging them). - Root candidate: #635 (split 2026-09-24, session 082c3e8b). - Where: plugins/os-sdlc/lib/os_sdlc/adapter/tea_tracker/body_fetch.rb:24 - Session: 082c3e8b-5598-4c69-9ea6-b717e9c8afb2 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/082c3e8b-5598-4c69-9ea6-b717e9c8afb2.jsonl
Author
Owner

Work started in session 082c3e8b on branch ticket-702 via /os-sdlc:implement.

Work started in session 082c3e8b on branch ticket-702 via /os-sdlc:implement.
Author
Owner

Resolution

Done: BodyFetch resolves os-backlog through OsSdlc::Adapter::BinaryLocator: PATH entries from the invocation env first, then the sibling checkout candidate; a miss raises BinaryLocator::NotFound naming every path tried, wrapped to HarnessError by TeaTracker. The hardcoded expand_path and the silent bare-string fallback are gone. No ENV read added: TeaTracker.for takes env: from the CLI Invocation (ADR-0213 respected).

Evidence: Commit 5c775d42 on branch ticket-702. Pipeline implementation 31, 12 dispatches; escalated at test-writer (unexpected pass) because the verifier correctly found the sibling fallback was never wired from TeaTracker; hand-finished per escalation-procedure step 4. Tests: tests/adapter/binary_locator_test.rb, tests/adapter/tea_tracker_binary_locator_test.rb (4 cases incl. default-locator miss naming both candidates), tests/adapter/tea_tracker/body_fetch_test.rb. os-sdlc suite 1424 runs, 1 failure = pre-existing fork-guard red on main (#705). rubocop clean on all changed files.

Follow-ups: #705 captured (pre-existing own_binary_fork_guard red on main, VERDICT: CREATE). Dropped: lsp_probe/daemon/spawner.rb sibling expand_path is os-sdlc's own bin, not a cross-plugin lookup.

## Resolution **Done:** BodyFetch resolves os-backlog through OsSdlc::Adapter::BinaryLocator: PATH entries from the invocation env first, then the sibling checkout candidate; a miss raises BinaryLocator::NotFound naming every path tried, wrapped to HarnessError by TeaTracker. The hardcoded expand_path and the silent bare-string fallback are gone. No ENV read added: TeaTracker.for takes env: from the CLI Invocation (ADR-0213 respected). **Evidence:** Commit 5c775d42 on branch ticket-702. Pipeline implementation 31, 12 dispatches; escalated at test-writer (unexpected pass) because the verifier correctly found the sibling fallback was never wired from TeaTracker; hand-finished per escalation-procedure step 4. Tests: tests/adapter/binary_locator_test.rb, tests/adapter/tea_tracker_binary_locator_test.rb (4 cases incl. default-locator miss naming both candidates), tests/adapter/tea_tracker/body_fetch_test.rb. os-sdlc suite 1424 runs, 1 failure = pre-existing fork-guard red on main (#705). rubocop clean on all changed files. **Follow-ups:** #705 captured (pre-existing own_binary_fork_guard red on main, VERDICT: CREATE). Dropped: lsp_probe/daemon/spawner.rb sibling expand_path is os-sdlc's own bin, not a cross-plugin lookup.
jared closed this issue 2026-09-24 20:42: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#702
No description provided.