Skip to content

provider/codex: detect turn completion via OSC 9 notification & update default model to luna - #11

Open
motonari728 wants to merge 5 commits into
wavever:mainfrom
motonari728:codex/fix-codex-tui-pty-prompt
Open

provider/codex: detect turn completion via OSC 9 notification & update default model to luna#11
motonari728 wants to merge 5 commits into
wavever:mainfrom
motonari728:codex/fix-codex-tui-pty-prompt

Conversation

@motonari728

@motonari728 motonari728 commented Sep 4, 2026

Copy link
Copy Markdown

Summary

Improve automated Codex/Spark TUI pings by avoiding premature stopping and a startup model-migration dialog. Related to #8.

Problem

Codex pings were not reliably reaching the point of completing a request.

Previously, limitping inferred completion from quiet PTY output: once at least four seconds had elapsed since startup and output had been quiet for at least 2.5 seconds, it sent Ctrl-C to stop the TUI. A pause during startup could satisfy this condition before Codex submitted the prompt. In that case, the CLI was stopped before it sent the ping at all.

Even when the prompt had been submitted, quiet output did not establish that the response had finished or the API request had succeeded. The same stopping heuristic could interrupt an in-progress turn and report a successful-looking ping.

During testing with Codex 0.153.2, the default gpt-5.4-mini also displayed a model-migration dialog that required user input and blocked automated pings.

Solution

Commit 1: Wait for TUI turn completion

  • Stop Codex/Spark after its turn-completion notification rather than a period of quiet output.
  • Enable notifications regardless of terminal focus, and provide a usable TERM value when it is unset or dumb.
  • Retain the 45-second safety timeout for stalled sessions.

Why use OSC 9 rather than screen text?

We also tried detecting text rendered in the PTY, but judged that approach unstable and sensitive to changes in Codex's TUI wording and layout. This PR therefore uses OSC 9 notifications as the single completion-detection mechanism.

Unlike matching screen text or a particular response, this relies on Codex CLI's notification feature and the OSC 9 sequence framing. We consider that a more robust boundary: the notification message itself can change without affecting detection.

The trade-off is that the normal completion path now depends on Codex CLI emitting its turn-completion notification. If Codex stops emitting it, notification detection will fail and limitping will stop the TUI through the 45-second fallback instead. We expect a user-facing notification feature to be less likely to disappear than individual TUI text, but that is an expectation, not a compatibility guarantee.

How the completion notification works

Checked against Codex CLI rust-v0.153.2 source:

  1. After finishing a turn, the TUI queues an agent-turn-complete notification when it is returning to user input rather than starting queued input or continuing an active goal. This is a lifecycle event, not a guess based on quiet terminal output. See turn completion.
  2. With the OSC 9 backend, Codex writes ESC ] 9 ; <message> BEL to stdout, which limitping reads through its PTY. The message is a preview of the final response (or a fallback label); limitping recognizes the notification sequence, not specific response text such as PONG. The notification is read directly from the PTY output; no desktop notification service is required. See notification text and OSC 9 output.
  3. Notifications default to unfocused terminals only, while the TUI initializes its focus state to focused. A spawned PTY without focus-loss events therefore needs the always override.

The PR passes these settings to the child Codex process:

Setting Purpose
tui.notifications=["agent-turn-complete"] Select only turn-completion notifications, excluding approval and plan prompts.
tui.notification_method="osc9" Emit a recognizable notification in the PTY output.
tui.notification_condition="always" Emit it regardless of terminal focus.

Codex's event filter and focus gate implement these controls. On receipt, limitping sends Ctrl-C to close the interactive TUI. The 45-second fallback still applies if no notification arrives; receiving a notification does not itself prove quota activation.

Commit 2: Update the default Codex model

  • Change the built-in default from gpt-5.4-mini to gpt-5.6-luna to avoid the observed startup dialog.
  • Leave explicitly configured models unchanged.

Claude behavior is unchanged. This PR does not verify server-side quota activation or require a completion notification for every successful exit. Quota verification is handled separately in #12; #13 connects completion evidence to stricter result reporting.

Checks

  • gofmt -l . prints nothing
  • go build ./...
  • go vet ./...
  • go test ./... (also tested with -race)

Safety

  • I did not include credentials, raw usage responses, or private account metadata
  • I updated README/config examples for user-facing behavior changes
  • I considered whether this change can unexpectedly consume provider quota

CI regression tests verify OSC 9 completion-notification detection through a real PTY on both Linux and macOS, using a fake Codex process. They check that the notification stops the TUI before the fallback timeout and that terminal setup is applied. The test runs with the race detector in the Linux test suite and a dedicated macOS job; both have passed. No Codex credentials or live model requests are required. This covers the PTY notification-handling path, not end-to-end behavior with the real Codex CLI or server-side quota activation. The test is skipped on native Windows.

Codex 0.153.2 emits an OSC 9 notification when a turn completes
(agent-turn-complete). Previously, triggerCodex sent Ctrl-C as early as
4 seconds simply because the initial TUI screen rendering went quiet,
terminating the process before Codex finished session initialization and
submitted the prompt.

By passing:
  -c tui.notifications=["agent-turn-complete"]
  -c tui.notification_method="osc9"
  -c tui.notification_condition="always"

we can deterministically detect turn completion even in a focused PTY
environment.

In addition:
- Fallback TERM to xterm-256color if unset or dumb, preventing TUI
  launch failures under cron or non-interactive daemon environments.
- Maintain the 45s timeout fallback as a safety net.
- Add regression test using mock codex script without external quotas.
- Document the notification flags and updated output in README.
In Codex 0.153.2, selecting gpt-5.4-mini displays an interactive model
migration dialog on startup, preventing automated turns from starting
when relying on the default model without explicit configuration.

Update the default Codex model from gpt-5.4-mini to gpt-5.6-luna in the
built-in config defaults, template TOML, documentation, and tests.
@motonari728

Copy link
Copy Markdown
Author

Native Windows scope

limitping's existing github.com/creack/pty dependency does not support native Windows: pty.Start() reaches StartWithSize(), which returns ErrUnsupported before Codex is launched. See the dependency's Windows implementation.

Accordingly, the end-to-end completion detection in this PR is treated as unsupported and unverified on native Windows. The Unix PTY regression test is skipped there; a successful Windows build is not evidence that TUI pings or completion detection work.

Codex CLI itself has a Windows ANSI-output path for OSC 9, but that alone does not resolve limitping's PTY limitation. Native Windows support would require a supported process/terminal transport and end-to-end testing. This PR does not add that support or introduce the existing limitation.

WSL running Linux binaries uses the Linux PTY path and is separate from native Windows execution.

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.

1 participant