Windows on ARM64 (Snapdragon): ./setup intermittently deadlocks in fork-heavy bash loops (gstack-patch-names / link_codex_skill_dirs) under x64-emulated Git-Bash
- Dominant language
- TypeScript
- Stars
- 133k
- Forks
- 19.9k
- Avg merge
- 18h 46m
- Merged PRs (30d)
- 26
Description
## Summary
On Windows 11 on ARM64 (Snapdragon X Elite), `./setup` intermittently **deadlocks** partway through and hangs indefinitely. I traced it to fork-heavy bash loops (`bin/gstack-patch-names`, and — after I patched that one — `link_codex_skill_dirs` in `setup`) wedging in MSYS2's `fork()` emulation, which here is executed by an **x86-64 Git-Bash running under Windows-on-ARM x64 emulation**.
## Environment
- Windows 11 ARM64, Qualcomm Snapdragon X Elite
- gstack **v1.58.5.0**, global git install (`~/.claude/skills/gstack`), host = claude (+ codex detected)
- Git for Windows is **hybrid**: native ARM64 git (`file $(which git)` → `ARM64`) but **x86-64 bash** (`file $(which bash)` → `PE32+ … x86-64`) → the shell runs under Prism x64 emulation
- bun (x64), node (x64 via fnm)
## Symptom
`./setup` prints through skill-doc generation, then hangs with **no further output and no `bun`/`node` process alive**. `bash -x ./setup` shows the last executed command is the `bin/gstack-patch-names 0` invocation. Tracing `gstack-patch-names` itself shows it wedged mid-loop on a per-file `grep -m1 '^name:' … | sed … | tr …` pipeline. The exact file **varies between runs** (non-deterministic), and that pipeline runs fine **in isolation** — it only deadlocks inside the bulk loop. It's intermittent: the daily auto-upgrade has completed cleanly on quiet days and hung during active use.
## Root cause
`gstack-patch-names` spawns `grep | sed | tr` (3 procs) + `basename` per skill — roughly **4 forks × ~55 skills ≈ 220** rapid `fork()`/`CreateProcess` calls. MSYS2's `fork()` is fragile by nature; under x64-on-ARM64 emulation (and amplified by AV scanning each spawned child) it intermittently deadlocks. Rewriting `gstack-patch-names` to be **fork-free** (read the frontmatter `name:` with a bash builtin `read` loop — ~2s, zero forks) fixed that step, but `./setup` then hung at the **next** fork-heavy loop, `link_codex_skill_dirs`. So it's systemic to the fork-per-file pattern, not a single script.
## Minimal repro (on an affected machine)
```bash
# Intermittently deadlocks (often within a few runs) on x64-emulated MSYS2 bash:
time bash ~/.claude/skills/gstack/bin/gstack-patch-names ~/.claude/skills/gstack 0
```
(Note it's a no-op for already-unprefixed installs — `PATCHED=0` — yet still hangs, because every iteration forks `grep|sed|tr` just to *read* the name.)
## Suggested fix
Replace the per-file process pipelines in the setup loops with a single-pass / fork-free approach:
- **`gstack-patch-names`**: read the frontmatter `name:` with a bash `read` loop (no fork) and only spawn `sed`/`mv` when an actual rename is required (usually none). Or do the whole job in one `awk`/`bun` pass over all `SKILL.md` files.
- Apply the same to `link_codex_skill_dirs` / `link_claude_skill_dirs` (avoid `basename` + `grep`/`sed` per iteration).
This removes the fork storm that triggers the deadlock and also speeds setup up on every platform. I have a verified fork-free `gstack-patch-names` drop-in and am happy to open a PR if useful.
Contributor guide
Research direction
Start with ./setup, bin/gstack-patch-names, and the link_codex_skill_dirs and link_claude_skill_dirs loops; reproduce the hang with the provided time bash command on affected Git-Bash. Trace the per-file process pipelines and compare the suggested fork-free approach. Done means setup completes reliably without fork-heavy per-file loops while preserving name patching and skill linking.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bash
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100