anthropics / anthropics/claude-code

[Bug] macOS: teammate spawn "fork failed: Device not configured" — xnu ptmx slot-vector race triggered by cat placeholder + respawn-pane -k (root cause for #77211)

Ouverte
#93,624 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub
area:agents bug has repro platform:macos
Langage dominant
Python
Étoiles
145k
Forks
23.1k
Métriques de merge des PR
Métriques de PR en attente

Description

Root cause for the recurring macOS "respawn pane failed: fork failed: Device not configured" on tmux teammate spawn (#77211 was closed by the stale bot without a diagnosis; this is the mechanism plus a deterministic repro and a backend fix). It is a kernel PTY-slot-vector race that the tmux teammate backend triggers deterministically at every 16-slot boundary.

**Mechanism (xnu `bsd/kern/tty_ptmx.c`, apple-oss-distributions main)**

Opening `/dev/ptmx` is two steps that are not atomic with respect to other closes:
1. `ptmx_clone()` (~L475–507) picks a minor number. If no free slot exists it returns `pis_total` — one past the end of the current vector.
2. `ptmx_get_ioctl()` (~L322–346) grows the vector by `PTMX_GROW_VECTOR` (16) **only if `pis_free == 0`**, then range-checks the minor (~L349–354) and fails with `ptmx_get_ioctl failed because minor number N was out of range` → ENXIO.

The vector never shrinks (`ptmx_free_ioctl` only bumps `pis_free`). So if a slot is freed between step 1 and step 2, step 1 has already chosen `pis_total` while step 2 sees `pis_free == 1` and skips the grow → out-of-range → ENXIO. The source even has an `XXX We fall off the end here` comment in `ptmx_clone`.

**Why the tmux backend hits it deterministically**

`TmuxBackend` creates a teammate pane as: `split-window -d … -- cat` (placeholder occupies one PTY) → `set-option -p remain-on-exit failed` → `respawn-pane -k -t -- `. `respawn-pane -k` closes the placeholder's master (SIGHUP → `cat` exits → slot freed asynchronously) and immediately `forkpty()`s the replacement. When system-wide PTY usage is exactly `vector_size − 1`, the placeholder fills the last slot, step 1 picks `pis_total`, the placeholder's slot frees mid-open, step 2 declines to grow → ENXIO. Killing the failed pane returns usage to `vector_size − 1`, so **every retry fails identically**. Usage near a 16-multiple boundary is why this appears with plenty of PTY headroom (190/511 in this issue; 117/999 and 113/999 in ours) and why `kern.tty.ptmx_max` bumps don't help.

**Evidence**

- 6/6 spawn failures at 17:58–18:01 (2026-09-11) each had a kernel log line at the same millisecond: `ptmx_get_ioctl failed because minor number 96 was out of range` (`log show --predicate 'process == "kernel" AND eventMessage CONTAINS "ptmx_get_ioctl"'`). No `ptmx_max limit` lines.
- Reproduction (hold PTYs until usage = boundary − 1, then run the exact split/respawn sequence on a private tmux server): 6/6 fail. Same state with plain `forkpty` → close master → immediate `openpty`: 5/5 ENXIO. Close master → wait for child exit → `openpty`: 5/5 success. ±1 PTY: success.
- Also: a second `respawn-pane` on the already-failed pane crashes tmux 3.6a (`server exited unexpectedly`) — so retry-on-failure is not a safe workaround.

Env: Darwin 25.5.0 arm64, tmux 3.6a, Claude Code 2.1.268, `teammateMode: "tmux"`.

**Suggested fix in the backend**

Any of these removes the trigger:
- Spawn the teammate command directly in `split-window` (no `cat` placeholder / no `respawn-pane`), setting border/title after the fact; or
- Before `respawn-pane -k`, kill the placeholder and wait for its pid to disappear (slot fully freed) so the new `forkpty` never races the free; or
- Detect ENXIO from `respawn-pane` and back off by briefly opening+closing one extra `/dev/ptmx` (forces the vector to grow) before retrying on a **fresh** pane.

**Workaround for users**: pre-grow the kernel vector once per boot (open ~512 `/dev/ptmx` fds and close them — 0.05 s); the vector never shrinks, so the boundary becomes unreachable at normal usage. `teammateMode: "in-process"` also avoids it entirely.

Guide de contribution

Aucun guide de contribution indexé pour ce dépôt

Piste de recherche

Start at the TmuxBackend entry point that issues split-window and respawn-pane -k; reproduce the failure on a private tmux server with PTY usage at vector_size minus one. Done means teammate spawning succeeds reliably at that boundary without retrying the failed pane, with regression coverage for the macOS PTY race.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
macos
Domaine
backend, cli, operating-systems
Type d'issue
Bug
Difficulté
5/5
Temps estimé
Plus d'une semaine
Activité
Active
Clarté
Plutôt claire
Accessibilité débutants
25/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.