Skip to content

fix(codex): parse the app-server port through ANSI color sequences - #512

Open
chemica-tan wants to merge 1 commit into
fujibee:mainfrom
chemica-tan:pr/ansi-port-parse
Open

fix(codex): parse the app-server port through ANSI color sequences#512
chemica-tan wants to merge 1 commit into
fujibee:mainfrom
chemica-tan:pr/ansi-port-parse

Conversation

@chemica-tan

@chemica-tan chemica-tan commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Mechanism

On our Windows 11 + Git Bash host, npm codex-cli 0.144.1 emitted ANSI SGR sequences in the redirected app-server log, with NO_COLOR set (verified with od on the captured log). codex-monitor.sh discovers the port by matching the literal text listening on: ws://127.0.0.1: in that log; whenever the banner carries SGR sequences between the label and the URL, the sed never matches.

Effect of a non-matching banner on a monitored launch:

  1. the port poll burns its full 10 s timeout,
  2. the monitor kills the fresh app-server it just started,
  3. it prints its explicit codex-monitor: / agmsg: warnings and falls back to a usable plain-codex session, with real-time delivery off for that session.

The fail-open works as designed — but because codex itself opens normally, the warning is easy to miss, and the user just stops receiving messages. With #497 making monitor the recommended default, a host whose codex emits the colorized banner loses delivery on every monitored launch until someone reads the log.

Scope honesty: we can attest the colorized-banner emission only for the npm build we measured (0.144.1, Windows). The parser's failure to match any colorized banner is host-independent, and that is the property the new test pins.

Change

Strip ANSI SGR sequences in the same sed invocation before matching — one expression added, no new dependency, bash 3.2-safe. This banner sed is the only scrape site: the reuse path reads the .port file (bare digits), and codex --version prints plain text.

Test

Adds a fake codex that reproduces the colorized banner verbatim over a node listener. The existing python-listener fakes print a plain banner and skip on Windows, which is why this class of breakage was invisible to the suite; the new fake has no Windows skip and passed in our manual Windows run. Note the current windows-latest CI job runs only the Windows-filtered cases of tests/test_install.bats, so in CI this suite still runs on the Ubuntu/macOS shards. Exact command:

npx bats --filter 'discovers the port when codex colorizes the banner' tests/test_codex_monitor.bats

Restore-to-FAIL verified on this branch: with the monitor sed reverted, the test fails.

Verification

Found live on Windows 11 + Git Bash + npm codex-cli 0.144.1 (2026-07-16); the fix has been running in our downstream deployment since. Mechanism re-checked against current main — the banner sed is unchanged (greppable as listening on: ws://127\.0\.0\.1:). The parser property is verifiable on any host via the test above; the banner emission itself we measured only on the stated Windows host.

Note on #505

The text hunks merge cleanly. However, #505 routes the same startup loop's $! liveness check (server_bg) through a tasklist-only helper, and our Windows probe shows it reports that live MSYS pid dead — we are reporting that on #505 separately. Re-testing the combined head on Windows before merging both is advisable.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EjjWN3rwUxE6qWBicECXHC

…odex 0.144+)

codex 0.144.1 (npm, updated 2026-07-15) colorizes the app-server startup
banner even when stdout is a redirected file (NO_COLOR is ignored). The
monitor's port discovery matched the literal "listening on: ws://127.0.0.1:"
text, which now carries SGR sequences between the label and the URL, so the
sed never matched: every monitored launch burned the 10s poll, killed its
own fresh app-server, and failed open to plain codex — no bridge, no
delivery. Found live in the chemica.4 E2E (step 3); the fail-open itself
worked as designed (loud, non-destructive, verified own-server stop).

Strip ANSI SGR sequences in the same sed invocation before matching. The
reuse path reads the .port file (bare digits) and `codex --version` prints
plain text (od-verified), so this banner sed is the only scrape site.

The new test drives the monitor with a fake codex that reproduces the
colorized banner verbatim over a node listener — it runs on the Windows
runner too, where the python-listener fakes skip (which is exactly why this
break was structurally invisible to the suite). Restore-to-FAIL verified:
the test fails against the pre-fix monitor.

Not a chemica.4 regression: the sed and spawn lines are unchanged since
upstream fujibee#170; the 2026-07-15 morning E2E passed on the pre-0.144 codex.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chemica-tan chemica-tan changed the title fix(codex): parse the app-server port through ANSI color sequences (codex 0.144+) fix(codex): parse the app-server port through ANSI color sequences Jul 28, 2026
@fujibee

fujibee commented Jul 29, 2026

Copy link
Copy Markdown
Owner

@chemica-tan

The mechanism and the fix both check out. The od evidence for SGR sequences in a redirected log with NO_COLOR set is convincing, the two-stage sed works across bash 3.2 and both BSD and GNU sed, and it composes cleanly with the liveness change that has since landed in the same loop on main (#505). Scoping the strip to SGR rather than claiming ANSI in general is the right call for this banner.

One blocking issue, in the new test rather than in the fix.

The node fake ends with setTimeout(() => process.exit(0), 60000), and the listener holds the run's capture pipe for its whole lifetime, so bats waits for it. Measured on a local run of your branch rebased onto current main:

$ bats --filter 'colorizes the banner' tests/test_codex_monitor.bats
1..1
ok 1 codex-monitor: discovers the port when codex colorizes the banner (0.144+)
elapsed=60s

Nothing is left running afterwards — the process does not leak — but the run itself blocks for the full minute. Since the suite is sharded four ways per OS, that minute is paid on whichever shard holds this file, on every run, on every PR.

Could you have the listener exit once the port has been handed off, or have the fixture forward the teardown signal to the node child and wait on it? Either shape keeps the regression coverage and gives the time back. The existing python fake in the same file is a reasonable model — it terminates with its parent rather than on a timer.

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