Skip to content

Windows: eval "$(gstack-paths)" strips backslashes from TMP_ROOT — mktemp fails and skills exit before Codex runs #2374

Closed

Description

@fangearhq-boop

Summary

On Windows (git-bash/MSYS), eval "$(gstack-paths)" corrupts TMP_ROOT before any skill
gets to use it. $TMP on Windows is a backslash path like C:\Users\me\AppData\Local\Temp,
and bin/gstack-paths emits it as a bare, unquoted assignment. eval then consumes the
backslashes as escape sequences, so the caller ends up with C:UsersmeAppDataLocalTemp.

Every skill that does mktemp "$TMP_ROOT/..." after Step 0.6 then fails, and the failure
cascades into exactly the same "empty TMPERR → : No such file or directory" symptom
already documented for macOS in #2091 — but from a different root cause, so the fix there
does not help here.

Environment

  • gstack: 1.60.1.0 (a3259400)
  • OS: Windows 11 Pro 26200, MINGW64_NT-10.0-26200
  • bash: GNU bash 5.2.37(1)-release (x86_64-pc-msys)
  • TMPDIR: unset (normal on Windows)
  • TMP: C:\Users\me\AppData\Local\Temp

Reproduction

P=~/.claude/skills/gstack/bin/gstack-paths

# The script's own output is correct:
env -u TMPDIR TMP='C:\Users\test\AppData\Local\Temp' bash "$P" | grep TMP_ROOT
# TMP_ROOT=C:\Users\test\AppData\Local\Temp

# ...but the documented usage, eval, corrupts it:
env -u TMPDIR TMP='C:\Users\test\AppData\Local\Temp' bash -c 'eval "$('"$P"')"; echo "[$TMP_ROOT]"'
# [C:UserstestAppDataLocalTemp]        <-- every separator gone

# A path with a space is worse — eval word-splits it and TMP_ROOT ends up empty:
env -u TMPDIR TMP='C:\Program Files\Temp' bash -c 'eval "$('"$P"')"; echo "[$TMP_ROOT]"'
# bash: line 3: FilesTemp: command not found
# []

Downstream impact

skills/codex/SKILL.md Step 2A (and every other skill using $TMP_ROOT) does:

TMPERR=$(mktemp "$TMP_ROOT/codex-err-XXXXXX.txt")
... codex review "..." 2>"$TMPERR"

With the corrupted value:

mktemp: failed to create file via template 'C:UserstestAppDataLocalTemp/codex-err-XXXXXX.txt': No such file or directory
bash: line 1: : No such file or directory
exit=1

mktemp fails, TMPERR is empty, and 2>"$TMPERR" is a redirect to an empty filename, so
Codex never runs at all. The user sees an immediate exit 1 with no review and no
obvious cause. In the C:\Program Files case TMP_ROOT is empty instead, so mktemp
targets the filesystem root and fails with Permission denied.

This is the same user-visible failure as #2091 / #2370, which is worth noting because
those are both filed as macOS/BSD-mktemp problems — a Windows user hitting this will find
them, try the BSD fix, and still be broken.

Root cause

bin/gstack-paths ends with:

echo "GSTACK_STATE_ROOT=$_state_root"
echo "PLAN_ROOT=$_plan_root"
echo "TMP_ROOT=$_tmp_root"

The documented contract is eval "$(gstack-paths)", so these lines are re-parsed by the
shell. An unquoted RHS means backslashes are escapes and spaces are word separators.

The header comment does anticipate hostile values:

Security: output values are not sanitized — callers may receive paths with shell-special
characters if env vars contain them. Skills should always quote expansions
("$GSTACK_STATE_ROOT", not $GSTACK_STATE_ROOT).

That mitigation does not apply to this failure. The corruption happens during eval,
before the caller has any variable to quote. Callers already quote correctly and are still
broken. Note this is not only a Windows concern: any POSIX TMPDIR containing a space or
backslash corrupts the same way.

Proposed fix

Emit shell-quoted assignments so eval round-trips the exact value. printf %q is a bash
builtin and the script is already #!/usr/bin/env bash:

-echo "GSTACK_STATE_ROOT=$_state_root"
-echo "PLAN_ROOT=$_plan_root"
-echo "TMP_ROOT=$_tmp_root"
+printf 'GSTACK_STATE_ROOT=%q\n' "$_state_root"
+printf 'PLAN_ROOT=%q\n' "$_plan_root"
+printf 'TMP_ROOT=%q\n' "$_tmp_root"

Verified round-trip through eval on all four cases:

Input Result
C:\Users\test\AppData\Local\Temp PASS
C:\Program Files\Temp PASS
/tmp/o'brien PASS
/plain/posix/path PASS

One tradeoff worth a maintainer decision: the header also documents a plain
non-eval usage (gstack-paths → prints GSTACK_STATE_ROOT=...). With %q, POSIX paths
print unchanged, but a Windows path prints escaped (C:\\Users\\...). If that
human-readable form matters, an alternative is keeping echo and changing the contract to
set -a; . <(gstack-paths), though that is a wider change across every skill's Step 0.6.

Notes

Found while dogfooding /codex review on Windows. Working around it with a literal
TMP_ROOT=/tmp before the mktemp makes /codex review work normally on 1.60.1.0,
which isolates this as the only blocker on that path.

Related but distinct: #2091, #2370 (same cascade, BSD mktemp root cause, macOS),
#1782 (gstack-config truncating paths at the first space — same unquoted-path family).

Activity

  1. fangearhq-boop commented on Jul 28, 2026

    @fangearhq-boop
    Author

    Fix is up in #2376 (three lines in bin/gstack-paths plus eval-round-trip regression tests).

    Note for anyone following the thread: an earlier PR #2375 with the identical commit was closed automatically when its head fork was deleted by mistake. #2376 is the live one.

  2. garrytan commented on Aug 17, 2026

    @garrytan
    Owner

    Fixed on main in v1.64.0.0 — gstack-paths no longer strips backslashes on Windows. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions