Fullscreen renderer auto-disables itself on machines running many concurrent sessions (false boot-canary strikes)

Status Open
Reported on v2.1.236
Maintainer reply None cached
Activity 0 comments · opened Aug 31, 2026

What happens

I run a lot of Claude Code sessions concurrently on one machine (10-20 at a time, launched in a burst and torn down together). Since the fullscreen boot canary landed in 2.1.236, fullscreen keeps turning itself off:

Claude Code's fullscreen renderer didn't finish starting last time on this machine, so this launch is using the classic renderer. It will try fullscreen again next launch; /tui default keeps the classic renderer.

and then, shortly after:

Claude Code's fullscreen renderer has repeatedly failed to start on this machine, so it has been turned off here. Run /tui fullscreen to try it again (this also resets after an update).

I have "tui": "fullscreen" in ~/.claude/settings.json. Fullscreen itself works fine, it renders perfectly every time. Nothing is actually failing to start. But I have to run /tui fullscreen again every few minutes, which is maddening.

Why I don't think these are real failures

fullscreenAutoDisabled in ~/.claude.json came back as:

{"version": "2.1.251", "at": 1788146545484, "strikes": 4}

The sticky threshold is 2, so a genuine "failed twice in a row" should trip at exactly 2. Landing on 4 means several strikes were banked from a single scan, which looks like a burst of orphaned fullscreenBootPending entries rather than four separate failed launches.

That map is keyed by PID and lives in the single shared ~/.claude.json. Every concurrent session adds its entry at startup and removes it ~10s later. When many sessions start and settle at nearly the same instant, they're all doing read-modify-write against the same file, and any lost update leaves an entry behind for a PID that is by then gone. On the next launch that reads as a crashed boot.

Two things make it hard to recover from:

  • Strikes are only cleared on a healthy settle. A signal-driven exit settles as withdrawn, which removes that PID's own entry but deliberately preserves the strike counter. On a machine where sessions are usually terminated rather than quit interactively, the counter effectively only ratchets up.
  • PID reuse is a false positive by construction: an entry whose PID matches the launching process's own PID is counted as a strike immediately, without checking whether it's actually the same process. With this much process churn, PID recycling is routine.

Repro shape

  1. Set "tui": "fullscreen" in ~/.claude/settings.json.
  2. Launch ~15 sessions concurrently from a supervising process, each in its own pty.
  3. Let them run past the 10s healthy mark, then terminate the whole group.
  4. Relaunch. Watch fullscreenBootStrikes / fullscreenAutoDisabled in ~/.claude.json.

What I tried

I assumed the supervisor was SIGKILLing sessions before cleanup finished, so I widened the SIGTERM-to-SIGKILL grace period. It didn't help, and if anything it got worse, which is consistent with contention rather than truncation: a longer grace means more processes alive and writing to the same file at the same moment.

Workaround

CLAUDE_CODE_NO_FLICKER=1 in the environment. That makes the entry path env_on, which is excluded from the set that arms the canary, so nothing is written and nothing can be orphaned. Fullscreen then just stays on.

Suggestions

  • Make the pending-map update atomic across processes, or tolerate lost updates by not treating an unattributable leftover entry as a strike.
  • Don't count pid === ownPid as a strike without confirming it's the same process (compare startedAt against the current process start time).
  • Cap strikes to at most one per launch, so one burst of orphans can't bank several at once.
  • Clear the strike counter on a withdrawn settle too, or at least on any settle where the renderer demonstrably reached its first frame.

Environment

  • Claude Code 2.1.251 (native install)
  • macOS 15.x (Darwin 25.6.0), arm64

View original on GitHub ↗