fix(codex): see the background-terminal row under codex 0.162's hint row

A codex session waiting on a background terminal read as plainly idle,
with no "1 background terminal" badge. Codex pins that row above its
composer, and the registry looked for it in the last three non-blank
rows. Codex 0.162.0 added a hint row under the status line at rest
(`  ← for agents · ? for shortcuts`), which pushes the chip to FOURTH from
the bottom exactly when the idle probe reads it. Measured live on the
1.36.0 beta with `sleep 600` started as a background terminal: chip,
composer, status line, hint row; the server reported `watching: null`.
While a prompt is typed the hint goes away and the chip is third again.

The codex entry now declares `watchingLines: 4`. The trade is stated in
the entry: with no terminal running, the fourth row from the bottom is
the last transcript row (the last two while typing), which the agent
writes. As before, this is contained by codex having no hooks: a forged
row costs a wrong badge, never a silenced alert.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-10-09 03:29:37 +02:00
parent a58991e6d8
commit f3b2080e69
4 changed files with 51 additions and 10 deletions
+5 -4
View File
@@ -81,10 +81,11 @@ Two CLIs declare such a row today, and they put it in different places. Claude w
chip on the last row of the screen, so it keeps the default one-row window and anchors on chip on the last row of the screen, so it keeps the default one-row window and anchors on
the `·` its footer joins items with. Codex pins the `·` its footer joins items with. Codex pins
`1 background terminal running · /ps to view · /stop to close` ABOVE its composer, which `1 background terminal running · /ps to view · /stop to close` ABOVE its composer, which
puts the row third from the bottom once the status line and the composer are counted, so its puts the row third from the bottom once the status line and the composer are counted, and
entry declares `watchingLines: 3` and matches that row end to end. Both were measured fourth on codex 0.162+ at rest, where a `← for agents · ? for shortcuts` hint row sits under
against live panes rather than read out of a binary, which is the standard for adding a the status line (it disappears while a prompt is typed). So its entry declares
third. `watchingLines: 4` and matches that row end to end. Both were measured against live panes
rather than read out of a binary, which is the standard for adding a third.
`awaitingLine` covers the quiet pane that is neither idle nor watching: a turn that ENDED `awaitingLine` covers the quiet pane that is neither idle nor watching: a turn that ENDED
to wait for workers the CLI will resume from by itself. When background agents or an to wait for workers the CLI will resume from by itself. When background agents or an
+8 -3
View File
@@ -619,10 +619,15 @@ const CODEX: CliEntry = {
// Measured against a live codex-cli 0.154.0 pane on 2026-09-22: the row appears when // Measured against a live codex-cli 0.154.0 pane on 2026-09-22: the row appears when
// the terminal starts, follows the composer down as the conversation grows, and is // the terminal starts, follows the composer down as the conversation grows, and is
// gone after `/stop`. // gone after `/stop`.
// ⚠️ Codex 0.162.0 (measured 2026-10-09) draws a hint row under the status line at
// rest (` ← for agents · ? for shortcuts`) and drops it while a prompt is typed, so
// the chip is FOURTH from the bottom at rest and third while typing. A three-row
// window never saw it at rest, which is exactly when the idle probe reads it, so a
// session waiting on its terminal read as plainly idle. Four rows cover both.
// ⚠️ This entry CANNOT promise what Claude's does, and the difference is Codex's // ⚠️ This entry CANNOT promise what Claude's does, and the difference is Codex's
// layout rather than its pattern. The third row from the bottom is the chip only // layout rather than its pattern. The fourth row from the bottom is the chip only
// while a terminal runs; with none running it is the last row of the transcript, // while a terminal runs; with none running it is the last row of the transcript,
// which the agent writes. Matching the complete row raises the bar — an assistant // which the agent writes (and while a prompt is typed, the last two). Matching the complete row raises the bar — an assistant
// message has to end with this exact line, to the character — but nothing here makes // message has to end with this exact line, to the character — but nothing here makes
// forging it impossible, so do not read the Claude comment above as applying here. // forging it impossible, so do not read the Claude comment above as applying here.
// What contains it is that codex declares `hooks: 'none'`: no hook event from a codex // What contains it is that codex declares `hooks: 'none'`: no hook event from a codex
@@ -641,7 +646,7 @@ const CODEX: CliEntry = {
promptGlyph: '›', promptGlyph: '›',
workingLine: '[Ee]sc to interrupt', workingLine: '[Ee]sc to interrupt',
watchingLine: String.raw`^\s{0,4}(\d+ background terminals?) running · /ps to view · /stop to close$`, watchingLine: String.raw`^\s{0,4}(\d+ background terminals?) running · /ps to view · /stop to close$`,
watchingLines: 3, watchingLines: 4,
}, },
// The footer under the composer, measured on a live 0.147.0 pane: // The footer under the composer, measured on a live 0.147.0 pane:
// ` gpt-5.6-terra default · ~/codeman-cases/th-scratch` (model, reasoning effort, // ` gpt-5.6-terra default · ~/codeman-cases/th-scratch` (model, reasoning effort,
+2 -2
View File
@@ -365,8 +365,8 @@ export interface CliCapabilities {
/** /**
* How many rows at the FOOT of the screen that row can appear in, counting non-blank * How many rows at the FOOT of the screen that row can appear in, counting non-blank
* rows only. Claude writes its chip on the last row and keeps the default; Codex pins * rows only. Claude writes its chip on the last row and keeps the default; Codex pins
* its own above the composer, which puts it third from the bottom, so it declares * its own above the composer, which puts it third or fourth from the bottom (its hint
* more. Keep each number as small as that CLI's layout allows: every extra row is * row comes and goes), so it declares more. Keep each number as small as that CLI's layout allows: every extra row is
* another row an agent might be able to write, and the label is what silences an * another row an agent might be able to write, and the label is what silences an
* alert. See `watchingLabel()` in `session-activity.ts`. * alert. See `watchingLabel()` in `session-activity.ts`.
*/ */
+36 -1
View File
@@ -45,6 +45,33 @@ const CODEX_WITH_TERMINAL = [
CODEX_STATUS, CODEX_STATUS,
'', '',
].join('\n'); ].join('\n');
/**
* The same with codex-cli 0.162.0 (verbatim, 2026-10-09): a hint row now sits under the
* status line at rest, which pushes the terminal row to FOURTH from the bottom. While a
* prompt is typed the hint goes away and the row is third again.
*/
const CODEX_162_STATUS = ' GPT-6-Luna default · ~/codeman-cases/testcase';
const CODEX_162_WITH_TERMINAL = [
'• OK',
'',
' Worked for 4s • 3:15 AM',
'',
' 1 background terminal running · /ps to view · /stop to close',
'',
'',
'› Ask Codex to do anything',
'',
CODEX_162_STATUS,
' ← for agents · ? for shortcuts',
'',
].join('\n');
const CODEX_162_TYPING = [
' Worked for 4s • 3:15 AM',
' 1 background terminal running · /ps to view · /stop to close',
'› typing probe',
CODEX_162_STATUS,
'',
].join('\n');
const CODEX_STOPPED = [ const CODEX_STOPPED = [
'• OK', '• OK',
'', '',
@@ -392,6 +419,14 @@ describe('the row Codex draws', () => {
expect(CODEX_TAIL).toBeGreaterThanOrEqual(3); expect(CODEX_TAIL).toBeGreaterThanOrEqual(3);
}); });
it("reads the row on codex 0.162's layout, at rest and while a prompt is typed", () => {
expect(watchingLabel(CODEX_162_WITH_TERMINAL, CODEX_WATCHING, CODEX_TAIL)).toBe('1 background terminal');
expect(watchingLabel(CODEX_162_TYPING, CODEX_WATCHING, CODEX_TAIL)).toBe('1 background terminal');
// The hint row is why three rows stopped being enough: at rest, the idle probe's
// moment, a three-row window ends at the composer.
expect(watchingLabel(CODEX_162_WITH_TERMINAL, CODEX_WATCHING, 3)).toBeNull();
});
it('refuses a mention that is not the whole row', () => { it('refuses a mention that is not the whole row', () => {
// The pattern matches Codex's row end to end, so prose about background terminals — // The pattern matches Codex's row end to end, so prose about background terminals —
// including prose quoting part of the row — is not enough. // including prose quoting part of the row — is not enough.
@@ -406,7 +441,7 @@ describe('the row Codex draws', () => {
}); });
it('CAN be forged by Codex own output, and is contained by Codex having no hooks', () => { it('CAN be forged by Codex own output, and is contained by Codex having no hooks', () => {
// Codex's row is third from the bottom only while a terminal runs; with none running // Codex's row is third or fourth from the bottom only while a terminal runs; with none running
// that slot is the last row of the transcript, which the agent writes. Matching the // that slot is the last row of the transcript, which the agent writes. Matching the
// complete row raises the bar but closes nothing, so this test states the limitation // complete row raises the bar but closes nothing, so this test states the limitation
// rather than a protection the code does not have. // rather than a protection the code does not have.