BE-013 HIGH: wire.js child/fd lifecycle on error — push() finally leaks stdin → reap HANGS; curl siblings leak

Three spawn-lifecycle holes in shared/wire.js where a throw on the happy path leaks the child's fds and can deadlock the blocking reap:

  1. push():642 — the finally closes only rfd then io.reap(pid); wfd is closed IN-try at :639, so a throw in drainRecvAdvert/writeAll BEFORE it leaves git-receive-pack blocked reading commands on its still-open stdin. FILEReap is an unconditional blocking waitpid loop (dog/abc/FILE.c:303) → be put --force over ssh HANGS forever, plus fd/zombie leak in the resident loop. Reachable via verbs/put/put.js:310. JS-076 fixed only wire.fetch; JS-100 only pushSession/pushRemote — push() is the untouched sibling.
  2. curlRun():181 — no try/finally: a handler throw or the guard path leaks/zombies the curl child, and reap precedes the fd close.
  3. curlPost():198 — stages the request body OUTSIDE the try: a write failure leaks the fd and the tmp file; the tmp name is predictable.

From the 2026-07-07 be/ review (BE/REVIEW), all three CONFIRMED (push() structural, curl by inspection). Method Issues.

Input

Context

Goals

Constraints

WIP

Design decisions

TODOs

Blockers and bummers

Outcome