Skip to content

fix(codex): stabilize app-server usage fetches - #12

Merged
bernardopg merged 2 commits into
bernardopg:mainfrom
gouwazi:fix/codex-app-server-handshake
Aug 6, 2026
Merged

fix(codex): stabilize app-server usage fetches#12
bernardopg merged 2 commits into
bernardopg:mainfrom
gouwazi:fix/codex-app-server-handshake

Conversation

@gouwazi

@gouwazi gouwazi commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • complete the documented Codex app-server initialize / initialized handshake
  • wait for both requested responses with a bounded deadline instead of closing stdin after a fixed four-second delay
  • retry an authenticated account's transient rate-limit transport failure once
  • preserve the underlying JSON-RPC error instead of replacing it with a misleading CLI-update message
  • reuse a successful snapshot for up to 15 minutes when both live attempts fail, while retaining its original updatedAt so the existing stale-data indicator remains accurate

Problem

The Codex widget intermittently alternated between valid usage and ERR even though the account remained authenticated. Live diagnostics identified three contributing behaviors:

  1. The adapter omitted the initialized notification required by the official app-server initialization sequence.
  2. It closed app-server stdin after four seconds; valid live rate-limit responses were observed arriving after that cutoff.
  3. The ChatGPT usage endpoint occasionally returned an explicit transient JSON-RPC error:
code=-32603
failed to fetch codex rate limits: error sending request for url
(https://chatgpt.com/backend-api/wham/usage)

The previous parser discarded that distinction and always suggested updating Codex CLI. A single retry reduced the frequency but still exposed ERR when two transient calls failed consecutively.

Behavior

The helper now keeps app-server stdin open while reading JSONL responses and exits as soon as both account and rate-limit responses arrive, with an eight-second bound per attempt. It retries only when an authenticated account lacks valid rate limits.

After a successful live response, the normalized provider payload is cached for 15 minutes. If both attempts later fail transiently, the cached payload is returned with source codex-app-server-cache and its original usage.updatedAt; the existing UI therefore marks it stale naturally. Missing or invalid authentication never falls back to cache.

Validation

  • shellcheck providers/get-*
  • actionlint -color
  • existing standard and weekly-only Codex fixtures
  • handshake ordering validation
  • JSON-RPC transport failure followed by a successful retry, asserting exactly two attempts
  • two transport failures followed by cache fallback
  • successful response delayed for five seconds, beyond the former cutoff
  • ten consecutive live reads with Codex CLI 0.146.1; all returned usage, including one recovery from a transient first-attempt failure
  • full fork PR CI: all checks passed

Closes #11

gouwazi and others added 2 commits August 6, 2026 22:17
The coproc body ran in an extra subshell, so $CODEX_APP_SERVER_PID pointed at
that subshell rather than at codex itself and the kill never reached the real
process. Under the previous timeout --kill-after implementation this did not
matter; now an app-server that outlives the read loop is leaked once per poll.

Exec the app-server so the recorded PID is the process we signal, and fall back
to SIGKILL when it ignores SIGTERM.
@bernardopg

Copy link
Copy Markdown
Owner

Thanks for tracking this down — the handshake fix is right and I confirmed the cache behaviour matches the docs (fresh cache returns codex-app-server-cache with the original updatedAt, expired cache surfaces the underlying JSON-RPC message, and an unauthenticated account never touches the cache).

I pushed one fix on top: the coproc body ran in an extra subshell, so $CODEX_APP_SERVER_PID pointed at that subshell rather than at codex, and the kill never reached the real process. Reproduced with a stub that ignores SIGTERM — the app-server survived the helper and leaked one process per poll, which the previous timeout --kill-after=2s had prevented. Exec'ing the app-server makes the recorded PID the one we signal, plus a SIGKILL fallback.

Two minor things I left alone, happy to take them here or in a follow-up:

  • tmp_error captures the app-server stderr but nothing ever reads the file — the surfaced message comes from limit_error.
  • The cache write (jq > "$cache_file") isn't atomic. Concurrent runs can truncate it; the jq -e guard on read means the only consequence is losing the cache, so it's low priority.

@bernardopg
bernardopg merged commit 2072ffd into bernardopg:main Aug 6, 2026
0 of 6 checks passed
bernardopg added a commit that referenced this pull request Aug 6, 2026
Finalize the 1.9.0 changelog (pi provider + Codex reliability), credit
contributors, and bump the version in plugin.json and the settings hero
pill. get-codex-usage reads plugin.json dynamically.

Contributions in this release:
- pi coding-agent local analytics provider — Geert Theys (#10)
- Codex app-server handshake stabilization — Xianggang Wang (#12, fixes #11)
@bernardopg

Copy link
Copy Markdown
Owner

Thank you, @gouwazi! 🎉

This has been merged and shipped in v1.9.0, and it closes #11. Excellent fix — completing the documented initialize / initialized handshake, waiting on both responses with a bounded deadline instead of the fixed four-second stdin close, retrying the transient rate-limit transport failure once, and preserving the underlying JSON-RPC error instead of the misleading CLI-update message all directly address the intermittent "missing rate limits" reports. The CI coverage you added for the retry / cache / delayed paths was especially appreciated.

One follow-up I layered on top while landing the release: the adapter now also reaps the codex app-server child on every exit path (abort/timeout), so a stuck server no longer leaks one process per poll — complementing your exec-based coproc handling.

Your original commits are preserved with full authorship. Thanks a lot for the thorough investigation and the clean, well-tested fix! 🙏

@gouwazi
gouwazi deleted the fix/codex-app-server-handshake branch August 7, 2026 02:48
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.

Codex provider intermittently reports missing rate limits due to incomplete app-server handshake

2 participants