CodePulse v2.3.147 — Bash hook achieves Linux/macOS parity with the Windows anchor-file resolver
The bash hook (`codepulse-pulse.sh`) now uses the same 4-tier resolver chain shipped for Windows in v2.3.145 — anchor file → env var → script-location inference → self-heal flag. `/health` cross-platform: it reads diag flags from both `%APPDATA%` (Windows) and `$HOME/.codepulse/diagnostics` (Linux/macOS). New CI workflow validates the bash code on real Linux instead of git-bash. Concludes the TAB-665 EPIC.
What this fixes
codepulse-pulse.sh was the Linux/macOS sibling of codepulse-pulse.ps1 and had the SAME env-var-fragility class that hit the Windows hook in the v2.3.142 outage — different triggers, identical failure mode:
- Profile-cleaner sweeps wiping
~/.bashrc/~/.zshrc(analogous to Windows AV cleaners pruningHKCU:\Environment). sudoinvocations that stripCODEPULSE_HOMEfrom the environment.- Stale shell sessions whose env was loaded before install.
- systemd user units / launchd plists with restricted env passthrough.
Pre-fix: any of these paths → resolver chain falls through (env → script-location inference) → if neither matches, hook silently exits with no diagnostic. Telegram surface dark, /health reports hooksHealthy: true because the original release only checked the Windows flag path.
What changed (TAB-668)
install/hooks/codepulse-pulse.sh — 4-tier resolver (TAB-649 parity)
- Anchor file beside the script (
codepulse-home.txt). Single line, plain text, absolute path. Trim whitespace, validate[[ -d ]]and[[ -f $value/.env ]]. CODEPULSE_HOMEenv var (legacy primary).- Script-location inference (
install/hooks/.. -> rootwalk). - Stamp diag flag and exit silently if all three above fail.
POSIX-atomic flag write: printf > tmp.$$ followed by mv -f (rename is atomic on the same filesystem). JSON payload constructed without a jq dependency, with proper escaping of \\, ", \t, \n, \r plus a control-char scrub for 0x00–0x1F so paths containing exotic bytes can't produce flag-unparseable JSON.
Stale-flag cleanup: rm -f first, fall back to truncate-to-empty (: > file) if the file is locked. Empty content is treated as "healthy" by readHookHealthFlag.
Orphan-tmp sweep with age guard (>5 seconds) so concurrent hook invocations don't race-clobber each other's in-flight tmp files. Cross-platform stat: tries stat -c %Y (GNU), falls back to stat -f %m (BSD/macOS).
install/lib/place-hooks.sh
New --codepulse-home <path> arg. When supplied AND target is a directory containing .env, the installer drops codepulse-home.txt next to the copied hook. Anchor write is non-fatal — a transient FS hiccup logs a WARN but doesn't break the install. Also rejects empty --codepulse-home arg up-front, and rejects target paths containing newline / CR (which would corrupt the bash hook's anchor-read trim semantics).
install/install.sh
Hoists single canonical CP_HOME_NORMALIZED (mirrors the same TAB-666 dedup we did on Windows). One source-of-truth for the path; passes through --codepulse-home to place-hooks.sh Step 3 and set-codepulse-home.sh Step 5.
install/lib/uninstall-hooks.sh
Step 2/3 now also removes codepulse-home.txt alongside the hook script. Idempotent — silent skip when absent (legacy installs from before TAB-668).
src/agent/approval-bridge-server.ts
The bridge's readHookHealthFlag() now probes BOTH platform paths:
- Windows:
%APPDATA%\CodePulse\diagnostics\hook-unresolved.flag - Unix:
$HOME/.codepulse/diagnostics/hook-unresolved.flag
When BOTH exist (rare; cross-host dev box running Windows + WSL/git-bash), the freshest by mtime wins. On tied mtime, the LAST candidate in iteration order wins (Unix), so a Linux/macOS dev sees their host shell's hook signal first.
Cross-platform CI is now real
Up to this release, the bash code was tested via git-bash on Windows in the Vitest suite. git-bash is bash 5+ but with Windows path semantics — close to but not identical to real Linux bash.
New workflow .github/workflows/tests-sh-linux.yml runs on ubuntu-latest:
bash -nsyntax check on all 4 modified.shfiles.- Vitest run of ONLY
tests/unit/tab-668-anchor-resolver-sh.test.ts(the file that doesn't usevi.mock, so it dodges the documented Linux-Vitest mock-hoisting flake class that prevents the FULL suite from running on Linux).
Not a release gate today (runs on every dev push but doesn't block versioning.yml). A future ticket will promote it to a hard gate after one or two release cycles of stability soak. The Windows tests.yml workflow remains the gating authority — the new Linux job is an early-warning system.
The first run of the new workflow was green on the v2.3.147 commit. ✅
Tests
18 new tests in tab-668-anchor-resolver-sh.test.ts mirroring the TAB-649 coverage matrix:
- A: place-hooks anchor-file write semantics (5 tests)
- B: uninstall anchor cleanup (2 tests)
- C: hook-script resolver chain via isolated tmp HOME (4 tests)
- D: cross-platform
readHookHealthFlagUnix-path semantics (7 tests, including the tied-mtime tie-break and the Windows-vs-Unix freshest-wins case)
Adversarial review × 2 per the project's quality protocol for cross-platform changes. Round 1 found 2 HIGH (JSON escape inconsistency + concurrent tmp race) + 4 MEDIUM + 5 LOW; Round 2 found 2 MEDIUM + 5 LOW. All HIGHs and primary MEDIUMs/LOWs applied.
Acknowledged drift vs Windows
The bash port is intentionally simpler in two places:
- No registry tier. POSIX has no analogue to Windows' HKCU registry (a system-wide-user persistent KV store outside the env). The 4-tier chain is the maximum natural depth.
- No per-process diag-jsonl writer. The Windows hook has a
Write-HookDiagjsonl logger for richer diag (anchor-vs-env-mismatch warnings etc.); the bash side relies on thehook-unresolved.flagfor the operator-visible signal. A hypothetical mismatch (env var pointing somewhere different from the anchor) is silent on Linux/macOS — anchor wins by design.
TAB-665 EPIC complete
This release closes the EPIC TAB-665 ("Hook resolver hardening + .sh parity — TAB-649 follow-ups"). Three sub-tickets, two release tags (v2.3.146 + v2.3.147), zero merge bubbles in the dev branch, zero force-pushes, zero pre-existing test regressions. The original v2.3.142 outage failure mode is now structurally impossible across both supported operating systems.