# Windows Host Paths

> Windows host paths — the space in `D:\GitHub Projects\…` defeats naive parsers

- Skill: `ghostlygawd/windows-host-paths` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ghostlygawd/windows-host-paths`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ghostlygawd/windows-host-paths/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: GhostlyGawd (https://skillmd.com/u/ghostlygawd)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ghostlygawd/windows-host-paths

---


# Windows host paths — the space in `D:\GitHub Projects\…` defeats naive parsers

On this host the repo lives at **`D:\GitHub Projects\recursive-harness`** — the
path contains a space. Any tool or guard that splits a command/path on whitespace
*before* honoring quotes truncates it to `D:\GitHub` and then acts on (or refuses)
the wrong target. The failure is usually SILENT — a no-op or a misleading block,
not a clear error — so it reads as "my command was wrong" when the real cause is
the space. When an op you KNOW is valid is blocked or quietly does nothing on this
checkout, **suspect the space first**, and fix it structurally — do not retry with
a different quoting trick (it fails the same way).

## Manifestation A — a leading env-var bypass token must be the FIRST token

`HARNESS_TRUNK_LEASE_OK=1` (and similar guard-bypass prefixes) only register when
the token is the **literal leading token** of the command. The guard regex allows
ONLY preceding `VAR=value` assignments whose values contain **no spaces**. These
silently defeat the bypass — the op stays BLOCKED with no new error:

- a leading `cd …` before the token,
- a multi-line script whose first line isn't the token,
- a preceding assignment to a space-containing path, e.g.
  `HARNESS="$(…)"` → `/d/GitHub Projects/recursive-harness` or
  `MAIN="D:/GitHub Projects/…"` (the space breaks the regex's `\S*`).

**Fix:** lead with the bypass, inline the path, and never put `cd` or a
space-valued `VAR=` before it:

```bash
HARNESS_TRUNK_LEASE_OK=1 git -C "/d/GitHub Projects/recursive-harness" worktree remove …
```

Use `git -C "<path>"` (or `os.chdir` in Python) instead of a leading `cd`. If you
need the harness path in a variable, set it AFTER the leading token, or hardcode
the POSIX form in the command.

## Manifestation B — PowerShell file-safety guard misfires → switch to Bash

A destructive PowerShell op (`Remove-Item`, `Move-Item`) on a legitimate path under
the repo gets blocked as **`protected system path 'D:\GitHub' is blocked`**. The
truncated `D:\GitHub` in that message is consistent with the built-in guard splitting
the path at the space and matching the prefix against its protected-roots list — but
this is the Claude Code built-in safety layer, not a harness hook, so the internal
mechanism is INFERRED from the symptom, not reproduced. What IS established by outcome:
retrying with different PowerShell quoting blocks AGAIN — it blocked twice in one
session before switching shells worked.

**Fix:** use the **Bash tool** with a POSIX-quoted path instead:

```bash
f="/d/GitHub Projects/recursive-harness/cartograph/map.json"; rm -f "$f"
```

The Bash tool quotes the whole path as one argument, so the space never splits it.

## Manifestation C — the harness's OWN guard has the same bug (locked)

`hooks/guard_worktree_isolation.py` has the same first-space truncation, pinned in
the merged proposal to `_expand_simple_vars` (line 315): its assignment-value regex
`([^\s;&|()]+)` stops at the first space, so a `$VAR` holding this repo's path
expands TRUNCATED into a phantom `.claude/worktrees/<name>` operand. The fix is
enforcement-locked and tracked in `proposals/resolved/P-2026-014-guard-worktree-space-split.md`
(human merges). Until it lands, expect worktree-path guard messages to show a
truncated `D:\GitHub`.

## Manifestation D — the user's shell is NOT my Bash tool

Rule 1 below ("prefer the Bash tool") is about the ASSISTANT's own tools, where Git
Bash is on PATH. It is WRONG guidance the moment it's copied into a command handed to
the **user**, or into a shipped script the user runs: their interactive shell is
**Windows PowerShell 5.1**, where `&&` is a parse error, a `/d/…` POSIX path resolves to
`D:\d\…` (wrong), and a `.sh` shebang is ignored (silent no-op). `bash` itself may not even
be on the user's PATH (this user hit `CommandNotFoundException`) — but DON'T rely on that
as the failure mode: even when Git-for-Windows puts `bash` on PATH, those bash idioms still
fail or run subtly wrong, so bash is unsafe to hand the user either way. Conflating "the Bash
tool I have" with "the shell the user has" produced three dead invocations in a row before
the user pushed back (session b46882f7).

**Fix:** anything the user will RUN ships as **native PowerShell** (`.ps1`), and any
command you give the user must be valid PS 5.1 — never `bash`, `&&`, `/d/…` paths, or
`./script.sh`. **Verify by RUNNING it in PS 5.1** (`powershell -ExecutionPolicy Bypass
-File …`) before handing it over; do not assert an untested command works.

## Manifestation E — PS 5.1 and pwsh 7 differ; test a `.ps1` under BOTH

A `.ps1` that passes under one runtime can break under the other. Two confirmed traps
(both in the heal ledger):

- A **non-ASCII char** (e.g. em-dash `—`) in a UTF-8-no-BOM `.ps1` is read as ANSI by PS
  5.1 and cascades into `Missing closing '}'` parse errors — pwsh 7 reads UTF-8 and masks
  it. Keep harness `.ps1` files **ASCII-only**; verify with a byte scan + `Parser::ParseFile`.
- `Move-Item` on a **directory** is an atomic rename under 5.1 but a non-atomic copy+delete
  under pwsh 7, leaving a **partial** destination when the source is locked. Use
  `[System.IO.Directory]::Move` for atomic same-volume dir moves that fail cleanly.

**Procedure that caught both:** a test that runs the tool as a child process under BOTH
`powershell` (5.1) AND `pwsh` (7), asserting pass in each (see
`tests/test-sync-account-sessions.ps1`).

**FALSE-GREEN trap (the tool is NOT the user's shell):** the assistant's PowerShell *tool*
runs **pwsh 7+** (where `&&`, ternary, `??` work), but the user's interactive shell is **5.1**
(where they fail). A green tool-side run is NOT proof a user-facing command works in 5.1 —
write 5.1-safe syntax (`;` not `&&`), and to actually confirm, have the USER run it in their
own session (the `! <command>` prefix runs a command inline there).

## Manifestation F — native-Windows `python`/`gh` can't open a `/d/…` path passed as a string LITERAL

The Bash tool is Git Bash (MSYS). MSYS rewrites a `/d/GitHub Projects/…` POSIX path to
`D:\…` **only when it appears as a command-line ARGUMENT** — it does NOT touch a path
string that lives *inside* a program. So a native-Windows `python`/`gh`/`node` launched
from the Bash tool receives the literal `/d/…` and fails — usually a bare
`FileNotFoundError` that reads as "my path was wrong":

```bash
python -c "open('/d/GitHub Projects/recursive-harness/state/x.json')"
# FileNotFoundError — native python sees a literal /d/.. it can't resolve;
# MSYS converted nothing because the path was never an argv entry.
```

This is the inverse of Manifestation A/B: there, `/d/…` is the RIGHT form because the
Bash tool quotes it as an argv arg; here the path is a string constant the child process
reads verbatim, so MSYS never sees it. Hit twice during /retro 2026-06-26 (`session_owners.json`
+ `autonomy.json` reads).

**Fix:** inside a program's string literals use a **Windows path** (`D:/GitHub Projects/…` —
forward slashes are accepted by the Windows API and dodge backslash-escaping), OR keep the
path OUT of the literal and hand it where MSYS DOES translate it — as an argv arg, via
`git -C "<path>"`, or `os.chdir(...)`. For a plain read, the **Read tool** sidesteps the
shell entirely and never hits this.

## Rules

- For the ASSISTANT's OWN file ops on this checkout, prefer the **Bash tool + POSIX
  `/d/GitHub Projects/…` paths** over PowerShell — POSIX quoting survives the space
  cleanly. But anything the USER runs is PowerShell 5.1 (Manifestation D) — never hand
  them bash.
- Lead any guard-bypass env token; never precede it with `cd` or a space-valued
  `VAR=` assignment. Use `git -C`/`os.chdir`, not `cd`.
- Quote EVERY path that touches the repo root, in every shell.
- A `/d/…` path inside a program's string LITERAL is NOT MSYS-translated (only argv
  args are) — use `D:/…`, `git -C`/`os.chdir`, or the Read tool (Manifestation F).
- A block or no-op on a known-good path is a SPACE smell, not a you-were-wrong
  smell. Don't re-quote-and-retry in the same shell — change the structure.

