- Reclaim renamed the lock by path after judging it stale, so a second setup
that had already reclaimed and re-created it lost its fresh lock and two
installers ran. After the rename the moved directory's pid is re-read: a
new live holder, or a fresh lock whose pid is not written yet, is moved
straight back.
- A pid file whose process is alive but whose lock is older than the install
bound is stale too (the holder is past its own deadline, or the pid was
recycled to an unrelated long-lived process); it was locked forever.
- The /proc fallback stripped the comm field to the FIRST ") ", so a comm
containing ") " hid a child from the kill. proc(5) says the last paren.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- A pid file holding "", "-1" or "0" counted as a live holder (kill -0 -1
signals every process and succeeds), locking Chromium out for good. A pid
must be a positive integer; anything else is stale.
- Two setups judging the same lock stale raced on rm -rf + mkdir and the
loser deleted the winner's fresh lock. The stale dir is renamed first
(atomic), so exactly one reclaims.
- A lock dir with no pid file (killed between mkdir and echo) was never
reclaimed; it now expires once older than the install bound.
- _kill_tree needed pgrep; debian-slim and git-bash ship none, so the bound
killed only the wrapper subshell and the installer kept running. Without
pgrep the children are found by walking /proc/*/stat.
- The timeout knob is normalized in one place with one comment; the trap's
exit 130 is the only exit the block may contain.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The relink gate alone left three destructive sites open:
- link_claude_skill_dirs runs BEFORE relink on every ./setup and used
`ln -snf` (Linux replaces a user's real SKILL.md with a symlink into
gstack) or, on Windows, rm -rf + cp followed by a marker that made the
user's directory "ours" on the next flip. It and _install_alias_skill_md
now consult _claude_entry_is_ours first and skip loudly.
- cleanup_prefixed_claude_symlinks kept a bare name-match deletion and a
`*gstack*` substring match. Symlink arms use anchored `gstack/` segment
patterns; the Windows real-file arm proves provenance (marker,
byte-identity with our source, or the full two-line gen-skill-docs banner
within the first 40 lines, never a one-line substring another generator
could emit). cleanup_old_claude_symlinks uses the same banner rule.
- gstack-relink's fast path judged absolute targets before canonicalizing,
so `/x/gstack/../foreign/SKILL.md` counted as ours; dot-segment targets
now canonicalize first. Its banner rule matches setup's.
The `.gstack-owned` marker records the owning payload's realpath. Entries
skipped by setup or relink are listed in the final setup summary.
Chromium bootstrap refinements from the pre-landing review: an INT/TERM
trap kills the installer's process tree; the Windows npm chain no longer
masks an install failure; GSTACK_SKIP_PLAYWRIGHT=1 is reported as a choice
rather than a failure and sends no telemetry; the timeout knob is
normalized (0, 000, non-numeric, or more than nine digits fall back to the
600s default instead of killing on the first poll or never killing).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
setup runs under `set -e`, and the Chromium bootstrap in section 2 sat ahead
of skill registration in section 4 with a bare `bunx playwright install
chromium`, an unbounded download, and an explicit `exit 1` after the
post-install launch probe. On an offline, proxied, or AppArmor-restricted box
the user ended with ZERO skills registered and a re-run that died at the same
line; a wedged download hung setup indefinitely.
Every browser failure now records a reason code in _PW_FAIL_REASON and setup
continues: skipped (GSTACK_SKIP_PLAYWRIGHT=1, #913), chromium-install,
chromium-install-timeout (the download is bounded by the existing
_wait_with_deadline helper, default 600s, env GSTACK_PLAYWRIGHT_INSTALL_TIMEOUT,
process tree killed via _kill_tree), chromium-install-locked (another setup
holds the lock: this one registers skills and re-probes next time instead of
exiting), windows-no-node, windows-node-modules, post-install-launch (with the
GSTACK_CHROMIUM_NO_SANDBOX=1 hint for Ubuntu 24.04's userns policy, #2157).
The daemon font refresh is skipped when Chromium is unavailable. The final
summary names the skills that need the browser (/qa, /qa-only,
/design-review, /browse, make-pdf, /pair-agent) and the fix for the recorded
reason, and logs the reason code (never a path) through gstack-telemetry-log
when telemetry is on.
Tests: static invariants over the anchor-sliced block (no exit, every reason
code, deadline helper, trap chaining, guarded refresh, summary contents) plus
an integration harness that executes the real block with a stubbed probe and
installer: install failure, hang killed at the deadline with the tree kill
recorded, non-numeric knob fallback, live lock (continues, installer not run,
lock preserved), stale lock reclaimed, post-install probe failure, and the
skip flag.
Credit @DavidMiserak (PR #1900) for the best-effort shape; re-implemented on
the current block.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>