fix(paths): shell-quote gstack-paths output so eval round-trips values

gstack-paths emitted bare KEY=VALUE lines, so the documented
eval "$(gstack-paths)" re-parsed the values: backslashes were eaten as
escapes (Windows $TMP C:\Users\... became C:Users...) and a space
word-split the assignment, leaving the variable empty. Emit each value
with printf %q so eval round-trips byte-for-byte; plain POSIX paths are
unchanged. Round-trip regression tests cover backslashes, spaces, and
embedded quotes.

Closes #2374.

Contributed by @fangearhq-boop (PR #2376); same fix independently by
@yannickspiess (PR #1580).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-14 20:21:01 -07:00
co-authored by Claude Fable 5
parent 4122721eb0
commit 72aff66438
2 changed files with 63 additions and 6 deletions
+12 -6
View File
@@ -13,9 +13,15 @@
# PLAN_ROOT: GSTACK_PLAN_DIR -> CLAUDE_PLANS_DIR -> $HOME/.claude/plans -> .claude/plans
# TMP_ROOT: TMPDIR -> TMP -> .gstack/tmp (and mkdir -p, best-effort)
#
# 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).
# Output: values are emitted shell-quoted (printf %q) so `eval` round-trips them
# byte-for-byte. This matters on Windows, where $TMP is a backslash path like
# C:\Users\me\AppData\Local\Temp — with a bare `echo`, eval consumes the
# backslashes as escapes and the caller gets C:UsersmeAppDataLocalTemp. A value
# containing a space (C:\Program Files\Temp) is worse: eval word-splits it and
# the variable ends up empty. Quoting here is the only fix that works, because
# the corruption happens during eval, before the caller has anything to quote.
# Callers should still quote expansions ("$GSTACK_STATE_ROOT") for the same
# reason any path variable needs quoting.
set -u
# State root: where gstack writes projects/, sessions/, analytics/.
@@ -69,6 +75,6 @@ _tmp_root="${_tmp_root%/}"
# will discover that on their own write attempt. Don't fail the eval here.
mkdir -p "$_tmp_root" 2>/dev/null || true
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"