Adversarial review (Claude + Codex, both high confidence) identified 6
critical production-harm findings in the /ship pre-landing pass.
All folded in.
Migration v1.0.1.0.sh hardening:
- Add explicit `[ -z "${HOME:-}" ]` guard. HOME="" survives set -u and
expands paths to /.claude/skills/... which could hit absolute paths
under root/containers/sudo-without-H.
- Add python3 fallback inside resolve_real() (was missing; broken
symlinks silently defeated ownership check).
- Ownership-guard Shape 2 (~/.claude/skills/gstack/checkpoint/). Was
unconditional rm -rf. Now: if symlink, check target resolves inside
gstack; if regular dir, check realpath resolves inside gstack. A
user's hand-edited customization or a symlink pointing outside gstack
is preserved with a notice.
- Use `rm --` and `rm -r --` consistently to resist hostile basenames.
- Use `find -type f -not -name .DS_Store -not -name ._*` instead of
`ls -A | grep`. macOS sidecars no longer mask a legit prefix-mode
install. Strip sidecars explicitly before removing the dir.
context-save/SKILL.md.tmpl:
- Sanitize title in bash, not LLM prose. Allowlist [a-z0-9.-], cap 60
chars, default to "untitled". Closes a prompt-injection surface where
`/context-save $(rm -rf ~)` could propagate into subsequent commands.
- Collision-safe filename. If ${TIMESTAMP}-${SLUG}.md already exists
(same-second double-save with same title), append a 4-char random
suffix. The skill contract says "saved files are append-only" — this
enforces it. Silent overwrite was a data-loss bug.
context-restore/SKILL.md.tmpl:
- Cap `find ... | sort -r` at 20 entries via `| head -20`. A user with
10k+ saved files no longer blows the context window just to pick one.
/context-save list still handles the full-history listing path.
test/skill-e2e-autoplan-dual-voice.test.ts:
- Filter transcript to tool_use / tool_result / assistant entries
before matching, so prompt-text mentions of "plan-ceo-review" don't
force the reachedPhase1 assertion to pass. Phase-1 assertion now
requires completion markers ("Phase 1 complete", "Phase 2 started"),
not mere name occurrence.
- claudeVoiceFired now requires JSON evidence of an Agent tool_use
(name:"Agent" or subagent_type field), not the literal string
"Agent(" which could appear anywhere.
- codexVoiceFired now requires a Bash tool_use with a `codex exec/review`
command string, not prompt-text mentions.
All SKILL.md files regenerated. Golden fixtures updated. bun test: 0
failures across 80+ targeted tests and the full suite.
Review source: /ship Step 11 adversarial pass (claude subagent + codex
exec). Same findings independently surfaced by both reviewers — this is
cross-model high confidence.
Main shipped the v1 prompts rewrite (simpler writing style + real LOC
receipts + /plan-tune observational substrate). Resolved conflicts:
- VERSION / package.json: bumped 0.18.5.0 → 1.0.1.0 (main is 1.0.0.0,
this branch lands next).
- CHANGELOG: moved the /context-save + /context-restore entry to the
top as v1.0.1.0, above main's v1.0.0.0. Also removed the em-dash
variants in the new entry (ship voice rule).
- TODOS: kept both sections — Context skills (lane feature TODO) first,
main's PACING_UPDATES_V0 + Plan Tune v2 deferrals below.
- Migration: renamed gstack-upgrade/migrations/v0.18.5.0.sh →
v1.0.1.0.sh (matches new version). Test path updated.
preamble.ts auto-merged cleanly: main's question-tuning, explain_level,
and writing-style sections composed with my context-save/context-restore
routing rule.
All SKILL.md files regenerated via `bun run gen:skill-docs --host all`
per CLAUDE.md's "never resolve generated files by accepting either
side" rule. Golden fixtures (claude/codex/factory ship) also regenerated.
bun test: 0 failures.
scripts/resolvers/preamble.ts:238 is the source of truth for the routing
rules that gstack writes into users' CLAUDE.md on first skill run, AND
gets baked into every generated SKILL.md. A single 'invoke checkpoint'
line points at a skill that no longer exists.
Replace with two lines:
- Save progress, save state, save my work → invoke context-save
- Resume, where was I, pick up where I left off → invoke context-restore
Tier comment at :750 also updated.
All SKILL.md files regenerated via bun run gen:skill-docs.
Claude Code ships /checkpoint as a native alias for /rewind (Esc+Esc),
which was shadowing the gstack skill. Training-data bleed meant agents
saw /checkpoint and sometimes described it as a built-in instead of
invoking the Skill tool, so nothing got saved.
Fix: rename the skill and split save from restore so each skill has one
job. Restore now loads the most recent saved context across ALL branches
by default (the previous flow was ambiguous between mode="restore" and
mode="list" and agents applied list-flow filtering to restore).
New commands:
- /context-save → save current state
- /context-save list → list saved contexts (current branch default)
- /context-restore → load newest saved context across all branches
- /context-restore X → load specific saved context by title fragment
Storage directory unchanged at ~/.gstack/projects/$SLUG/checkpoints/ so
existing saved files remain loadable.
Canonical ordering is now the filename YYYYMMDD-HHMMSS prefix, not
filesystem mtime — filenames are stable across copies/rsync, mtime is
not.
Empty-set handling in both restore and list flows uses find+sort instead
of ls -1t, which on macOS falls back to listing cwd when the input is
empty.
Sources for the collision:
- https://code.claude.com/docs/en/checkpointing
- https://claudelog.com/mechanics/rewind/