Skip to content

cookbook/two-agent-chat: sends to Codex are refused because its composer rows are drawn over an animated particle field #590

Description

@LameBaker

What happened: Every peer-chat.py --to codex send is refused before anything is typed — target composer prompt is not recognisable (shell mode, disabled input, a trailing modal or status row, or an unknown prompt glyph); nothing was typed, five times at 10 s, then exit 1 — while the Codex pane is idle with an empty composer. Codex 0.154.0 draws an ambient animated particle field across its composer box: scattered Braille dots on the prompt row itself and on the rows immediately above and below it, drifting continuously. Sends to Claude Code are unaffected.

This is the same class as #540, #553 and #564, but from the Codex side rather than the Claude side — the first one where the agent decorates the rows around the prompt rather than the prompt's content.

Expected vs actual: Expected: an idle Codex pane whose composer holds only the Ask Codex to do anything placeholder is recognised, and the send goes through. Actual: refused pre-write, the message is never typed.

Where it breaks: trailing_input_block (peer-chat.py:462) pops trailing blank lines, pops one footer row, then walks up while lines are non-blank. The particle rows are no longer blank, so the block starts one row too high and CODEX_PROMPT_RE.match(block[0]) in codex_live_prompt_text (:476) sees a row of dots instead of . live_prompt_text returns None and send (:933) raises PromptBlocked.

Pane tail at the moment of refusal, blank rows marked:

4: [BLANK]
5: [⠁   ⠈         ⠄                 ⠁       ⠁        ⠐ ⠐    ]   <- particles
6: [› Ask Codex to do anything⡀          ⠂    ⠁]                 <- prompt
7: [      ⠠⢀            ⠠     ⠄                    ⢀     ⡀    ]  <- particles
8: [  gpt-6-astra medium · ~/Code/overgear-vpn · main · ...]      <- footer

Three separate breakages, and the first one is the least important. Restoring only the block boundary would move the failure later, not remove it:

  1. Block boundary, above.
  2. composer_is_empty (:521) compares against the exact string Ask Codex to do anything. Particles land on the prompt row too (row 6 above), so the content check fails even once the block starts in the right place.
  3. wait_for_composer_change (:543) requires the composer state to hold still for settle_delay within PROBE_TIMEOUT 3.0 s. The animation redraws about every 150 ms (measured below), so the 0.5 s window this function is given before submit can never be met.

(3) is on the body-write path, not the preflight, and it is the reason a preflight-only relaxation would make things worse rather than better. type_body (:611) uses CHUNK_SETTLE_DELAY 0.1 s after every chunk but COMPOSER_SETTLE_DELAY 0.5 s after the last one. A ~150 ms redraw period satisfies 0.1 s, so every intermediate chunk would verify normally and the send would run to the final step — then fail at message marker after chunk N/N was not confirmably removed; submit withheld, with the whole message typed into Codex's composer and only the cleanup backspaces standing between that and a stranded draft. That is the #502 failure mode, reached at the last possible moment. Whatever shape the fix takes, it has to cover the verification path, not just the gate.

Steps to reproduce:

  1. Codex 0.154.0 in the right pane, Claude Code in the left.
  2. Let Codex finish a turn and leave the pane idle. The particle animation runs on and around the composer row (it is not a splash-screen effect; it is still there after a completed turn).
  3. From the Claude pane, peer-chat.py --to codex --stdin <<'CHAT' … CHAT.
  4. Refused with target composer prompt is not recognisable …; nothing was typed, five attempts, exit 1.

On a possible discriminator: the particle glyphs are a clean signal. Sampling the composer box repeatedly (12 reads of the three rows above the footer) yields exactly one non-ASCII glyph that is not a particle — U+203A, the prompt itself — and otherwise only U+2801, U+2802, U+2804, U+2808, U+2810, U+2820, U+2840 and U+2880: all eight single-dot Braille patterns, nothing else from the block, and nothing outside it. Dropping that block from the pane text before composer state is derived would restore all three checks at once. I am not proposing that relaxation: it would make any message body containing Braille invisible to the same checks, and that trade is yours.

The animation does not stop for a draft, and its period is measured. With ыв typed into the composer by hand and left there, particles keep drifting across the row, including into the prompt-glyph column itself:

'›⠁ыв         ⠠ ⠁        ⡀          ⠈ ⠂  ⠁'
'›⠁ыв         ⠠ ⠁                   ⠈ ⠂  ⠁'
'›⠁ыв         ⠠                          ⠁'

Polling session text as fast as it returns — 526 reads in 6 s, ~11 ms each — the three composer rows changed 39 times, with a gap between redraws of min 141 ms, median 151 ms, max 163 ms. So the state is never still for 0.5 s, and breakage (3) is real rather than hypothetical.

No user-side workaround. #564 had "promptSuggestionEnabled": false on the Claude side. Codex has no equivalent that I can find: codex features list shows no flag matching ambient|anim|particle|splash|idle|composer — the only near hit is prevent_idle_sleep, which is unrelated.

Environment: agterm 0.28.0 (6305191), macOS 26.6.2 (25G83), Codex CLI 0.154.0, Claude Code 2.1.268, Python 3.14.3, peer-chat.py byte-identical to cookbook/two-agent-chat/peer-chat.py at 9dd5294.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions