Skip to content

fix(source-control): convert the mktemp path to Windows mixed form before it feeds the Write tool - #3497

Merged
kyle-sexton merged 2 commits into
mainfrom
fix/worktree-create-msys-path-emit
Aug 31, 2026
Merged

fix(source-control): convert the mktemp path to Windows mixed form before it feeds the Write tool#3497
kyle-sexton merged 2 commits into
mainfrom
fix/worktree-create-msys-path-emit

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Closes #3496

Summary

plugins/source-control/skills/worktree/context/create.md step 1 told the model to print a bare mktemp -d path and hand it onward — as the Write tool's file_path (the worktree-root / data-root steps) and substituted into the later Bash block. On Windows, Git Bash's GNU mktemp prints the POSIX literal /tmp/tmp.XXXXXXXXXX (TMPDIR is unset; MSYS mounts /tmp on %TEMP%). The Write tool is native node.exe: Win32 resolves the leading / against the current drive, so the file lands in a phantom C:\tmp\tmp.XXXXXXXXXX while the real directory sits in %TEMP%. Nothing errors — the silent drive-root class docs/conventions/windows-path-emit/README.md owns, previously worked as #2834. Confirmed live on this machine: an empty C:\tmp\tmp.rSFIkHm5DO created at 14:15:56 on 2026-08-30 by exactly this path.

Fix

The snippet now converts before printing, per the windows-path-emit convention:

root_dir="$(mktemp -d)"
case "${OSTYPE:-}" in
msys* | cygwin* | win32) root_dir="$(cygpath -m -l -- "$root_dir")" || exit 2 ;;
esac
printf '%s\n' "$root_dir"
  • Rule 3 — cygpath, mixed form. -m (C:/...) is correct for both consumers of the one printed value: the Write tool and the later Git Bash block (--fallback-root-file, rm -rf), so no half-converted split arises (Rule 2). -l additionally expands the 8.3 short name %TEMP% carries on this machine (KYLESE~1), whose ~ misbehaves downstream.
  • Rule 4 — fail loud. || exit 2; the unconverted MSYS literal is never printed, because it is precisely what writes to the wrong place. Non-Windows passes through unchanged, gated on $OSTYPE exactly as scripts/emit-windows-path.sh does. The helper itself is marketplace-root tooling not shipped inside the plugin, and create.md is markdown instruction — so its contract is inlined, not called.
  • mktemp -d -p "$TEMP" rejected, twice over: mktemp -p is an ACTIVE flagged GNU/BSD-divergence token in scripts/shell-portability-tokens.txt (it would fail the portability gate), and it yields mixed separators (C:\...\Temp/tmp.XXX).
  • The "load-bearing details" list now documents the conversion (two entries → three) so the next editor does not revert it as noise. Both original properties are preserved: mktemp -d (directory exists, file inside does not — the Write-overwrite refusal) and the deliberate Write-tool round-trip for the raw ${user_config.worktree_root} substitution.

No sibling doc in the skill repeats the snippet (checked: the only other occurrence is the evals.json grading criterion, which is form-agnostic and stays accurate).

Verification

  • Executed the new snippet live in Git Bash on Windows: prints C:/Users/KyleSexton/AppData/Local/Temp/tmp.qiZCfYGBa6 — mixed form, short name expanded. With OSTYPE=linux-gnu the path passes through unchanged, exit 0. With a stubbed failing cygpath, exit 2 and zero stdout.
  • scripts/check-shell-portability.sh --paths <file>: clean ("No unexcused GNU-only constructs").
  • scripts/check-changed-skills.sh origin/main: CHECK-SKILL worktree PASS — 0 errors.
  • npx markdownlint-cli2 <file>: 0 issues.
  • scripts/affected-tests.sh --run --explain: the changed file is the excused *.md class (hygiene lane); basename over-selection picked 6 shell suites that merely name create.md. Four completed locally, zero failures — repo-local telemetry sink (2 PASS), claude-ops telemetry sink (PASS=16 FAIL=0), flag-commit-pr-skill-bypass (PASS=35 FAIL=0), check-hook-wiring-liveness (all assertions passed) — plus 71 green assertions from the two pr-linkage suites before the local 10-minute tool cap cut them off (0 failures observed; they still run detached locally). CI re-runs the authoritative suites on this PR.
  • Independent fresh-context verifier: 8/8 criteria PASS (mktemp -d property, mixed-form emission, POSIX pass-through, fail-loud, no mktemp -p, doc coherence for both consumers, markdownlint, convention-link resolution). Non-blocking notes only (an empty temp dir is orphaned in %TEMP% if cygpath fails — OS temp cleaning covers it).

Related

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T01:24:39.306768Z 65195ae PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 3m 22s —— View job


Code review complete

  • Gather context (PR diff, changed file, /review:code-review criteria)
  • Review plugins/source-control/skills/worktree/context/create.md for correctness and maintainability
  • Post findings

No blocking findings. This is a small (+7/-2), well-scoped fix and it holds up under review:

  • The case "${OSTYPE:-}" in msys* | cygwin* | win32) gate matches scripts/emit-windows-path.sh's host gate exactly (scripts/emit-windows-path.sh#L96-L97).
  • cygpath -m -l combining mixed-form with 8.3 long-name expansion isn't invented for this PR — it's the same flag combination already established in lib/hook-utils.sh's hook::expand_8dot3 (cygpath -l -m, lib/hook-utils.sh#L438-L439) and the paired-path check (cygpath -lm, lib/hook-utils.sh#L650-L651). -l requires -w/-m per cygpath's own contract, so the pairing is correct usage, not a novel risk.
  • || exit 2 fails loud on a cygpath failure (including "not found") before anything is printed, matching Rule 4 of the convention doc it cites.
  • The relative link to docs/conventions/windows-path-emit/README.md resolves correctly from the file's location, and Rules 3–4 as quoted in the new bullet accurately reflect that doc's text.
  • Reusing the single converted mixed-form value for both the Write tool and the later Bash block (rm -rf, the helper invocation) is a deliberate call against Rule 2's "convert at the boundary, not the source" — but it's the right call here: Git Bash/MSYS accepts C:/... mixed-form paths transparently in [[ -f ]]/cat/rm, so there's no half-converted split, only one crossing point for both consumers.
  • The eval file's expectation (plugins/source-control/skills/worktree/evals/evals.json) is confirmed form-agnostic — it only checks for a mktemp -d directory and the Write-tool handoff pattern, not path spelling — so it doesn't need updating alongside this fix, as the PR description claims.

Author-claimed verification (live Git Bash run, check-shell-portability.sh, check-changed-skills.sh, markdownlint-cli2, affected-tests.sh) is noted but not independently re-run in this review pass.

@claude

claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 30s —— View job


Security review — /review:security-review

  • Gather context (PR diff, changed file)
  • Apply skip-gate check
  • Post final result

Result: no security-relevant surface — skip gate applies.

The change is confined to plugins/source-control/skills/worktree/context/create.md, converting a mktemp -d path to Windows mixed form before it's used as a Write tool file_path. Reviewed for the things this lane hunts (logic/trust-boundary flaws, injection, credential handling, dangerous Actions patterns):

  • root_dir originates from mktemp -d output — not user- or attacker-influenced input, so there's no injection surface to exploit via the cygpath call.
  • cygpath -m -l -- "$root_dir" quotes the variable and uses -- to block option injection even in a hypothetical adversarial-path scenario.
  • || exit 2 is a fail-loud path (no silent fallback to an unconverted/unsafe value) — a defensive improvement, not a new gap.
  • No GitHub Actions workflow, permissions, secrets handling, or dependency pins are touched — this is a markdown instruction file for an agent skill, not executable CI config.
  • The pre-existing shell-injection note in this same file about ${user_config.worktree_root} (handled via the Write tool rather than shell interpolation) is unchanged by this diff and out of scope for this review.

No security findings to report for this PR.

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown

Last security-reviewed head: 9f4a700fb197626a3f91686e63f464b2caf43c30. On the next push, the relevance gate compares only the commits since this SHA; delete this comment to force a full re-review.

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown

Claude has reviewed this PR 3 times. The lane skips further automatic reviews after 5; deleting this comment resets the count.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65195aee49

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/source-control/skills/worktree/context/create.md
@claude

claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 38s —— View job


Security review — /review:security-review

  • Gather context (PR diff, changed files, prior review state)
  • Apply skip gate
  • Post final result

Result: skip gate applies — no new security-relevant surface since the last completed review.

The last security review on this PR ran at head 65195aee and covered the only security-relevant change in this PR: plugins/source-control/skills/worktree/context/create.md (converting a mktemp -d path to Windows mixed form before it feeds the Write tool). That review found no security findings: root_dir originates from mktemp -d, not attacker-controlled input; cygpath -m -l -- "$root_dir" is properly quoted with -- to block option injection; || exit 2 fails loud instead of silently falling back to an unsafe path; no GitHub Actions workflow, permissions, or secrets handling is touched.

The commits added since that review (65195aec9230f4) only touch:

Both are metadata/documentation with no logic, trust-boundary, injection, credential, or Actions-workflow surface. This addresses the Codex P1 comment from the prior commit (publish the fix under a new plugin version), and introduces nothing new to review.

No security findings to report for this PR.

@claude

claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 2m 47s —— View job


Code review complete

  • Gather context (PR diff, changed file, /review:code-review criteria)
  • Review changed files for correctness and maintainability
  • Post findings

No blocking findings.

Reviewed the current HEAD (c9230f4), which includes both commits: the create.md conversion fix and the follow-up version/changelog bump.

  • The new snippet (create.md L87-93) matches the established cygpath-conversion precedent exactly: the case "${OSTYPE:-}" in msys* | cygwin* | win32) gate is byte-identical to scripts/emit-windows-path.sh#L96-L97, and cygpath -m -l combining mixed-form with 8.3 expansion mirrors lib/hook-utils.sh's hook::expand_8dot3 / paired-path-strip call sites — not novel usage.
  • root_dir="$(cygpath ...)" || exit 2 correctly propagates cygpath's exit status through the assignment (bash sets $? from the command substitution), so a missing/failing cygpath fails loud with nothing printed to stdout, per Rule 4 of the windows-path-emit convention it cites.
  • Verified the relative link ../../../../../docs/conventions/windows-path-emit/README.md resolves correctly from create.md's location (5 levels up from skills/worktree/context/ reaches repo root).
  • Verified evals/evals.json is indeed form-agnostic (checks for the mktemp -d + Write-tool handoff pattern, not path spelling), confirming the PR's claim that it didn't need a matching update.
  • The Codex P1 finding on this PR ("publish under a new plugin version") is already resolved in the current HEAD — plugin.json is bumped to 0.55.32 with a matching CHANGELOG.md entry.

One non-blocking observation: the inlined snippet doesn't carry the reference helper's [[ -n "$converted" ]] empty-output guard (scripts/emit-windows-path.sh#L112) — if cygpath ever exited 0 with empty output, root_dir would go empty and the later rm -rf "<root_dir>" would become a silent no-op instead of erroring. mktemp -d always hands cygpath a real, just-created directory, so this is very unlikely to fire in practice; not blocking.

Author-claimed verification (live Git Bash run, check-shell-portability.sh, check-changed-skills.sh, markdownlint-cli2, affected-tests.sh) is noted but not independently re-run in this pass — this lane's tool grant doesn't include arbitrary script execution.

kyle-sexton and others added 2 commits August 31, 2026 01:56
…fore it feeds the Write tool

The worktree create context told the model to print a bare `mktemp -d`
path and use it as a `Write` tool `file_path`. On Windows Git Bash that
prints the MSYS literal `/tmp/tmp.XXXXXXXXXX`; the Write tool's native
side resolves the leading `/` against the current drive, creating a
phantom `<drive>:\tmp\...` while the real directory sits in %TEMP% —
the same silent drive-root class as #2834. Confirmed live: an empty
`C:\tmp\tmp.rSFIkHm5DO` created by exactly this path.

Per docs/conventions/windows-path-emit/README.md Rule 3 (cygpath mixed
form, correct for both the Write tool and the later Bash consumers of
the same printed value; `-l` expands the 8.3 short name %TEMP% carries)
and Rule 4 (fail loud — `|| exit 2`, never the unconverted literal).
Non-Windows passes through unchanged, gated on $OSTYPE exactly as
scripts/emit-windows-path.sh does; the helper itself is marketplace-root
tooling the shipped plugin cannot call, so the contract is inlined.

`mktemp -d -p "$TEMP"` was rejected: `mktemp -p` is an active flagged
token in scripts/shell-portability-tokens.txt, and it yields mixed
separators anyway. The load-bearing-details list now documents the
conversion so it is not reverted as noise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ath fix to existing installs

The plugin version is the consumer update-cache key: without a bump,
installations already carrying an earlier version keep the old create.md
and never receive the Windows mixed-form conversion, so the phantom
drive-root write stays live on every existing install. Bump the manifest
and add the matching '## [0.55.34]' release entry (changelog-parity
--check-bump requires the pair; 0.55.31-0.55.33 are already published on
main and may not be reused — originally staged as 0.55.32, renumbered on
rebase after main released 0.55.32 and 0.55.33).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton force-pushed the fix/worktree-create-msys-path-emit branch from c9230f4 to 9f4a700 Compare August 31, 2026 09:58
@claude

claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 57s —— View job


/review:code-review — CI code-review lane

  • Gather context (PR diff, changed files, prior review state, /review:code-review criteria)
  • Apply skip gate
  • Post result

Result: skip gate applies — no new review surface since the last completed review from this lane.

This PR was already reviewed by this lane at head c9230f4 (see the comment posted 2026-08-31T05:07:35Z), which covered both the create.md mktemp/cygpath conversion fix and the version-bump/changelog commit that addressed the Codex P1 finding, and found no blocking issues.

The only commit added since then (9f4a700) does not touch create.md at all — it only renumbers the version:

This is a mechanical rebase renumbering: origin/main published 0.55.32 and 0.55.33 while this branch was in flight, so the original 0.55.32 bump would have collided (PUBLISHED VERSION REUSE). Confirmed origin/main's plugin.json is currently at 0.55.33, so 0.55.34 is next-available and non-colliding — the bump is correct.

Since the content already reviewed is unchanged and the delta is a trivial, non-colliding version renumber with no logic to evaluate, there is nothing new to flag. Standing non-blocking note from the prior review still applies for context: the inlined snippet omits the reference helper's empty-output guard ([[ -n "$converted" ]]) after cygpath, so a cygpath that exited 0 with empty output would make the later rm -rf "$root_dir" a silent no-op — extremely unlikely since mktemp -d always hands cygpath a real, just-created directory. Not blocking.

No new findings to report for this PR.

@claude

claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 43s —— View job


Security review — /review:security-review

  • Gather context (PR diff, changed files, prior review state)
  • Apply skip gate
  • Post final result

Result: skip gate applies — no new security-relevant surface since the last completed review.

The last security review on this PR ran at head c9230f4 and covered the security-relevant change: plugins/source-control/skills/worktree/context/create.md (converting a mktemp -d path to Windows mixed form before it feeds the Write tool). That review found no security findings: root_dir originates from mktemp -d, not attacker-controlled input; cygpath -m -l -- "$root_dir" is quoted with -- to block option injection; || exit 2 fails loud instead of silently falling back to an unsafe value; no GitHub Actions workflow, permissions, or secrets handling is touched.

The commits added since that review (c9230f49f4a700) only touch:

Both are metadata/documentation with no logic, trust-boundary, injection, credential, or Actions-workflow surface (this is the marketplace release commit publishing the fix under a new plugin version — the version bump requested by the earlier Codex P1 finding). Nothing new to review.

No security findings to report for this PR.

@kyle-sexton
kyle-sexton merged commit b21798f into main Aug 31, 2026
60 checks passed
@kyle-sexton
kyle-sexton deleted the fix/worktree-create-msys-path-emit branch August 31, 2026 13:34
kyle-sexton added a commit that referenced this pull request Aug 31, 2026
…ume-root entries (#3498)

## Summary

Adds machine-health catalog check #19, `drive-root-litter`, so stray
files and directories at fixed-volume roots surface on a routine health
run instead of only during a manual disk audit. The audit that prompted
this found an empty `C:\tmp` path-translation artifact and a 0-byte
`C:\log.txt` owned by BUILTIN\Administrators — neither visible to any
existing check.

Closes #3499

## Fix

- **New check** `scripts/windows/checks/Test-DriveRootLitter.ps1`: lists
each fixed-volume root non-recursively and diffs it against an
expected-entry baseline. Read-only; unelevated; Windows-only.
- **Baseline is data, not logic**:
`references/windows/drive-root-baseline.jsonc` holds the expected sets
(any-volume housekeeping / system-drive-only / known litter-name shapes)
as type-aware, case-insensitive `-like` patterns. Admitting a newly
legitimate entry is a data edit, never a script change.
- **Per-volume posture**: system drive gets the full baseline diff;
non-system fixed volumes (data drives, Dev Drives) legitimately hold
arbitrary user content, so only litter-name shapes (`tmp`, `temp`,
`tmp.*`, `log.txt`, `*.tmp`) are reported there. Removable/network
drives never scanned.
- **Proportionate severity**: OK / INFO (1–9 residue) / WARN (≥10,
something actively dumping) / UNKNOWN (baseline unreadable or a root
unlistable). Never CRIT, and excluded from the trend engine's generic
upward upgrade (`Invoke-TrendAnalysis.ps1` maps it to `residue_count`
for history only).
- **Trend-aware**: deterministic output (sorted residue, day-granularity
created dates) so an unchanged dropping feeds `identical_streak`
demotion instead of reading as news weekly.
- Catalog entry in `checks.jsonc`, rubric §19 in
`references/windows/check-catalog.md`, plugin bumped to 0.12.0 with
changelog entry. Owner and directory-emptiness probes are best-effort
(`try/catch` → `null`) so a denied ACL read degrades instead of
erroring.

## Verification

- **Real `C:\` acceptance run (unelevated)**: the manual audit's ground
truth was exactly three unexpected entries, and the check reported
exactly those three — `C:\symbols` (directory, SymSrv store), `C:\tmp`
(directory, path-translation artifact), `C:\log.txt` (0-byte file, owner
BUILTIN\Administrators read without elevation) — plus one entry that
postdates the audit: `C:\worktrees`, an empty user-owned directory
created 2026-08-30 14:24:32 and registered to no worktree in either
repo's `git worktree list` — a second instance of the same leak CLASS
from a different producer, not a recurrence of the same artifact. The
`C:\tmp` leak itself fired once, at 2026-08-30 14:15:56, from the
`mktemp -d` path in
`plugins/source-control/skills/worktree/context/create.md` (addressed
separately in PR #3497); it was never deleted or re-created. Zero false
positives: 16 of 20 `C:\` root entries matched baseline, all stock names
(Windows, Program Files, ProgramData, Users, PerfLogs, Recovery,
$Recycle.Bin, System Volume Information, pagefile.sys, swapfile.sys,
DumpStack.log.tmp, Documents and Settings, OneDriveTemp, Config.Msi, …)
suppressed. `D:\tmp` reported on the data volume via the litter-name
posture.
- New Pester suite `Test-DriveRootLitter.Tests.ps1`: 16/16 — clean
baseline-only root, stray file, stray directory, type-aware matching,
severity ladder (never CRIT), non-system posture, UNKNOWN degradations,
mutation-free assertion, deterministic ordering.
- `Invoke-TrendAnalysis` suite extended for the new mapping: 12/12.
- Full machine-health suite: 440 passed; the single failure
(`Test-EnvironmentHealth` %VAR% expansion) is
machine-environment-dependent and fails identically on an untouched main
checkout.
- `scripts/affected-tests.sh`: every changed file maps (`--explain` exit
0); `--run` exit 3 as documented (all selected suites are Pester,
covered above).
- markdownlint-cli2 clean on changed markdown; PSScriptAnalyzer clean on
the check script (test helper carries the same accepted warning as its
siblings).
- Independent fresh-context verifier passed all 11 acceptance criteria
(catalog shape, rubric anchor, data-only baseline, zero stock false
positives, file+dir reporting, severity vocabulary, no mutation, honest
needs_admin, windows-only os, Pester, determinism) with no defects.

## Related

Closes #3499. The repo-tooling `scripts/check-drive-root-litter.sh` is
intentionally untouched — it is CI tooling being widened separately, not
a machine-health check.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01Egdf11hXdBch1HTFmjB8FV

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

source-control: worktree create.md emits a bare mktemp -d POSIX path that the native Write tool resolves to a phantom drive-root tmp

1 participant