Display text leaks into persisted respawnFlags: background agents relaunched with --model "opus[1m]" (ANSI bold fragment)

Open 💬 0 comments Opened Jul 14, 2026 by michaelbship

Environment

  • Claude Code 2.1.201 when the corruption was captured; artifacts persist and are reused after updating to 2.1.206
  • npm global install under Homebrew node, macOS arm64 (Darwin 25.5.0)
  • Background agents: daemon (claude daemon run --origin transient) + --bg-pty-host + worker architecture

Bug

Three background-agent jobs ended up with a corrupted model value persisted in ~/.claude/jobs/<id>/state.json:

"respawnFlags": [
  "--agent", "claude",
  "--permission-mode", "auto",
  "--effort", "xhigh",
  "--model", "opus[1m]"
]

The stored value is the literal 8 characters opus[1m] (verified with Python repr() — no ESC byte present). [1m is the ANSI SGR bold prefix; the model name appears to have been captured from rendered display text (the CLI renders model names wrapped in ESC[1m…ESC[22m) with the ESC byte stripped, rather than from the raw model id/alias.

Every supervisor respawn then relaunches the worker with the bogus flag, visible in ps:

claude.exe --resume …/<session>.jsonl --agent claude --permission-mode auto --effort xhigh --model opus[1m]

Impact

  • Workers start and run anyway (some fallback resolution applies), so sessions may silently run a different model than the user selected.
  • The corrupted value is replayed on every respawn. Notably, after updating to 2.1.206, the daemon's post-takeover prewarm respawned one of these jobs and the new worker inherited --model opus[1m] verbatim from state.json — respawn does not sanitize or re-derive the model.

Context / repro hints

  • All three affected jobs were created 2026-07-02 → 07-04 on 2.1.201 via the background-agent dispatch path with --permission-mode auto (two also with --effort xhigh). Other jobs on the same host captured clean values (opus, fable, claude-opus-4-8), so the contamination is path-dependent rather than universal.
  • A fresh dispatch on 2.1.206 (attaching and sending a new message) re-captured a clean claude-opus-4-8 — which is how one of the three self-corrected. The other two required hand-editing state.json.
  • Possibly related to the resume/respawn family: #73754, #74182, #75986 (the crash-loop itself is fixed as of 2.1.202 — this report is only about the corrupted flag capture/persistence).

Expected

respawnFlags should only ever contain raw model ids/aliases. Display strings should never reach persisted argv, and respawn could defensively reject or normalize values containing control-sequence remnants (e.g. [<n>m).

View original on GitHub ↗