Tilde-in-quoted-string disables brain-sync silently across 42 SKILL.md files (regression of #785)
Summary
The artifacts-sync preamble and epilogue that 42 ~/.claude/skills/gstack/*/SKILL.md files include both quote a literal ~ — which bash never expands. The probe runs at every skill start and the --discover-new / --once calls run at every skill end, but all of them fail silently because every command is suffixed with 2>/dev/null || true. The user-visible effect: cross-machine handoff (e.g. /context-save on host A → /context-restore on host B) silently never pushes.
This is the same bug class as already-closed #785 ("Use $HOME instead of ~ in SKILL.md browse binary assignment") — different files, same defect, never audited across the rest of the skill set.
Affected files
$ grep -l '"~/.claude/skills/gstack/bin/' ~/.claude/skills/gstack/*/SKILL.md | wc -l
42
Includes context-save/SKILL.md, context-restore/SKILL.md, autoplan/SKILL.md, benchmark/SKILL.md, benchmark-models/SKILL.md, browse/SKILL.md, canary/SKILL.md, codex/SKILL.md, connect-chrome/SKILL.md, … (all 42 share the same template-generated block).
Concrete defect
context-save/SKILL.md, lines 369–370:
_BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync"
_BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config"
Tilde inside double quotes is literal — bash only expands ~ when it is unquoted at the start of a word, or after : / = in PATH-like assignments. These two variables hold the literal 8-byte string starting ~/....
Three downstream failures cascade from this:
-
Line 400 — config probe:
_BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || echo off)
The shell tries to exec the literal path ~/.claude/..., gets ENOENT, the 2>/dev/null || echo off swallows it, so _BRAIN_SYNC_MODE=off regardless of the user's actual config. Every downstream brain-sync branch in the preamble is dead.
-
Line 436 — preamble push:
"$_BRAIN_SYNC_BIN" --once 2>/dev/null || true
Skipped because of (1), but also broken on its own merits.
-
Lines 479–480 — end-of-skill snippet:
"~/.claude/skills/gstack/bin/gstack-brain-sync" --discover-new 2>/dev/null || true
"~/.claude/skills/gstack/bin/gstack-brain-sync" --once 2>/dev/null || true
Same defect — tilde in quoted string. Both no-op silently.
Net effect: brain-sync never runs from any skill, on any host. The only thing that triggers it in practice is the user manually invoking gstack-brain-sync --once or an external scheduler (cron, etc.).
Repro
The cleanest repro is the silent cross-machine failure users actually hit:
- Host A:
/context-save. Checkpoint markdown is written under ~/.gstack/projects/<slug>/checkpoints/<timestamp>.md, but the post-save brain-sync --once does not run.
- Host A:
cat ~/.gstack/.brain-last-push — unchanged.
- Host B:
( cd ~/.gstack && git pull ) then /context-restore — does not see the save from host A.
Forcing it manually proves it works:
"$HOME/.claude/skills/gstack/bin/gstack-brain-sync" --discover-new
"$HOME/.claude/skills/gstack/bin/gstack-brain-sync" --once
— commits and pushes the queued checkpoint immediately.
Standalone bash repro (no gstack required):
$ X="~/.claude/skills/gstack/bin/gstack-brain-sync"
$ echo "$X"
~/.claude/skills/gstack/bin/gstack-brain-sync # literal tilde
$ "$X" --status
bash: ~/.claude/skills/gstack/bin/gstack-brain-sync: No such file or directory
Workaround
Wrap brain-sync invocations in a cron entry that uses $HOME explicitly:
*/15 * * * * $HOME/.gstack/cron-sync.sh
Where cron-sync.sh is:
#!/usr/bin/env bash
set -uo pipefail
GSTACK_HOME="$HOME/.gstack"
BRAIN_SYNC="$HOME/.claude/skills/gstack/bin/gstack-brain-sync"
( cd "$GSTACK_HOME" && git pull --ff-only --quiet origin main ) || true
"$BRAIN_SYNC" --discover-new
"$BRAIN_SYNC" --once
"$BRAIN_SYNC" --status
This restores cross-machine handoff but adds up to 15-minute latency, and it requires every user on every host to know about and install it.
Fix
Single template change, regenerate all 42 SKILL.md files. The pattern is s/"~/"$HOME/ in both places:
-_BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync"
-_BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config"
+_BRAIN_SYNC_BIN="$HOME/.claude/skills/gstack/bin/gstack-brain-sync"
+_BRAIN_CONFIG_BIN="$HOME/.claude/skills/gstack/bin/gstack-config"
-"~/.claude/skills/gstack/bin/gstack-brain-sync" --discover-new 2>/dev/null || true
-"~/.claude/skills/gstack/bin/gstack-brain-sync" --once 2>/dev/null || true
+"$HOME/.claude/skills/gstack/bin/gstack-brain-sync" --discover-new 2>/dev/null || true
+"$HOME/.claude/skills/gstack/bin/gstack-brain-sync" --once 2>/dev/null || true
Optional but recommended (separate change): drop 2>/dev/null from at least the config probe so future "exec failed" errors surface instead of silently flipping artifacts_sync_mode to off. The current swallow + || echo off masks any future regression of this class.
A grep-based regression test would also catch recurrence:
! grep -RE '"~/' ~/.claude/skills/gstack/*/SKILL.md
Refs
Environment
- gstack v1.40.x
- Reproduced on macOS 14 (Darwin 25.5.0) and Debian 12 — bash semantics, not OS-dependent
Tilde-in-quoted-string disables brain-sync silently across 42 SKILL.md files (regression of #785)
Summary
The artifacts-sync preamble and epilogue that 42
~/.claude/skills/gstack/*/SKILL.mdfiles include both quote a literal~— which bash never expands. The probe runs at every skill start and the--discover-new/--oncecalls run at every skill end, but all of them fail silently because every command is suffixed with2>/dev/null || true. The user-visible effect: cross-machine handoff (e.g./context-saveon host A →/context-restoreon host B) silently never pushes.This is the same bug class as already-closed #785 ("Use $HOME instead of ~ in SKILL.md browse binary assignment") — different files, same defect, never audited across the rest of the skill set.
Affected files
Includes
context-save/SKILL.md,context-restore/SKILL.md,autoplan/SKILL.md,benchmark/SKILL.md,benchmark-models/SKILL.md,browse/SKILL.md,canary/SKILL.md,codex/SKILL.md,connect-chrome/SKILL.md, … (all 42 share the same template-generated block).Concrete defect
context-save/SKILL.md, lines 369–370:Tilde inside double quotes is literal — bash only expands
~when it is unquoted at the start of a word, or after:/=in PATH-like assignments. These two variables hold the literal 8-byte string starting~/....Three downstream failures cascade from this:
Line 400 — config probe:
_BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || echo off)The shell tries to exec the literal path
~/.claude/..., gets ENOENT, the2>/dev/null || echo offswallows it, so_BRAIN_SYNC_MODE=offregardless of the user's actual config. Every downstream brain-sync branch in the preamble is dead.Line 436 — preamble push:
Skipped because of (1), but also broken on its own merits.
Lines 479–480 — end-of-skill snippet:
Same defect — tilde in quoted string. Both no-op silently.
Net effect: brain-sync never runs from any skill, on any host. The only thing that triggers it in practice is the user manually invoking
gstack-brain-sync --onceor an external scheduler (cron, etc.).Repro
The cleanest repro is the silent cross-machine failure users actually hit:
/context-save. Checkpoint markdown is written under~/.gstack/projects/<slug>/checkpoints/<timestamp>.md, but the post-savebrain-sync --oncedoes not run.cat ~/.gstack/.brain-last-push— unchanged.( cd ~/.gstack && git pull )then/context-restore— does not see the save from host A.Forcing it manually proves it works:
— commits and pushes the queued checkpoint immediately.
Standalone bash repro (no gstack required):
Workaround
Wrap brain-sync invocations in a cron entry that uses
$HOMEexplicitly:Where
cron-sync.shis:This restores cross-machine handoff but adds up to 15-minute latency, and it requires every user on every host to know about and install it.
Fix
Single template change, regenerate all 42 SKILL.md files. The pattern is
s/"~/"$HOME/in both places:Optional but recommended (separate change): drop
2>/dev/nullfrom at least the config probe so future "exec failed" errors surface instead of silently flippingartifacts_sync_modetooff. The current swallow +|| echo offmasks any future regression of this class.A grep-based regression test would also catch recurrence:
Refs
Environment