Skip to content

Fix Codex and Spark quota-window detection and automatic recovery - #12

Open
motonari728 wants to merge 15 commits into
wavever:mainfrom
motonari728:codex/codex-window-verification
Open

Fix Codex and Spark quota-window detection and automatic recovery#12
motonari728 wants to merge 15 commits into
wavever:mainfrom
motonari728:codex/codex-window-verification

Conversation

@motonari728

Copy link
Copy Markdown

Summary

Improve quota-window detection and automatic recovery for Codex and Spark. Claude behavior and the Codex TUI transport are unchanged.

Problem

Codex can report 0% usage for both started and unstarted quota windows. A single response can look identical in both cases.

For example, consider two observations of a five-hour window (illustrative times):

Pattern API response at 10:00 API response at 10:01
Started, but usage still reported as 0% 0% used; resets at 15:00 0% used; resets at 15:00
Not started; reset time slides forward 0% used; resets at 15:00 0% used; resets at 15:01

In the first case, the reset stays at 15:00: the remaining time decreases from five hours to four hours and 59 minutes. In the second, the reset moves forward by the same minute as the observation: it remains five hours away rather than counting down toward a fixed boundary.

Treating both as inactive because usage is 0% can cause unnecessary pings or incorrect scheduling. The same ambiguity matters for weekly windows, including on plans with no five-hour limit.

A successful CLI request also does not guarantee that a quota window has started. Previously, the watcher reported a new window immediately after a successful request, without verifying it or providing timely recovery when it remained unstarted.

Solution

  • Use the same precheck → CLI → postcheck path for manual and watcher pings. A failed precheck reports the reason and exits without sending or consuming an automatic attempt. If the postcheck fails, report the CLI outcome separately and leave quota activation unconfirmed.
  • When a 0% result is ambiguous, compare reset times at least 60 seconds apart. In the example above, 15:00 → 15:00 supports a started window; 15:00 → 15:01 as the observation advances from 10:00 to 10:01 supports an unstarted window. Inconclusive results remain unconfirmed.
  • Keep manual ping responsive: no retry sleeps or one-minute verification wait. HTTP 401 still permits immediate credential reload/refresh and re-request. When further observation is needed, suggest limitping status, which reads usage without sending a ping.
  • Let watch and bg recheck automatically. If a window is confirmed unstarted after a ping, retry with increasing delays, capped at four automatic attempts per rolling hour per account and quota bucket. Unconfirmed results or API errors do not trigger immediate model retries.
  • Let only the watcher schedule retries. Authentication/permission failures back off from 30 seconds to at most one hour; other quota-read failures cap at ten minutes, respecting Retry-After. This also applies when the send-time precheck fails repeatedly. Restarting the watcher clears these read-retry delays after access is restored.
  • Schedule against the five-hour window when present, otherwise the weekly window. Verification time counts toward reset_buffer; an unknown previous reset adds no buffer wait.
  • Retain observations across commands and restarts. Status JSON adds start_state and optional verification_due_at for Codex/Spark.

Reset-time comparison is an empirical inference, not an explicit API start flag. The original Plus-plan scenario has not been revalidated on the current plan.

See Codex/Spark window verification for a brief explanation. This PR is independent of #11.

Local state

State is stored in $XDG_CONFIG_HOME/limitping/state/codex/, or ~/.config/limitping/state/codex/ when XDG_CONFIG_HOME is unset.

This is a bounded working snapshot, not an accumulating API history: observations are overwritten, and automatic-attempt timestamps older than one hour are removed on subsequent observation or attempt updates. No credentials or raw API responses are stored. Storage stays roughly constant for the same accounts and quota buckets; unused account/model entries are not automatically removed.

Missing state is recreated from fresh quota API reads. For ambiguous 0% usage, the first read establishes a baseline and another read at least 60 seconds later can restore the start-state determination. watch/bg performs that recheck automatically; interactive users run limitping status again. No model request is needed just to reconstruct the observations.

State also retains automatic-ping retry cooldowns and in-progress ping coordination, so deleting it loses those safeguards as well as cached observations. It should not be cleared while a watcher or ping is running. Manual pings bypass automatic cooldowns and attempt budgets, but retain the existing in-progress duplicate protection. API-read backoff is watcher-local and is not persisted.

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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-07T13:31:07.761756Z 2eda9cb PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@motonari728

motonari728 commented Sep 7, 2026

Copy link
Copy Markdown
Author

Research notes: quota API fields and the verification approach

Can one API response identify an unstarted window?

The quota response inspected on 2026-09-07 had no explicit window-start field. It provides usage and reset timing, but at 0% usage those values can describe either an unstarted window or one that has just started.

The windows under rate_limit and additional_rate_limits[].rate_limit in GET https://chatgpt.com/backend-api/wham/usage contained these integer fields:

Field What it tells us
used_percent Positive usage indicates activity. At 0%, the start state is ambiguous.
limit_window_seconds The window's duration.
reset_at The reset timestamp. Comparing observations reveals whether it stays fixed or moves forward.
reset_after_seconds Time until reset. A full interval can also occur just after a window starts.

allowed and limit_reached describe whether more usage is permitted, not whether the window has started. model_usage.*.available_at concerns model availability and was null in this response.

The app-server schema generated in the earlier investigation likewise exposed usedPercent, windowDurationMins, and resetsAt, with no explicit start flag.

Why compare observations one minute apart?

What we need to learn is whether the reset is counting down or sliding forward. For example, with usage still at 0%:

Window Read at 10:00 Read at 10:01
Started Resets at 15:00 Resets at 15:00
Unstarted Resets at 15:00 Resets at 15:01

Waiting a minute separates this movement from small timestamp variations. This PR uses that comparison to infer the start state and leaves inconsistent observations unconfirmed. A completed ping is followed by a quota check so CLI completion and window activation remain separate results.

What other OSS implementations do

The 2026-09-06 source review found several useful approaches:

Project Relevant approach and limitation
onWatch Infers unstarted from a reset roughly a full window away, using one sample. A genuinely started window in its first two minutes can match the same condition.
codex-switch Requires positive usage and five inferred elapsed minutes before treating the window as active. This does not recognize an active window still reported as 0%.
CPA quota scheduler Reads quota before a send and again three seconds afterward, with persisted recovery/backoff. Its classifier does not immediately recognize a newly fixed reset at 0%.
cockpit-tools Checks completed response output, but records no before/after quota verification on the active wakeup path. Its reset scheduler uses cached deadlines, not fixed-versus-sliding comparison.

These implementations provide precedents for quota checks and recovery, but none directly handles the distinction this PR needs: a fixed reset at 0% versus a sliding reset at 0%. That is why this proposal adds comparison across observations rather than adopting one of their classifiers unchanged.

A codex-lb user report describes the same practical concern: a successful-looking warmup followed by a still-sliding Pro weekly reset.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2eda9cb9bf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/scheduler/codex.go
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