fix(openemr-cmd): use portable realpath for macOS/BSD#783
Merged
Conversation
The worktree subcommands passed GNU coreutils flags (-e, -m) to realpath, which the BSD realpath shipped on macOS rejects with "realpath: illegal option -- e". The git worktree got half-created but state registration (compose override + .env + .worktrees.json) never completed. GNU realpath has three modes: default (all but leaf must exist), -e (all incl. leaf must exist, else fail), and -m (nothing need exist). BSD realpath has a single mode equivalent to GNU -e: it resolves symlinks and errors on a missing leaf. Every -m call site here resolves a path that already exists (mkdir -p runs first, or git just reported it), so bare BSD realpath is equivalent at all sites. Detect the platform once via 'realpath --version' (GNU prints it; BSD errors) and build command arrays with the right flags. Using arrays rather than a wrapper function keeps realpath an external command, which avoids shellcheck SC2310 (function in an || condition disables set -e) under the repo's enable=all config. Symlink resolution -- the security property feeding the path-containment checks -- is preserved natively on both platforms. Assisted-by: Claude Code
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes openemr-cmd worktree portability on macOS/BSD by removing reliance on GNU realpath flags (-e, -m) that are not supported by the BSD realpath shipped with macOS, while preserving the script’s path canonicalization/symlink-resolution behavior used for safety checks.
Changes:
- Added a one-time platform capability check (
realpath --version) and introducedOE_REALPATH_E/OE_REALPATH_Mcommand arrays to select GNU flags when available and fall back to barerealpathon BSD/macOS. - Replaced all
realpath -e/realpath -mcall sites in worktree-related logic with the new command arrays. - Bumped script version from
1.0.38to1.0.39.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
4 tasks
The realpath platform-detection block added 14 lines above the function definitions, shifting the end-of-functions boundary from 1446 to 1460. The test harness slices the script at OC_SCRIPT_FUNCS_END to source only the function defs; the stale 1446 cut landed mid-if, breaking every sourced-function test with "unexpected end of file". Assisted-by: Claude Code
kojiromike
added a commit
that referenced
this pull request
Jun 6, 2026
…#784) ## Summary `openemr-cmd worktree list` and `worktree remove` can fail on setups with many git worktrees: `list` shows `(no worktrees)` even when entries exist, and `remove` aborts with `Path '...' is not a registered git worktree`. ## Root cause `wt_validate_dir` and `wt_validate_dir_safe` validate a path by piping git's worktree listing into `grep`: ```bash git -C "${OPENEMR_ROOT}" worktree list --porcelain 2>/dev/null \ | grep -Fqx "worktree ${dir_real}" ``` The script runs under `set -o pipefail`. `grep -q` exits as soon as it finds a match and closes the read end of the pipe. If git is still writing (its output exceeds the ~64KB pipe buffer, which happens once you have many worktrees), git receives `SIGPIPE` and exits 141. Under `pipefail` the pipeline's exit status becomes that 141, so the `if !` branch fires and a path that *is* a registered worktree gets rejected. This stays latent on typical setups where git's output fits in the pipe buffer and git finishes writing before `grep` exits — the SIGPIPE never happens. ## Fix Capture the listing into a variable first, then match with a herestring. No pipe means no SIGPIPE: ```bash local worktrees worktrees=$(git -C "${OPENEMR_ROOT}" worktree list --porcelain 2>/dev/null) if ! grep -Fqx "worktree ${dir_real}" <<< "${worktrees}"; then ... fi ``` ## Test plan - [x] `shellcheck` clean under the repo's `.shellcheckrc` (`enable=all`) - [x] `bash -n` passes - [x] Reproduced the failure on a machine with ~150 worktrees: the old pipe form fails under `pipefail` (pipeline exits 141); the herestring form returns 0 - [x] End-to-end on macOS: `worktree list` shows the entry and `worktree remove` completes cleanly (both previously broken) ## Note Found while verifying #783 (the macOS realpath fix). That fix let execution finally reach this validation code, surfacing this separate, platform-independent bug. The two are independent; this PR stands alone.
5 tasks
kojiromike
added a commit
to kojiromike/openemr-devops
that referenced
this pull request
Jun 6, 2026
openemr-cmd is a host CLI users run on their Macs, but BATS only ran on ubuntu-22.04 — so macOS portability bugs (e.g. the BSD realpath issue in PR openemr#783) reached users with zero CI coverage. Add a macos-14 leg to the BATS matrix, where the script runs under system bash 3.2. That surfaced two real bash 3.2 incompatibilities: - openemr-cmd used ${RESP,,} (bash 4+ case-modification), which throws "bad substitution" under macOS bash 3.2. Replace with a [Yy] glob match, preserving the [Y/n] default-yes semantics. - The test harness sourced the script via process substitution (source <(head -n ...)), which silently defines no functions under macOS bash 3.2. Switch the three sites to eval "$(head -n ...)". The repo is public, so GitHub-hosted macOS minutes are free. The macOS leg is advisory for now (fail-fast: false, not branch-protection required). Suite passes 39/39 under both bash 3.2 and bash 5. Assisted-by: Claude Code
kojiromike
added a commit
that referenced
this pull request
Jun 6, 2026
) ## Summary `openemr-cmd` is a host CLI developers run on their Macs, but its BATS workflow only ran on `ubuntu-22.04`. So macOS-portability bugs reached users with zero CI coverage — most recently the BSD `realpath` failure fixed manually in #783. This adds a `macos-14` leg to the BATS matrix, where the script runs under macOS system **bash 3.2**, the sharpest portability risk. Adding that leg immediately surfaced two real bash 3.2 incompatibilities, both fixed here: - **`openemr-cmd`** used `${RESP,,}` (bash 4+ case-modification), which throws `bad substitution` under bash 3.2. Replaced with a `[Yy]` glob match in `[[ ]]`, preserving the `[Y/n]` default-yes semantics. (Verified identical behavior on bash 3.2 and 5.) - **Test harness** sourced the script via process substitution (`source <(head -n …)`), which silently defines no functions under macOS bash 3.2. Switched the three sites (`helpers.bash`, `helpers_pure.bats`, `state.bats`) to `eval "$(head -n …)"`. The repo is public, so GitHub-hosted macOS minutes are free. The macOS leg is **advisory** for now (`fail-fast: false`, not branch-protection-required) and can be promoted to required later. ## Test plan - [x] Full openemr-cmd BATS suite passes **39/39 under system bash 3.2** (macOS-runner equivalent) - [x] Full suite passes **39/39 under bash 5** (Linux-runner equivalent) - [x] `${RESP,,}` → `[Yy]` glob verified semantically identical across both shells (empty→pull, y/Y→pull, else→skip) - [x] Workflow YAML validates; `helpers.bash` and `openemr-cmd` shellcheck-clean - [ ] CI: confirm both `ubuntu-22.04` and `macos-14` legs go green
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
openemr-cmd worktreesubcommands fail on macOS withrealpath: illegal option -- e. They pass GNU coreutils flags (-e,-m) torealpath, but the BSDrealpathshipped on macOS doesn't accept them. The git worktree gets half-created on disk, but state registration (compose override +.env+.worktrees.jsonentry) never completes.Background: GNU vs BSD realpath
GNU
realpathhas three modes:-e— all components including the leaf must exist, else fail-m— nothing need existBSD
realpath(macOS) has a single mode equivalent to GNU-e: it resolves symlinks and errors on a missing leaf. Every-mcall site in this script resolves a path that already exists (mkdir -pruns first, or the path was just reported by git), so bare BSDrealpathis behaviorally equivalent at all 17 sites.Fix
Detect the platform once via
realpath --version(GNU prints a version; BSD errors out) and build command arrays carrying the correct flags:Using command arrays rather than a wrapper function keeps
realpathan external command, which avoids shellcheckSC2310(a function invoked in an||condition disablesset -e) under the repo'senable=allconfig. Symlink resolution — the security property feeding the path-containment checks (e.g. blockingdocker/library -> /etcescapes) — is preserved natively on both platforms.Zero behavior change on GNU/Linux: the flags pass straight through. No new runtime dependency.
Test plan
shellcheckclean under the repo's.shellcheckrc(enable=all,--check-sourced --external-sources)bash -npassesopenemr-cmd worktree add <branch> -bcompletes withoutrealpath: illegal option— compose override +.envwritten,.worktrees.jsonentry registeredset -u)