Skip to content

[Bug]: Codex provider drains plan credits while T3 Code is idle in background (Codex equivalent of #2191) #2720

@guilhermelippert

Description

@guilhermelippert

Before submitting

Area

apps/server

Summary

T3 Code (Alpha) drains the user's Codex Pro plan credits while idle in the background, even with the UI closed. In my case it burned 1000 purchased credits between ~01:00 BR and ~12:00 BR on 2026-05-15 while the app was minimized and the laptop was unattended. Activity pauses when the macOS lid is closed (system sleep) and resumes on wake — consistent with an in-app loop, not a server-side schedule.

The pattern matches the now-closed Claude bug #2191 ("Nightly: Claude Code burns tokens every 5 minutes while t3code is running idle"), but for the Codex provider. The fix Julius shipped for Claude in probeClaudeCapabilities() does not appear to extend to probeCodexAppServerProvider() in CodexProvider.ts.

Steps to reproduce

  1. Have Codex Pro authenticated locally (codex login, populates ~/.codex/auth.json).
  2. Launch T3 Code (Alpha), use it briefly, then minimize / close the window but leave the app running in the dock.
  3. Watch ~/.codex/logs_2.sqlite:
    SELECT datetime(ts,'unixepoch','-3 hours') AS brt, COUNT(*) AS events
    FROM logs
    WHERE feedback_log_body LIKE '%t3code_desktop%'
    GROUP BY strftime('%Y-%m-%d %H', datetime(ts,'unixepoch','-3 hours'))
    ORDER BY brt;
  4. Observe periodic model/list, account/rateLimits/read, and (less frequently but most expensive) responses_websocket activity with no corresponding user input. Killing every T3 process makes the t3code_desktop client_name disappear from the logs entirely.

Expected behavior

When the user has not issued a turn, T3 Code should not initiate Codex API calls — especially anything that hits the responses_websocket / sse::responses streaming endpoints, which incur per-token plan consumption.

Actual behavior

While idle, T3 Code keeps a Codex app-server process alive identifying itself as clientInfo.name = "t3code_desktop" (set in apps/server/src/provider/Layers/CodexProvider.ts lines 241 and 280), and periodically refreshes its provider snapshot via makeManagedServerProvider.ts lines 141–146 — an unconditional Effect.forever(Effect.sleep(refreshInterval)) loop. CodexDriver.ts sets SNAPSHOT_REFRESH_INTERVAL = Duration.minutes(5).

The refresh itself calls probeCodexAppServerProvider which sends initialize, account/read, model/list, and skills/list. Those are not the costly ones on their own — but they keep the websocket warm and, in my observed window, also produced bursts of codex_api::endpoint::responses_websocket activity (e.g. 279 events in a single minute at 08:28 BR) while I was asleep. I don't have a smoking-gun trace of which T3 code path triggers those — that's where I need the maintainers to look. The strongest candidates:

  • A stuck activeTurnId keeping ProviderSessionReaper from reaping the orphan session (ProviderSessionReaper.ts:64-71 short-circuits when activeTurnId != null). This is what PR reconcile provider session state and settle stuck turns #2666 already addresses.
  • The Codex websocket warmup running on every refresh / reconnect.

Why the Claude fix doesn't cover this

Issue #2191 was closed because probeClaudeCapabilities() was changed to stop hitting the Anthropic API. The Codex equivalent (probeCodexAppServerProvider in apps/server/src/provider/Layers/CodexProvider.ts) was not given the same treatment — it still spawns a Codex app-server every refresh, which in turn handshakes against chatgpt.com. And the broader gating PR #2679 ("Add background activity policy and host power monitoring") is still OPEN, CONFLICTING, not merged — so even released builds remain affected.

Evidence

1. Burn timeline (Brazil time, UTC-3) — from ~/.codex/logs_2.sqlite

The last user-initiated Codex rollout file in ~/.codex/sessions/ is 2026-05-14T22:32 BR (<turn_aborted> as final event). Everything after this is non-user-initiated.

Hora BR Events Notes
14/05 22:50 12,087 tail of user session
15/05 01:41 92 first t3code_desktop calls to chatgpt.com (model/list)
15/05 03:32 t3code_desktop calls account/rateLimits/read
15/05 03:48 699 t3code_desktop heavy refresh
15/05 08:28 279 events on codex_api::endpoint::responses_websocket + 19 on codex_api::sse::responses (asleep, no input)
15/05 10:57 1,301 sustained

2. t3code_desktop is the only non-Codex-official client_name seen

Distinct client_name values observed making outbound calls during the burn window:

  • t3code_desktop — the only third-party identifier
  • codex_desktop / codex_cli — official OpenAI Codex clients (user-driven during earlier daytime hours)

3. Burn stops the moment T3 is killed

After pkill -f "T3 Code" and trashing the app:

  • 0 t3code_desktop log entries in the next 5 minutes.
  • Subsequent Codex API attempts (from official Codex clients reconnecting) immediately get error.message="You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at May 18th, 2026 7:50 PM." — confirming the plan quota was the constrained resource.

4. Process tree right before the kill

PID    ELAPSED    COMMAND
36707  05:34:40   /Applications/T3 Code (Alpha).app/Contents/MacOS/T3 Code (Alpha)
36717  05:34:38   /Applications/T3 Code (Alpha).app/Contents/Frameworks/.../T3 Code (Alpha) Helper --type=gpu-process --user-data-dir=.../t3code
36718  05:34:38   /Applications/T3 Code (Alpha).app/Contents/Frameworks/.../T3 Code (Alpha) Helper --type=utility --utility-sub-type=network.mojom.NetworkService
36783  05:34:37   /Applications/T3 Code (Alpha).app/Contents/MacOS/T3 Code (Alpha) /Applications/T3 Code (Alpha).app/Contents/Resources/app.asar/apps/server/dist/bin.mjs --bootstrap-fd 3
37178  05:34:30   /Applications/T3 Code (Alpha).app/Contents/Frameworks/.../T3 Code (Alpha) Helper (Renderer)

apps/server/dist/bin.mjs (pid 36783) is the in-app server holding the Codex app-server channels open.

Suggested places to look

  1. apps/server/src/provider/Layers/CodexProvider.ts:251probeCodexAppServerProvider. Gate this behind foreground client demand the same way Add background activity policy and host power monitoring #2679 plans to gate makeManagedServerProvider, or change it to read account/model info from local Codex state instead of spawning a fresh app-server every 5 minutes.
  2. apps/server/src/provider/Layers/ProviderSessionReaper.ts:64-71 — the activeTurnId != null short-circuit. PR reconcile provider session state and settle stuck turns #2666 addresses this; a stuck activeTurnId after <turn_aborted> means the reaper never tears down orphan sessions, and any background reconnect/warmup on that session is on the user's dime.
  3. Whatever path triggers codex_api::endpoint::responses_websocket traffic from a non-user-initiated context. Most likely candidates: websocket warmup running on every refresh tick, or a stuck turn being silently retried.

Impact

Blocks work completely — drained 1000 purchased credits in ~14 hours of idle time. The user is now blocked from Codex use until 2026-05-18 19:50 BR (next plan window reset).

Version or commit

T3 Code (Alpha) — latest installer as of 2026-05-15 (the package.json in main reads 0.0.24).
Codex CLI spawned by T3: 0.131.0-alpha.9.

Environment

macOS Darwin 25.3.0 (Apple Silicon), ~/.codex auth via OAuth (auth_mode = "Chatgpt", Codex Pro plan).

Related

Happy to share raw logs_2.sqlite excerpts or specific timestamps on request.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions