Skip to content

fix(codex): locate a composer whose continuation row is indented deeper than the gutter - #140

Merged
AltanS merged 1 commit into
AltanS:mainfrom
sdertli:fix/codex-composer-continuation-indent
Aug 30, 2026
Merged

fix(codex): locate a composer whose continuation row is indented deeper than the gutter#140
AltanS merged 1 commit into
AltanS:mainfrom
sdertli:fix/codex-composer-continuation-indent

Conversation

@sdertli

@sdertli sdertli commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

The symptom

Every reply sent from the phone to a Codex pane was refused with

The agent's input box isn't on screen — a menu or dialog is probably up. Nothing was typed.

with no dialog anywhere on the screen. Claude, Grok and agy panes on the same host were fine.

It is a deadlock, not a transient: the refusal leaves the text in the composer, and the
stranded draft is what makes the next send fail too. The pane never recovers on its own — the
operator has to notice, clear the box by hand, and retype.

The cause

codex/chrome.ts recognised a wrapped-draft continuation row with CONTINUATION = /^ {2}\S/
"exactly two spaces, then a non-space". But the two spaces are the composer's gutter; what
follows them is the operator's own text, and that text may itself begin with spaces. One
shift+enter — a single tap on a phone keyboard — with the next line starting indented is enough:

› please move all the images across to the new blog
    then take the originals down once the copy is verified     ← four spaces
  gpt-5.6-sol medium · /tmp/collie-codex-sandbox

locateComposer walks up from the status row, hits that row, matches neither promptText nor
CONTINUATION, and returns null. composerReady is then false on every frame for as long as
the draft sits there, and preflight refuses the send.

The fix

CONTINUATION = /^ {2}\s*\S/ — assert the gutter, not the shape of what follows it.

Nothing else is loosened, and the discrimination this row's regex looked like it was doing is
still done, by the two checks that were always the load-bearing ones:

  • the walk starts only from a row isStatusRow accepts at the buffer tail (dialogs replace the
    prompt/status pair, so their footer is the tail and the walk never starts);
  • the run is bounded above by the blank row, tested first in the same expression — Codex
    separates every section of a screen with exactly one blank row, so a contiguous non-blank run
    directly under a validated status row is the draft by construction.

and rows still start at column 0, so neither can pass as a continuation.

Evidence

web/src/fixtures/panes/codex--v0151-draft-indented-line.txt — a byte-faithful format:ansi
capture of the screen above from a throwaway herdr tab (Codex 0.151.0, herdr 0.8.2, Linux),
one length-preserving sanitization pass on the shell prompt line. It returns composerReady: false on main and true with this patch; both were run before and after the change.

Tests

cd web && bun run test → 118 files, 3841 passed / 30 todo.
bun run test (root) → 717 passed + collie-ctl.test.sh passed.
bun run typecheck on both sides → clean. scripts/check-version.sh → ✓ (0.36.0, untouched).

Per CLAUDE.md the version files and CHANGELOG are left alone — this is a fork PR. Suggested
CHANGELOG line, if you want one in my words:

- Codex: a draft whose continuation row is indented deeper than the gutter no longer reads as a dialog, which had refused every send in that pane until the box was cleared by hand

…er than the gutter

The two spaces `CONTINUATION` matched are the composer's gutter; what follows them is the
operator's own text, which may itself begin with spaces. A draft carrying a hard line break whose
next line starts indented — one shift+enter on a phone keyboard — paints a four-space row that
`/^ {2}\S/` refused, so `locateComposer` returned null and `composerReady` was false on every
frame. That refused EVERY send in the pane with "the agent's input box isn't on screen" for as
long as the draft sat there: a deadlock, because the refusal is itself what kept the draft from
being sent, so the pane could not recover on its own.

Assert the gutter, not the shape of what follows it. The discrimination that regex looked like it
was doing is still done by the two checks that were always load-bearing: the walk only starts from
a row `isStatusRow` accepts at the buffer tail, and the run is bounded above by the blank row
tested first in the same expression. `› ` and `• ` rows still start at column 0.

Pinned by a byte-faithful capture of the screen (Codex 0.151.0), which reads composerReady false
before the change and true after it.
@AltanS

AltanS commented Aug 30, 2026

Copy link
Copy Markdown
Owner

Thank you — the root cause here is a good catch: treating the gutter's two spaces as the boundary when the operator's own text can start with spaces. The fixture you added is what makes it stick. Verified against main with #141, #142 and #121: no conflicts, full suite green.

@AltanS
AltanS merged commit 71fc207 into AltanS:main Aug 30, 2026
AltanS added a commit that referenced this pull request Aug 30, 2026
Brings #140, #141, #142 and #121 across. Two of them needed a hand on the way
in, because git merged the text and not the meaning:

  · codex/markers.ts — #141 was written against main's `CONTROL_CHARS` regex.
    v1 replaced that constant with `hasControlChar()`, which scans by code unit
    the way links.ts does. The merge took #141's new `isDimSuffixFieldSegment`
    verbatim, so it called a name v1 had deleted. Swapped to the function; the
    two are byte-equivalent, tab exempted on both sides.

  · omp/markers.test.ts — #142's new cases bind `const row`, and this file has
    a module-level `row(name, i)` helper. main has no lint gate so it passed
    there; v1's oxlint calls it no-shadow, and it is right to. Renamed to
    `borderRow`, which is what v1's own version of the conflicted case already
    used.

The one textual conflict was the same assertion under two names. Took main's
"painted in one foreground end to end" — after #142 the rule reads a colour
CHANGE, so v1's older "no unstyled text to anchor against" no longer describes
what is being tested.
AltanS added a commit that referenced this pull request Aug 30, 2026
Cuts the merge of main's four fixes (05561ae) — #140, #141, #142, #121.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants