ci: run BATS test suite on push and PR#520
Conversation
The /tests/ directory has 186 BATS tests (launcher-common, launcher-xrdp-detection, and four cowork-*.bats files) but no workflow ever invoked `bats` — the entire suite was effectively inert. A regression in launcher-common.sh or cowork-vm-service.js would not fail any check, including the BATS suite added by PR aaddrick#395. Add a standalone tests.yml workflow that: - installs bats + nodejs - runs `bats tests/*.bats` - executes on every PR - executes on pushes to main Push triggers are path-filtered to: - tests/ - scripts/ - .github/workflows/tests.yml PR triggers remain unfiltered so required-check behaviour stays predictable. Kept this standalone rather than extending test-artifacts.yml so unit tests run in seconds instead of waiting for full artifact builds. This can be promoted to a build gate later once it proves stable in CI. CODEOWNERS - adds /.github/workflows/tests.yml under @sabiut - keeps /tests/cowork-*.bats ownership with @RayCharlizard This PR only enables CI coverage for existing tests and does not modify cowork test logic.
The "doctor: reports custom bwrap mounts" and "doctor: warns
about disabled critical mount /usr" tests failed in CI but
passed locally.
Root cause:
- _doctor_check_bwrap_mounts in scripts/doctor.sh resolves
the config dir via ${XDG_CONFIG_HOME:-$HOME/.config}/Claude
- The test setup() only sandboxes HOME via TEST_TMP
- GitHub Actions runners export XDG_CONFIG_HOME ambient
- Function reads the runner's real config dir, not the test
fixture, and silently emits no output
- Assertions on /opt/tools, WARN, etc. fail
Surfaced by PR aaddrick#520 wiring BATS into CI for the first time;
the bug existed before but was hidden by the suite never
running.
Fix: unset XDG_CONFIG_HOME in setup() so the function falls
back to \$HOME/.config (which is sandboxed). Comment in the
file documents why HOME alone is insufficient.
Verified: 186/186 pass with XDG_CONFIG_HOME set ambient
(reproduces CI env).
aaddrick
left a comment
There was a problem hiding this comment.
@sabiut — 186 inert tests only show up when someone actually runs them. Nice catch wiring this in. _doctor_check_bwrap_mounts (scripts/doctor.sh:140) reads ${XDG_CONFIG_HOME:-$HOME/.config}/Claude, and GH runners export XDG_CONFIG_HOME ambient. That wins the :- expansion before the inline HOME= at the call site gets a chance. Your unset in setup() forces the fallback path so the HOME override takes effect.
I checked out the branch and ran the suite with deliberately polluted env: empty XDG_CONFIG_HOME, populated, and pointed at a config crafted to match the test's assertion patterns (the false-positive scenario). 186/186 in every case. Reverting the unset reproduces the failure deterministically. Fix is correct.
Workflow shape is what I'd have asked for. No reason to gate seconds-fast unit tests behind a 10-minute artifact build. Push path-filtered with PR unfiltered lines up with required-check semantics under GH branch protection, and the concurrency group plus permissions: contents: read are both good to see.
@RayCharlizard — tests/cowork-bwrap-config.bats is on your surface per CODEOWNERS. Soft tag for awareness, not blocking. Fix is verified, so ship-it whenever you've had a look. One optional observation if consistency matters: launcher-common.bats:21-30 does the full XDG sandbox in setup() itself rather than per-call inline. Converging cowork-bwrap-config on that pattern would be cleaner, but not for this PR.
Written by Claude Opus 4.7 (1M context) via Claude Code
RayCharlizard
left a comment
There was a problem hiding this comment.
Hey @sabiut — confirming the fix from the cowork side. Good catch.
Walking through the path-resolution claim: _doctor_check_bwrap_mounts at scripts/doctor.sh:140 resolves the config dir as ${XDG_CONFIG_HOME:-$HOME/.config}/Claude. The :- only falls back when XDG_CONFIG_HOME is unset or empty. The two failing tests use HOME="${TEST_TMP}" run _doctor_check_bwrap_mounts — that scoped HOME override is meaningless if XDG_CONFIG_HOME is already exported in the runner env, which it is on ubuntu-latest. Your unset XDG_CONFIG_HOME in setup() forces the fallback path so the inline HOME actually wins. Targeted and correct.
One thing worth noting that strengthens the case: the third doctor test in this file (doctor: no output when no custom mounts configured at line 657) was probably also reading from the runner's real config dir before this fix. It passes either way because it asserts empty output and the runner has no claude_desktop_linux_config.json to find — green for the wrong reason. Your unset in shared setup() fixes that too.
I checked the other cowork suites for the same ambient-leak pattern. cowork-backend-detection.bats and cowork-path-translation.bats only invoke node against pure JS functions — no XDG_* resolution, no shell. cowork-resources-staging.bats works on a TEST_TMP staging layout via claude_extract_dir/electron_resources_dest, no XDG paths. The fix is complete, not partial.
Workflow shape is fine. bats --print-output-on-failure tests/*.bats picks up all four cowork-.bats and both launcher-.bats files. Concurrency group, permissions: contents: read, path filters scoped to tests/** and scripts/**, PR trigger unfiltered for predictable required-check semantics — all the pieces I'd expect. CODEOWNERS update is clean: only adds the new tests.yml line under @sabiut, leaves cowork-*.bats ownership where it sits.
Echoing aaddrick's optional observation — launcher-common.bats:21-30 does the full XDG sandbox in setup() (exports HOME and all XDG_* into TEST_TMP, then mkdir -p). Converging cowork-bwrap-config on that pattern would be more defensive than a single targeted unset, since any future test in the file that touches a different XDG var inherits the same trap. Not a blocker, and expanding the sandbox here would belong in its own change.
Confirmed CI is green at HEAD (BATS unit tests passes in 35s alongside everything else). Approving from the cowork side — ship it whenever.
Written by Claude Opus 4.7 (1M context) via Claude Code
Summary
The
/tests/directory contains 186 BATS tests across launcher and cowork suites, but no CI workflow has ever invokedbats— the entire test suite is currently inert. A regression inlauncher-common.shorcowork-vm-service.jswould not fail any check today. This PR adds a standalonetests.ymlworkflow that actually runs the suite on push and PR.The problem
/tests/has 6 BATS test files containing 186 tests total:tests/launcher-common.bats(48 tests)tests/launcher-xrdp-detection.bats(8 tests)tests/cowork-backend-detection.batstests/cowork-bwrap-config.batstests/cowork-path-translation.batstests/cowork-resources-staging.batsAudit on
main(6d281c9):Confirmed:
test-artifacts.yml,test-flags.yml,ci.yml, andshellcheck.ymlall run, but none invokesbats. PR #395's recently-merged launcher-common BATS suite (48 tests) landed under that same gap and has never actually executed in CI.The fix
Adds
.github/workflows/tests.yml— a standalone workflow that:main(unfiltered, so required-check semantics stay predictable)main(path-filtered totests/**,scripts/**, and the workflow itself)batsandnodejsvia apt — cowork tests shell out tonodeto loadscripts/cowork-vm-service.jsbats --print-output-on-failure tests/*.batspermissions: contents: readAlso updates
.github/CODEOWNERSto claim the new workflow under@sabiut, matching the existing pattern fortest-artifacts.ymlandtest-flags.yml./tests/cowork-*.batsreview ownership stays with @RayCharlizard.Bug surfaced by wiring CI
The first CI run on this PR caught two pre-existing test failures in
tests/cowork-bwrap-config.bats(tests 46 and 47 —doctor: reports custom bwrap mountsanddoctor: warns about disabled critical mount /usr).Root cause:
_doctor_check_bwrap_mountsinscripts/doctor.shresolves the config dir via${XDG_CONFIG_HOME:-$HOME/.config}/Claude. The testsetup()only sandboxedHOMEviaTEST_TMP. GitHub Actions runners exportXDG_CONFIG_HOMEambient, which overrode the per-testHOMEand made the function read the runner's real config dir. Assertions silently failed against empty output.The bug existed on
mainbefore this PR, but was hidden because the BATS suite never ran in CI — exactly the gap this PR exists to close. Fixed in the second commit (fix(tests): unset XDG_CONFIG_HOME in cowork-bwrap-config setup) by addingunset XDG_CONFIG_HOMEto the test'ssetup(). Verified locally that all 186 tests pass withXDG_CONFIG_HOMEset ambient (reproduces CI env).Why standalone instead of extending
test-artifacts.yml?BATS Testscheck means "your code broke a test", not "the build broke and we never reached tests".ci.ymlchanges to coordinate, easier to roll back.ci.ymlas a build gate later once it's proven stable.