Skip to content

fix(backends): make a missing adapter file a guardable refusal under set -e - #3512

Open
npayette84 wants to merge 1 commit into
kunchenguid:mainfrom
npayette84:fm/fm-herdr-preflight-missing-adapter-fixture
Open

fix(backends): make a missing adapter file a guardable refusal under set -e#3512
npayette84 wants to merge 1 commit into
kunchenguid:mainfrom
npayette84:fm/fm-herdr-preflight-missing-adapter-fixture

Conversation

@npayette84

Copy link
Copy Markdown
Contributor

Intent

Ship the independently selected correction for the pre-existing macOS Bash 3.2 herdr-preflight-missing-adapter teardown fixture failure: fm_backend_source on stock bash 3.2 under set -e made a failed source of a missing adapter file fatal to the caller shell even inside ||/if guards, so fm-teardown.sh's herdr preflight exited 0 silently through its EXIT trap instead of printing the 'prerequisites are unavailable; nothing was changed' refusal, which is the herdr-preflight-missing-adapter fixture failure in tests/fm-teardown.test.sh. The fix is exactly the judge-selected candidate B (commit e0b5d224): a seven-line existence precheck ([ -f "$FM_BACKEND_LIB_DIR/backends/$name.sh" ] || return 1) inserted in fm_backend_source right after fm_backend_validate, plus its focused library-boundary regression test in tests/fm-backend.test.sh (under set -eu, sourcing with the adapter absent must refuse instead of killing the caller). Requirements and exclusions: do not import candidate A's errexit bracket helper (fm_backend_source_file) because it can misreport a half-loaded adapter as successfully sourced and let destructive teardown cleanup proceed; do not broaden into corrupt, unreadable, or partial-install adapter handling; preserve the existing fail-stop behavior for every non-missing-file failure class; base must be the fresh current upstream/main and the PR targets upstream/main without merging. Already verified before this run: the patch bytes match candidate B exactly; the new regression test is red on the unmodified upstream base (caller shell died rc=1) and green on the branch; and the full prescribed validation set passed on macOS stock bash 3.2.57 (teardown 58 ok including all four preflight refusal modes, backend suite with the new regression, herdr backend suite, afk-launch suite, tmux/cmux smoke rc 0, fm-lint clean, doc-audience-check ok, bash -n parse coverage over bin/.sh and bin/backends/.sh).

What Changed

  • fm_backend_source in bin/fm-backend.sh now checks that $FM_BACKEND_LIB_DIR/backends/$name.sh exists and returns 1 if it does not, right after fm_backend_validate and before any . of the adapter. On bash 3.2 a failed . on a nonexistent file is a fatal special-builtin error even inside || and if guards, so callers running under set -e were exiting instead of taking their refusal path. The precheck means the source only ever runs against a file that is present.
  • Added test_backend_source_missing_adapter_refuses_guardably to tests/fm-backend.test.sh, registered in the suite runner. It sources the library under set -eu, points FM_BACKEND_LIB_DIR at an empty backends directory, and asserts two things: the caller shell survives (rc 0) and the guard reports refused.
  • Behavior for every other failure class is untouched. Only the missing-file case changed.

Risk Assessment

✅ Low: A single guarded existence precheck whose only behavioral delta is turning a fatal missing-file source into a returnable refusal — every known backend has its adapter present so normal paths are unchanged, fail-stop is preserved for all other failure classes, and the change is accompanied by a behavior-level regression test on an unmodified upstream base.

Testing

Ran the two targeted suites on macOS stock bash 3.2.57 - tests/fm-teardown.test.sh (58 ok, rc 0, all four herdr preflight refusal modes including the previously failing missing-adapter fixture) and tests/fm-backend.test.sh (29 ok, rc 0, including the new library-boundary regression) - then produced the end-user evidence by invoking the real bin/fm-teardown.sh --force against an install with the herdr adapter deleted, once with the pre-fix base fm-backend.sh and once with the branch's: pre-fix exits 0 with only a raw "No such file or directory" line, post-fix exits 1 with the visible "prerequisites are unavailable ... nothing was changed" refusal and every record, the task branch and the isolated copy intact with no treehouse return or pane close attempted. A base-vs-branch failure-class probe confirms the change is confined to the missing-file case, with syntax-error, unreadable and nonzero-return adapters retaining identical fail-stop behavior. No UI surface is involved, so the reviewer-visible artifacts are CLI transcripts; the worktree was left clean.

Evidence: Teardown CLI transcript: missing herdr adapter, pre-fix vs post-fix

Source: Teardown CLI transcript: missing herdr adapter, pre-fix vs post-fix

CASE: before-fix (bin/backends/herdr.sh deleted; fm-backend.sh from 763f597) $ bin/fm-teardown.sh task-x1 --force --- stderr --- .../bin/fm-backend.sh: line 609: .../bin/backends/herdr.sh: No such file or directory --- exit code: 0 exit status : 0 (operator is told teardown succeeded) refusal explained : NO (silent) CASE: after-fix (bin/backends/herdr.sh deleted; fm-backend.sh from the branch) $ bin/fm-teardown.sh task-x1 --force --- stderr --- error: herdr teardown prerequisites are unavailable for task-x1; nothing was changed - restore the adapter and rerun teardown --- exit code: 1 exit status : NON-ZERO (operator sees a failure) refusal explained : yes ('nothing was changed' printed) endpoint metadata : preserved task status record : preserved isolated copy (wt) : preserved on fm/task-x1 treehouse return run : no herdr pane close : no

==================================================================
CASE: before-fix   (bin/backends/herdr.sh deleted from the install)
      bin/fm-backend.sh taken from 763f5979a7eb988397292faac372c15d2416ed43 (pre-fix)
$ bin/fm-teardown.sh task-x1 --force
--- stdout ---
--- stderr ---
/private/var/folders/70/p814fs691f103nxddhx58mhh0000gn/T/fm-teardown-tests.DozZtg/manual-before-fix/test-root/bin/fm-backend.sh: line 609: /private/var/folders/70/p814fs691f103nxddhx58mhh0000gn/T/fm-teardown-tests.DozZtg/manual-before-fix/test-root/bin/backends/herdr.sh: No such file or directory
--- exit code: 0
--- observable state after the command ---
  exit status          : 0 (operator is told teardown succeeded)
  refusal explained    : NO (silent)
  endpoint metadata    : preserved
  task status record   : preserved
  isolated copy (wt)   : preserved on fm/task-x1
  treehouse return run : no
  herdr pane close     : no

==================================================================
CASE: after-fix   (bin/backends/herdr.sh deleted from the install)
      bin/fm-backend.sh from the branch under test (fixed)
$ bin/fm-teardown.sh task-x1 --force
--- stdout ---
--- stderr ---
error: herdr teardown prerequisites are unavailable for task-x1; nothing was changed - restore the adapter and rerun teardown
--- exit code: 1
--- observable state after the command ---
  exit status          : NON-ZERO (operator sees a failure)
  refusal explained    : yes ('nothing was changed' printed)
  endpoint metadata    : preserved
  task status record   : preserved
  isolated copy (wt)   : preserved on fm/task-x1
  treehouse return run : no
  herdr pane close     : no
Evidence: Failure-class parity under set -eu on bash 3.2.57 (base vs branch)

Source: Failure-class parity under set -eu on bash 3.2.57 (base vs branch)

bash: 3.2.57(1)-release base ok caller-rc=0 guarded-result=sourced branch ok caller-rc=0 guarded-result=sourced base missing caller-rc=1 guarded-result=<caller shell died before the guard> branch missing caller-rc=0 guarded-result=refused base nonzero caller-rc=0 guarded-result=refused branch nonzero caller-rc=0 guarded-result=refused base syntax caller-rc=2 guarded-result=<caller shell died before the guard> branch syntax caller-rc=2 guarded-result=<caller shell died before the guard> base unreadable caller-rc=1 guarded-result=<caller shell died before the guard> branch unreadable caller-rc=1 guarded-result=<caller shell died before the guard>

bash: 3.2.57(1)-release
base    ok          caller-rc=0   guarded-result=sourced
branch  ok          caller-rc=0   guarded-result=sourced
base    missing     caller-rc=1   guarded-result=<caller shell died before the guard>
branch  missing     caller-rc=0   guarded-result=refused
base    nonzero     caller-rc=0   guarded-result=refused
branch  nonzero     caller-rc=0   guarded-result=refused
base    syntax      caller-rc=2   guarded-result=<caller shell died before the guard>
branch  syntax      caller-rc=2   guarded-result=<caller shell died before the guard>
base    unreadable  caller-rc=1   guarded-result=<caller shell died before the guard>
branch  unreadable  caller-rc=1   guarded-result=<caller shell died before the guard>

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 2 infos
  • ℹ️ tests/fm-backend.test.sh:515 - The regression test sets FM_BACKEND_LIB_DIR to a directory it created and named backends, so the path fm_backend_source actually consults is $TMP_ROOT/missing-adapter-lib/backends/backends/herdr.sh — the mkdir'd directory is the lib dir, not the adapter dir. The case therefore exercises "adapter parent directory absent" rather than the fixture's real shape (populated backends/ dir with only herdr.sh removed). Both fail [ -f ] identically, so the test is valid and still red-before/green-after, but tmpbin=&#34;$TMP_ROOT/missing-adapter-lib&#34;; mkdir -p &#34;$tmpbin/backends&#34; would mirror tests/fm-teardown.test.sh:1560 exactly and prove the precheck keys on the file rather than on the missing directory.
  • ℹ️ bin/backends/herdr.sh:79 - The same silent-exit-0 class remains reachable one level down: bin/backends/herdr.sh:79 and :87 source fm-composer-lib.sh and fm-transition-lib.sh unguarded, so a partial install where the adapter is present but a sibling lib is missing still kills the caller shell under set -e before teardown's refusal prints. This is explicitly excluded by the intent ("do not broaden into corrupt, unreadable, or partial-install adapter handling") and the change correctly preserves fail-stop for that class, so no action is expected here — noting it only as the known remaining boundary of this fix.
✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-teardown.test.sh (58 ok, rc 0; includes herdr-preflight-missing-adapter, missing-parser, missing-explicit-close-helper, unresolvable-lock)
  • bash tests/fm-backend.test.sh (29 ok, rc 0; includes test_backend_source_missing_adapter_refuses_guardably)
  • Manual CLI reproduction: real bin/fm-teardown.sh task-x1 --force run against an install with bin/backends/herdr.sh removed, executed twice - once with base 763f597 bin/fm-backend.sh (pre-fix) and once with the branch's - capturing stdout/stderr, exit code, record survival, treehouse-return and herdr pane-close side effects
  • Failure-class parity probe under set -eu on bash 3.2.57 comparing base vs branch fm_backend_source herdr for present/missing/nonzero-return/syntax-error/unreadable adapter files
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

fm_backend_source attempted . backends/<name>.sh with only an || return 1
guard, but on bash 3.2 a failed source of a nonexistent file is a fatal
special-builtin error under set -e even inside ||/if guards: the whole
caller shell exited with status 0 and its EXIT trap, so fm-teardown.sh's
herdr preflight never reached its 'prerequisites are unavailable;
nothing was changed' refusal and teardown silently reported success
while doing nothing. This is the reproducible herdr-preflight-missing-adapter
fixture failure in tests/fm-teardown.test.sh, failing since the fixture
landed in 66b0f77 on stock macOS bash.

Precheck adapter existence in fm_backend_source - the single owner of
adapter sourcing - so a missing adapter returns 1 guardably for every
backend and every caller, letting each consumer's own refusal fire.
Every other failure class keeps its existing fail-stop behavior; no
errexit suppression is introduced anywhere.

Add a library-boundary regression in tests/fm-backend.test.sh: under
set -eu, sourcing with the adapter absent must refuse instead of killing
the caller. It fails before the fix (caller shell died, rc=1) and passes
after; the existing teardown fixture stays the end-to-end regression.

Validated on macOS stock bash 3.2.57 from a fresh upstream/main base:
tests/fm-teardown.test.sh 58 ok (all four preflight refusal modes),
tests/fm-backend.test.sh (new regression red on base, green here),
tests/fm-backend-herdr.test.sh 182 ok, tests/fm-afk-launch.test.sh 46 ok,
tmux/cmux smoke rc 0, bin/fm-lint.sh and bin/fm-doc-audience-check.sh
clean, /bin/bash -n parse coverage over bin/*.sh and bin/backends/*.sh.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T08:46:06.150517Z 1481bdb PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge, with no actionable correctness or security failures identified.

The existence check uses the same adapter path subsequently sourced and converts only the documented missing-file failure into a normal refusal while preserving successful adapter loading.

Reviews (1): Last reviewed commit: "fix(backends): make a missing adapter a ..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant