Skip to content

Tilde-in-quoted-string disables brain-sync silently across 42 SKILL.md files (regression of #785) #1656

@drmusayilmaz

Description

@drmusayilmaz

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:

  1. 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.

  2. Line 436 — preamble push:

    "$_BRAIN_SYNC_BIN" --once 2>/dev/null || true

    Skipped because of (1), but also broken on its own merits.

  3. 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:

  1. 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.
  2. Host A: cat ~/.gstack/.brain-last-push — unchanged.
  3. 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

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