← Files Compound EngineeringARCHIVED FILE

docs/plans/2026-07-31-004-fix-preserve-explicit-windows-bash-plan.md

7.42 KB · Oct 2, 2026 · 00:33 UTC

↓ Download file

---
title: "fix: Preserve explicit absolute non-WSL bash on Windows"
type: fix
status: active
date: 2026-07-31
origin: "PR #1292 Codex P2 (discussion_r3690011704); follow-up to docs/plans/2026-07-31-002-fix-prefer-git-bash-over-wsl-plan.md"
artifact_contract: ce-unified-plan/v1
artifact_readiness: implementation-ready
product_contract_source: ce-plan-bootstrap
execution: code
---

# fix: Preserve explicit absolute non-WSL bash on Windows

## Goal Capsule

- **Objective:** When a Windows peer worker argv already names an absolute non-System32 bash/sh (e.g. portable Git), keep that path — do not substitute the preferred resolver result or fail closed because the portable path is undiscoverable.
- **Authority:** Codex P2 on PR #1292; origin plan edge case “explicit absolute non-System32 bash prefix left as-is”; session-settled KD2 from #1268 applies to **bare** bash/sh only.
- **Stop when:** Absolute non-WSL bash is kept at `cmd_start` and spawn; System32 absolute bash still rewritten/fail-closed; bare names still resolve; six runners stay byte-identical; unit coverage proves both behaviors.

---

## Debug Summary (carried into planning)

**Problem:** `start -- C:\PortableGit\bin\bash.exe script.sh …` (or any absolute non-WSL bash) is treated like bare `bash`.

**Root cause:** `cmd_start` keys only on `basename(argv0) in {bash,sh,…}` and always calls `_resolve_windows_posix_shell()`, so the absolute-path branch never runs for `…\bash.exe`. Same basename gate in `_popen_argv` and `_rewrite_windows_env_bash_argv` substitutes preferred Git Bash or raises if the portable path is not in env/well-known/PATH.

**Evidence:** Origin plan U2 edge: “explicit absolute non-System32 bash prefix left as-is (already preferred).” Goal capsule of #1268: explicit Git Bash path already worked before this PR.

**Fix direction:** Treat absolute (drive/sep) bash/sh as caller-supplied: accept if file exists and not System32 WSL; reject System32; resolve only bare names.

---

## Product Contract

### Summary

Keep #1268 Git-Bash preference for **bare** `bash`/`sh` and System32 WSL rewrites, but honor an explicitly supplied absolute non-WSL shell so portable Git and other installs are not silently replaced or spuriously fail-closed.

Product Contract preservation: N/A (bootstrap). Does not reopen fail-closed / no-WSL-fallback / bare-rewrite product decisions — it restores the absolute-path exception already implied by those decisions and the #1268 plan’s edge case.

### Requirements

- R1. On Windows, if worker argv0 (or env-prefixed bash token) is an absolute path to an existing non-System32 bash/sh, use that path unchanged for meta and spawn.
- R2. If that absolute path is System32 WSL bash/sh, do not use it — rewrite via the existing resolver or fail closed (same as bare System32).
- R3. Bare `bash`/`sh`/`bash.exe`/`sh.exe` continue to resolve via `_resolve_windows_posix_shell()` (env → well-known → PATH walk).
- R4. Production change is in canonical `peer-job-runner.py` then byte-copied to all consumer skills; no product change outside shell selection.

### Scope Boundaries

- In: `cmd_start` bash/sh branch, `_popen_argv` bash/sh head, `_rewrite_windows_env_bash_argv`; unit tests; parity copy.
- Out: WindowsApps/Sysnative alias residual; changing resolver precedence; skipping Linux path tests; unrelated PR hygiene.

### Key Decisions

- KD1. Absolute path detection uses the same Windows absolute rule already used elsewhere in `cmd_start` (`os.sep in path` or drive-letter `X:`), not “looks like bash basename only.”
- KD2. Prefer a small shared helper (e.g. resolve-or-keep argv0 / token) used by start + spawn + env-rewrite so the three sites cannot drift.

---

## Planning Contract

### Key Technical Decisions

- KTD1. Introduce a helper along the lines of “given a bash/sh token, return the absolute shell to use”: if absolute → require `isfile`, reject System32 via `_is_system32_wsl_bash`, else return abspath; if bare → `_resolve_windows_posix_shell()`. (Governs R1–R3)
- KTD2. Wire that helper in `cmd_start`’s basename-bash branch (so absolute `…\bash.exe` no longer always resolves), in `_popen_argv`’s bash/sh head branch, and in `_rewrite_windows_env_bash_argv` for the bash token. (Governs R1, R4)
- KTD3. Fixture-only tests under existing `windows_platform` / `ntpath` helpers: absolute portable kept; absolute System32 rewritten/fail path; bare still resolves. No production behavior change for bare `bash`. (Governs R1–R3)

### Assumptions

- Codex P1 (Linux path semantics in unit fixture) remains addressed by `97ff131`; this plan does not reopen it.
- Callers that pass absolute bash intend that binary; `CE_PEER_BASH` remains the override for bare-name launches, not a silent override of an explicit argv0.

### Patterns to Follow

- Existing `_is_system32_wsl_bash` and absolute-path preflight in `cmd_start`.
- Byte-copy + `tests/peer-job-runner-parity.test.ts`.
- Unit fixture `windows_platform` for portable Windows path mocks.

---

## Implementation Units

### U1. Keep absolute non-WSL bash; resolve only bare names

**Goal:** Explicit absolute shells are preserved; bare and System32 paths still go through preference / fail-closed.

**Requirements:** R1, R2, R3, R4

**Dependencies:** None

**Files:**
- Modify: `skills/ce-doc-review/scripts/peer-job-runner.py` (canonical), then copy to `skills/ce-code-review|ce-pov|ce-work|ce-plan|ce-brainstorm/scripts/peer-job-runner.py`
- Modify: `tests/fixtures/peer-job-runner-unit.py`

**Approach:**
1. Add shared helper per KTD1.
2. Replace unconditional resolve in `cmd_start` bash basename branch with the helper; set `windows_posix_shell` from the kept/resolved path.
3. Same for `_popen_argv` bash/sh head and env-token rewrite.
4. Tests: absolute portable kept; absolute System32 not kept; bare `bash` still rewritten to preferred; env `… absolute_bash …` kept when non-WSL.

**Execution note:** Test-first in the unit fixture (absolute-keep must fail on current code), then implement helper + wire sites, then parity copy.

**Patterns to follow:** `#1268` resolver and System32 helper; `windows_platform` fixture.

**Test scenarios:**
- Happy: `cmd_start` / `_popen_argv` with absolute portable bash → argv0 unchanged; meta `windows_posix_shell` equals that path when recorded.
- Happy: bare `bash` still becomes preferred absolute Git Bash.
- Edge: absolute System32 bash → not used; resolver preferred or fail-closed with actionable message.
- Edge: env-prefixed argv with absolute non-WSL bash token → token kept.
- Error: absolute path that does not exist → preflight failure, no detach.
- Integration: six skill copies byte-identical after copy.

**Verification:** Unit fixture green under Windows and under `windows_platform` on Linux semantics; parity test green; no smoke policy change required.

---

## Verification Contract

- `python tests/fixtures/peer-job-runner-unit.py` exits 0 / prints OK.
- `bun test tests/peer-job-runner-parity.test.ts` passes.
- Manual or fixture assertion covering absolute-keep vs bare-rewrite.

---

## Definition of Done

- Codex P2 resolved on PR #1292 head.
- Origin plan absolute-prefix edge case restored without weakening bare-name Git Bash preference.
- Parity intact across six runners.

## Sources & Research

- https://github.com/EveryInc/compound-engineering-plugin/pull/1292#discussion_r3690011704
- `docs/plans/2026-07-31-002-fix-prefer-git-bash-over-wsl-plan.md` (U2 edge: absolute non-System32 left as-is)
- Prior residual: WindowsApps/Sysnative still out of scope

SHA-256: fcb9d00625036a426b6dae2e57adcd71eadca85798a2b85bbac7b0a40bcc0269