Skip to content

feat(openemr-cmd): pass HOST_UID/HOST_GID through compose to openemr container#840

Merged
bradymiller merged 4 commits into
openemr:masterfrom
bradymiller:feat/worktree-host-uid-passthrough
Jun 26, 2026
Merged

feat(openemr-cmd): pass HOST_UID/HOST_GID through compose to openemr container#840
bradymiller merged 4 commits into
openemr:masterfrom
bradymiller:feat/worktree-host-uid-passthrough

Conversation

@bradymiller

@bradymiller bradymiller commented Jun 26, 2026

Copy link
Copy Markdown
Member

Summary

Completes the cross-repo HOST_UID story started in openemr/openemr#12642 (entrypoint side, already merged + new flex image SHA pinned via dependabot #12644). This PR wires the openemr-devops side: wt_write_override emits HOST_UID/HOST_GID env vars in the openemr service block, sourced from id -u / id -g at worktree-add / regen time.

Result

For non-uid=1000 hosts (CI runners at uid=1001, multi-user dev boxes, distros that start uids at 1001+, macOS via Docker Desktop): apache inside the container now writes bind-mount files with the host's uid. Host-side writes (git commit, IDE edits, worktree regen, worktree set-env) work without any chown dance. The uid-mismatch class of bugs that dominated #833 / #836 / #838 review cycles is eliminated end-to-end.

For uid=1000 hosts (most Linux desktop developers): zero functional change. The env vars are emitted unconditionally because the entrypoint adoption block is a sub-millisecond no-op on uid=1000 and the consistent behavior across hosts is worth more than the savings.

Backwards compatible: pre-HOST_UID-aware images silently ignore the env vars.

CI workarounds removed (now obsolete)

Both sudo chown -R "$(id -u):$(id -g)" steps in test-bats-openemr-cmd-real-docker.yml:

  1. worktree-lifecycle-e2e "One-shot chown back to runner (enables host-side regen + set-env)" — added in ci(openemr-cmd): e2e job titles match contents; add list/regen/set-env/auto-chown coverage #834
  2. prek-workflow-e2e "One-shot chown back to runner (enables host-side writes + git commits)" — added in test(openemr-cmd): e2e — prek install + real git commit; expand multi-concurrent to 3 envs #836

Both replaced with NOTE: comments documenting what used to live there and why the chown is no longer needed — for archaeology by future contributors.

Kept as back-compat shim

The #833 auto-chown block in cmd_worktree_remove stays in place. Inline comment updated to reflect its new status (no-op when the image honors HOST_UID; still useful for users on pre-HOST_UID images and for any drift the entrypoint adoption might miss).

Tests

New bats file tests/bats/openemr-cmd/host_uid_compose.bats pins three behaviors:

  1. wt_write_override emits HOST_UID and HOST_GID env vars in the override file
  2. Values match id -u / id -g at write time
  3. Env vars are correctly nested under services.openemr.environment (structural check via yaml.safe_load)
  4. Emitted for all three env variants (easy / easy-light / easy-redis)

Test plan

  • BATS openemr-cmd (ubuntu-22.04 + macos-14) pass — new tests + existing suite
  • e2e — full worktree lifecycle: passes WITHOUT the sudo chown step (proves apache now adopts runner uid; regen + set-env writes work as runner)
  • e2e — prek install + real git commit: passes WITHOUT the sudo chown step (proves git commit from the worktree works as runner)
  • Other e2e jobs unchanged
  • FUNCS_END drift sentinel still passes (no new function added — wt_write_override grew inline, last function still at line 1882)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Generated OpenEMR docker-compose override files now include the current host user and group IDs (HOST_UID/HOST_GID) in the container environment.
  • Bug Fixes
    • Worktree lifecycle and pre-install commit workflows no longer require host-side ownership adjustment before regeneration and environment setup.
    • Permission recovery/chown behavior is more consistent, with backward-compatible fallback for older images or ownership drift.
  • Tests
    • Added BATS coverage validating HOST_UID/HOST_GID emission and stability of golden comparisons by masking host-specific values.
    • Improved BATS harness sourcing for hermetic function-only execution.

…container

Completes the cross-repo HOST_UID story. openemr/openemr#12642
landed the flex entrypoint change that honors HOST_UID/HOST_GID
(apache adopts the host uid before any chown work). This PR wires
the openemr-devops side: wt_write_override emits HOST_UID/HOST_GID
env vars in the openemr service block, sourced from `id -u` / `id -g`
at worktree-add time.

Result for non-uid=1000 hosts (CI runners at uid=1001, multi-user
dev boxes, distros that start uids at 1001+, macOS via Docker
Desktop): apache inside the container now writes bind-mount files
with the host's uid. Host-side writes (`git commit`, IDE edits,
`worktree regen`, `worktree set-env`) work without any chown
dance. The uid-mismatch class of bugs (openemr#833, openemr#836, openemr#838) is
eliminated end-to-end.

Backwards compatible: pre-HOST_UID-aware images silently ignore
the env vars (current behavior preserved). Emitted unconditionally
even on uid=1000 hosts because the entrypoint adoption block is a
sub-millisecond no-op there — the consistent behavior across hosts
is worth more than the savings.

CI workarounds removed (now obsolete):
- "One-shot chown back to runner (enables host-side regen + set-env)"
  in worktree-lifecycle-e2e (added in openemr#834)
- "One-shot chown back to runner (enables host-side writes + git
  commits)" in prek-workflow-e2e (added in openemr#836)

Both replaced with NOTE comments explaining what used to live there
and why the chown is no longer needed.

The openemr#833 auto-chown block in cmd_worktree_remove is kept as a
back-compat shim for users on pre-HOST_UID images. Inline comment
updated to reflect its new status (no-op when image honors HOST_UID,
useful for older images and for any drift cases the entrypoint
adoption misses).

Tests: tests/bats/openemr-cmd/host_uid_compose.bats pins the new
behavior — 3 cases (env vars present, structurally nested under
services.openemr.environment, emitted for all 3 envs).

Assisted-by: Claude Code
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c0b151eb-aa74-4022-b5f7-adb13a81d602

📥 Commits

Reviewing files that changed from the base of the PR and between 69dca3e and cc57e71.

📒 Files selected for processing (2)
  • tests/bats/openemr-cmd/helpers_pure.bats
  • tests/bats/openemr-cmd/host_uid_compose.bats
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/bats/openemr-cmd/host_uid_compose.bats
  • tests/bats/openemr-cmd/helpers_pure.bats

📝 Walkthrough

Walkthrough

The PR adds HOST_UID/HOST_GID to generated OpenEMR compose overrides, updates BATS coverage and harness sourcing to load openemr-cmd functions directly, and removes host-side chown steps from the real-docker workflow.

Changes

OpenEMR UID and sourcing flow

Layer / File(s) Summary
Workflow chown cleanup
.github/workflows/test-bats-openemr-cmd-real-docker.yml
The real-docker workflow removes the host-side chown steps for the lifecycle and pre-workflow jobs, replacing them with notes that reference HOST_UID passthrough and wt_write_override.
Override output and validation
utilities/openemr-cmd/openemr-cmd, tests/bats/openemr-cmd/host_uid_compose.bats, tests/bats/openemr-cmd/goldens/easy/override.yml, tests/bats/openemr-cmd/goldens/easy-light/override.yml, tests/bats/openemr-cmd/goldens/easy-redis/override.yml, tests/bats/openemr-cmd/worktree_add_goldens.bats
wt_write_override appends HOST_UID and HOST_GID under services.openemr.environment, cmd_worktree_remove adds a new ownership note, the script version changes, and the BATS goldens and new tests assert the generated override contents and placement across supported envs.
Functions-only sourcing in tests
utilities/openemr-cmd/openemr-cmd, tests/bats/openemr-cmd/helpers.bash, tests/bats/openemr-cmd/helpers_pure.bats, tests/bats/openemr-cmd/copy_base_env.bats, tests/bats/openemr-cmd/state.bats, tests/bats/openemr-cmd/state_lock.bats, tests/bats/openemr-cmd/state_lock_crash_recovery.bats, tests/bats/openemr-cmd/worktree_add_base.bats
The script adds a __OPENEMR_CMD_SOURCE_FUNCS_ONLY guard, and the BATS helpers and suites switch from head/eval function slicing to sourcing the full script under that guard.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

I hopped through the override, neat and bright,
With UID and GID tucked in just right.
No chown parade, just a gentler stream,
And tests that source the script in one clean beam. 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: passing HOST_UID/HOST_GID into the OpenEMR container via compose.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

…urce-only guard; bump VERSION

Two cleanups bundled:

(1) The hermetic bats suite used to source openemr-cmd's function
definitions via `eval "$(head -n OC_SCRIPT_FUNCS_END script)"` with
a magic line-number constant that had to be bumped every time the
script grew/shrank near end-of-functions. A drift sentinel test
caught stale constants but the bump itself was easy to forget —
which broke CI on openemr#838 and again now on this branch, distracting
from the actual review surface.

Replace with a __OPENEMR_CMD_SOURCE_FUNCS_ONLY=1 env-var guard:
the script returns early from sourcing right after the last
function def when the flag is set; direct invocation (./openemr-cmd)
is unaffected since the flag isn't in the runtime environment. Tests
that want only function defs set the flag and source — no magic
number, no drift, no maintenance.

- openemr-cmd: source guard added before USAGE_EXIT_CODE=13
- helpers.bash: oc_source_funcs uses the env var; OC_SCRIPT_FUNCS_END
  constant removed
- helpers_pure.bats: oc_run_in_funcs drops the funcs_end arg; the
  drift sentinel test replaced with two guard-behavior tests (sourcing
  with the flag defines funcs but skips dispatch; direct execution
  is unaffected by the flag)
- copy_base_env.bats: oc_run_in_funcs drops the funcs_end arg
- state.bats, state_lock.bats, state_lock_crash_recovery.bats,
  worktree_add_base.bats: inline eval-of-head replaced with
  source-with-flag pattern

(2) Bump VERSION 1.0.48 → 1.0.49 for the HOST_UID passthrough +
this refactor. Was missed in the initial openemr#840 push.

Assisted-by: Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/bats/openemr-cmd/helpers_pure.bats (1)

24-37: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Stale parameter arity — tmp_root is now always empty, OPENEMR_ROOT set blank.

The callers were updated to the new 3-argument shape (oc_run_in_funcs '<snippet>' "$SCRIPT" "$TMP_ROOT"), dropping the old funcs_end argument. But this helper still declares local funcs_end=$3 / local tmp_root=$4, so $TMP_ROOT lands in funcs_end and tmp_root resolves to empty — making env OPENEMR_ROOT="${tmp_root}" set an empty root. The sibling copy_base_env.bats helper was already corrected to local tmp_root=$3; this one was missed.

🐛 Proposed fix
 oc_run_in_funcs() {
     local snippet=$1
     local script_path=$2
-    local funcs_end=$3
-    local tmp_root=$4
+    local tmp_root=$3
     run env OPENEMR_ROOT="${tmp_root}" bash -c "
         set -euo pipefail
         __OPENEMR_CMD_SOURCE_FUNCS_ONLY=1
         source '${script_path}'
         ${snippet}
     "
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bats/openemr-cmd/helpers_pure.bats` around lines 24 - 37, The
oc_run_in_funcs helper has stale argument parsing after the call sites switched
to three arguments, so OPENEMR_ROOT is being set from an empty tmp_root value.
Update the helper signature/locals in oc_run_in_funcs to match the new
3-argument shape used by the callers, following the same pattern as the sibling
helper in copy_base_env.bats, so script_path and tmp_root are read from the
correct positions and env OPENEMR_ROOT receives the actual temp root.
🧹 Nitpick comments (1)
tests/bats/openemr-cmd/helpers_pure.bats (1)

30-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale comment. The # eval, not 'source <(...)' rationale no longer applies now that the helper sources the script directly rather than eval-ing it. Consider updating to reference the __OPENEMR_CMD_SOURCE_FUNCS_ONLY mechanism to avoid confusing future readers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bats/openemr-cmd/helpers_pure.bats` around lines 30 - 32, The inline
comment in the helper setup is stale and still references the old
eval/process-substitution behavior. Update the comment near the shell setup in
helpers_pure.bats to describe the current direct-sourcing approach and mention
the __OPENEMR_CMD_SOURCE_FUNCS_ONLY mechanism so readers understand why only
functions are loaded.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@tests/bats/openemr-cmd/helpers_pure.bats`:
- Around line 24-37: The oc_run_in_funcs helper has stale argument parsing after
the call sites switched to three arguments, so OPENEMR_ROOT is being set from an
empty tmp_root value. Update the helper signature/locals in oc_run_in_funcs to
match the new 3-argument shape used by the callers, following the same pattern
as the sibling helper in copy_base_env.bats, so script_path and tmp_root are
read from the correct positions and env OPENEMR_ROOT receives the actual temp
root.

---

Nitpick comments:
In `@tests/bats/openemr-cmd/helpers_pure.bats`:
- Around line 30-32: The inline comment in the helper setup is stale and still
references the old eval/process-substitution behavior. Update the comment near
the shell setup in helpers_pure.bats to describe the current direct-sourcing
approach and mention the __OPENEMR_CMD_SOURCE_FUNCS_ONLY mechanism so readers
understand why only functions are loaded.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b895dd52-8eeb-4323-a391-1455e2120683

📥 Commits

Reviewing files that changed from the base of the PR and between 9099e54 and 56d4723.

📒 Files selected for processing (8)
  • tests/bats/openemr-cmd/copy_base_env.bats
  • tests/bats/openemr-cmd/helpers.bash
  • tests/bats/openemr-cmd/helpers_pure.bats
  • tests/bats/openemr-cmd/state.bats
  • tests/bats/openemr-cmd/state_lock.bats
  • tests/bats/openemr-cmd/state_lock_crash_recovery.bats
  • tests/bats/openemr-cmd/worktree_add_base.bats
  • utilities/openemr-cmd/openemr-cmd

…d values

The override.yml golden fixtures didn't include the new HOST_UID /
HOST_GID env vars that wt_write_override now emits, breaking the 3
golden assertion tests. Add the environment block to each env's
golden (easy / easy-light / easy-redis) right before the volumes
block, matching the live script output.

Mask the uid/gid values to __HOST_UID__ / __HOST_GID__ in mask_paths
so goldens stay stable across runner uids (CI = 1001, contributor
dev boxes typically 1000, etc.) without UPDATE_GOLDENS needing to
run on each contributor's machine.

Assisted-by: Claude Code
…yyaml dep)

Two macOS-specific failures from the previous push:

(1) "source-funcs-only guard: direct execution is unaffected by the
flag" expected exit 14 (VERSION_EXIT_CODE) but got 16 (DOCKER_CODE)
on macOS. The script's docker-availability check runs before
--version parsing — macOS GH-hosted runners don't have docker
installed, so the script exits 16 before reaching --version. Fix:
stub docker via oc_make_docker_stub_dir (already used by other
hermetic tests), then run --version against the stubbed PATH.

(2) "host_uid: env vars are nested under services.openemr.environment"
used pyyaml which isn't on macOS runners (ModuleNotFoundError).
Replace with awk that walks the override file linearly tracking
the in_services/in_openemr/in_env state. Same structural assertion,
no Python deps. Verified locally that the awk parses the real
wt_write_override output correctly.

Assisted-by: Claude Code
@bradymiller bradymiller merged commit 0722b9c into openemr:master Jun 26, 2026
12 checks passed
@bradymiller bradymiller deleted the feat/worktree-host-uid-passthrough branch June 26, 2026 05:49
bradymiller added a commit to bradymiller/openemr that referenced this pull request Jun 26, 2026
Brief mention of the openemr-cmd HOST_UID/HOST_GID auto-export in
two places where contributors and agents will look:

- CONTRIBUTING.md: added a sub-bullet under the openemr-cmd intro
  explaining that bind-mounted files apache writes are host-owned
  for any host uid (transparent for new checkouts; one-shot sudo
  chown documented for legacy checkouts with root-owned cruft).
- CLAUDE.md: added a Common Gotchas entry so future agents debugging
  EACCES errors on bind-mount edits go straight to "did you bypass
  openemr-cmd?" instead of tracing it themselves. Also notes the
  insane env hasn't been wired up yet (older image tags don't
  honor HOST_UID).

The auto-export landed in openemr/openemr-devops#840 (worktree side)
and openemr/openemr-devops#842 (non-worktree side); the entrypoint
adoption it depends on landed in openemr#12642 (image side)
and openemr#12647 (compose env declaration).

Assisted-by: Claude Code
bradymiller added a commit to openemr/openemr that referenced this pull request Jun 26, 2026
…12650)

## Summary

Two small docs additions for the HOST_UID/HOST_GID auto-export that
openemr-cmd now handles transparently (landed via
openemr/openemr-devops#840 + #842; depends on #12642
entrypoint + #12647 compose env declarations, both also
already landed).

**CONTRIBUTING.md** — added a sub-bullet under the openemr-cmd intro at
line 118. New contributors using openemr-cmd see the behavior is
transparent for new checkouts; long-lived checkouts with root-owned
cruft from older docker runs have a one-shot `sudo chown -R "$(id
-u):$(id -g)" .` cleanup.

**CLAUDE.md** — added a Common Gotchas entry so future agents debugging
EACCES on bind-mount edits go straight to "did you bypass openemr-cmd?"
instead of tracing it themselves.

## Scope

10 lines added across the two files. No code/behavior changes.

## What this does NOT cover

- The insane env compose + image tags don't honor HOST_UID yet. Out of
scope for this docs PR; tracked as a separate follow-up (extend HOST_UID
to insane requires rebuilding 11+ openemr image variants + bumping SHA
pins). The CLAUDE.md gotcha notes this explicitly so agents debugging
insane env know not to be surprised.

## Test plan

- [x] prek hooks (codespell, trailing-whitespace, end-of-file-fixer,
conventional-commits) pass on the diff
- [ ] Standard openemr CI passes (docs-only change, no functional impact
expected)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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