Skip to content

fix(mode-switch): use portable case folding for Bash 3.2 compatibility - #7843

Merged
Lightheartdevs merged 2 commits into
Osmantic:mainfrom
vaibhavsrv:fix/mode-switch-bash32-portability
Oct 22, 2026
Merged

Lightheartdevs merged 2 commits into
Osmantic:mainfrom
vaibhavsrv:fix/mode-switch-bash32-portability

Conversation

@vaibhavsrv

@vaibhavsrv vaibhavsrv commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Under macOS default /bin/bash (Bash 3.2.57), running ods/scripts/mode-switch.sh crashes immediately with a syntax error:
/bin/bash: ${configured_backend,,}: bad substitution (exit code 1).

Lines 79 and 90 previously used Bash 4+ case-modification parameter expansion ${configured_backend,,} and ${switchboard_mode,,}. This replaces ${var,,} with printf '%s' "$var" | tr '[:upper:]' '[:lower:]', which is fully portable across POSIX, Bash 3.2, and Bash 5+.

Behavior differences versus main:

  • mode-switch.sh succeeds under /bin/bash 3.2 on macOS instead of failing with exit code 1.
  • Case folding: Uses standard POSIX tr '[:upper:]' '[:lower:]' (ASCII transliteration) instead of Bash 4 ${var,,} (locale-aware). For ASCII configuration tokens (external, enabled), behavior is identical.
  • Extra subshell fork: Spawns a subshell pipe to tr for each variable lookup (once per invocation; negligible execution overhead).
  • All rejection guards (e.g. rejecting external LLM backends with guidance to run ./install.sh --no-external-llm) and switchboard environment updates remain identical.

Follow-up to prior PR #5880 on public-beta (by vaibhavsrv), which was closed during the branch retirement sweep on 2026-10-04 with maintainer guidance to rebase onto main.

AI Assistance

AI drafted the fix and tests and ran the checks. The human author reviewed the final diff on 2026-10-21.

Release Lane

  • Stable hotfix targeting release/2.6.x
  • Mainline change targeting main
  • Next-minor work targeting the next feature/minor release
  • Not sure; reviewer should help classify

Stable hotfix reason:

<!-- If this targets release/2.6.x, explain the current-stable user problem it fixes. -->

Changed Surface

  • Docs only
  • Tests only
  • Dashboard UI
  • Dashboard API / host agent
  • Installer / bootstrap / lifecycle
  • Docker Compose / service manifests
  • Model routing / Hermes / capabilities
  • Network exposure / auth / proxy
  • Dependencies / runtime wiring

Risk And Validation

  • Risk level: Medium
  • Validation run:
    • git diff --check
    • Markdown/link sanity for docs
    • Focused tests listed below
    • Dashboard lint/test/build
    • Extension audit / compose validation
    • Release-grade fleet or scoped hardware validation
    • Stable-lane patch validation, if targeting release/2.6.x
    • Not required because:

Commands/results:

Tested:
- Unpatched /bin/bash execution on macOS: failed with "/bin/bash: ${configured_backend,,}: bad substitution" (exit 1).
- Unpatched Linux CI execution: On Linux, /bin/bash is Bash 5, so direct execution does not fail; the non-portable syntax grep check fails before the fix.
- Post-fix bash 5.3.15: 31 passed, 0 failed in ods/tests/test-mode-switch-status.sh.
- Post-fix /bin/bash 3.2.57: 31 passed, 0 failed in ods/tests/test-mode-switch-status.sh.
- Neighboring tests (test-env-set-literal-key.sh, test-env-append-final-newline.sh): 0 failures before and after.
- bash -n and shellcheck on both files: clean (0 errors, 0 warnings).
- git diff --check: clean (0 whitespace warnings).
- python3 .github/scripts/list-unwired-tests.py --check: PASS (1311 tests, 1293 executed by CI).
- Mutation check: reverting the fix in mode-switch.sh caused 4 test assertion failures under /bin/bash on macOS.

Not tested:
- Full Docker Compose container startup (tests use isolated temp directories with mocked .env files).

Operational Change Check

If this PR touches installer phases, bootstrap logic, compose stack generation,
service manifests, dashboard API control flows, Hermes, model routing, GPU or
runtime detection, lifecycle commands, host mutation, or network exposure, it
requires release-grade fleet validation before release unless the PR explains a
narrower equivalent.

  • This is not an operational change.
  • This is an operational change and validation is recorded above.
  • This is an operational change and validation is intentionally deferred for:

Notes For Reviewers

Overlap check:

Limits:

  • Scope is strictly limited to ods/scripts/mode-switch.sh and its regression test ods/tests/test-mode-switch-status.sh.
  • The real /bin/bash 3.2 execution block runs on macOS; in CI under Linux, compatibility is guarded by the syntax check grep for non-portable case expansion.
  • Path limitation: install-macos.sh and ods-macos.sh require Homebrew Bash 4+ (brew install bash), and ods-cli re-execs Homebrew Bash. This fix specifically protects users or scripts invoking mode-switch.sh directly under stock macOS /bin/bash.

ods/scripts/mode-switch.sh lines 79 and 90 previously used Bash 4+ parameter
case expansion ${configured_backend,,} and ${switchboard_mode,,}. Under
macOS default /bin/bash 3.2.57, running mode-switch.sh causes the shell
to abort immediately with a syntax error:
/bin/bash: ${configured_backend,,}: bad substitution (exit 1).

This replaces ${var,,} with printf '%s' "$var" | tr '[:upper:]' '[:lower:]',
restoring portable case-folding compatibility across Bash 3.2 and Bash 5+.
Adds regression assertions in ods/tests/test-mode-switch-status.sh.
@Lightheartdevs

Copy link
Copy Markdown
Collaborator

Thanks for fixing the macOS Bash 3.2 failure while preserving the external-backend and switchboard guards.

Advisory status: not ready yet

Quickest path to merge:

  • Please complete the human diff review and update the PR's AI Assistance statement, which currently says the author has not reviewed each line. CONTRIBUTING.md requires the contributor to read the final diff and own the changed behavior; this is especially relevant to ods/scripts/mode-switch.sh, a lifecycle entrypoint.
  • Once the separate dependency repair (fix(security): update aiohttp, certifi and Pillow runtime locks #7839) lands, update from main and re-run the three failing python-locks jobs. The dashboard-api audit log reports aiohttp 3.14.3 advisories with 3.14.4 as the fix; current main has that same pin. These failures are outside this two-file change, so there is no need to broaden this PR with an unrelated dependency patch.

What I checked:

  • Reviewed both changed files and the surrounding mode-switch guards at 5e83d5b; found no concrete runtime regression in the portable case-folding change.
  • Ran ods/tests/test-mode-switch-status.sh from the fetched head under Git Bash: 31 passed, 0 failed. That is local Bash coverage, not an independent macOS/Bash 3.2 run; the latter is reported in the PR.
  • Inspected head checks, the dependency audit failure, and the existing CI test registration.

@vaibhavsrv

vaibhavsrv commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor Author

@Lightheartdevs , updated PR status:

  • Merged upstream main (733390e4, incorporating fix(security): update aiohttp, certifi and Pillow runtime locks #7839); diff remains strictly scoped to ods/scripts/mode-switch.sh and ods/tests/test-mode-switch-status.sh.
  • CI checks: all 9 python-locks jobs in Runtime security contracts now pass cleanly (0 failing checks).
  • Re-verified ods/tests/test-mode-switch-status.sh directly under /bin/bash 3.2.57 on the author's macOS machine (31 passed, 0 failed), as well as Bash 5.3, with clean shellcheck and git diff --check.
  • AI assistance disclosure in the PR description has been updated to reflect the author's diff review.

@Lightheartdevs

Copy link
Copy Markdown
Collaborator

Thanks for fixing stock Bash compatibility and following through on the earlier review feedback.

Advisory status: looks merge-ready

What looks good:

  • ods/scripts/mode-switch.sh replaces both Bash 4 case-folding expansions while preserving the external-backend refusal and switchboard routing behavior.
  • The regression suite checks uppercase configuration values, unchanged .env on refusal, and direct shell execution. The author has recorded final-diff review, and the dependency-check failures noted on the previous head are now clear.

What I checked:

  • Reviewed both changed files, surrounding mode-switch logic, previous discussion, and the test's registration in test-linux.yml.
  • Ran ods/tests/test-mode-switch-status.sh from this head under Git Bash: 31 passed, 0 failed.
  • Current head has 67 successful checks. The macOS Bash 3.2 result is author-reported; my local run did not independently verify that platform or start Docker services.

@Lightheartdevs
Lightheartdevs merged commit 4906514 into Osmantic:main Oct 22, 2026
67 checks passed
Lightheartdevs added a commit that referenced this pull request Oct 23, 2026
* fix(preflight): run the LLM route check under macOS Bash 3.2

ods-preflight.sh is #!/bin/bash, which is Bash 3.2 on macOS, and sources
lib/preflight-llm-route.sh. Its ${mode,,} is Bash 4 syntax, so on a Mac
the preflight aborted at "[4/8] Checking LLM endpoint" with "bad
substitution" in every mode. Lowercase with tr instead, as #7843 did for
mode-switch.sh, and test the selector under /bin/bash.

* test(macos): exercise preflight selector with stock Bash 3.2

---------

Co-authored-by: Roshan Kumar Gupta <31376345+roshangupta00750@users.noreply.github.com>
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.

2 participants