Skip to content

Show and consume rate limit reset credits#144

Open
johnknott wants to merge 1 commit into
Loongphy:mainfrom
johnknott:reset-credits
Open

Show and consume rate limit reset credits#144
johnknott wants to merge 1 commit into
Loongphy:mainfrom
johnknott:reset-credits

Conversation

@johnknott

Copy link
Copy Markdown

This adds support for ChatGPT rate limit reset credits in codex-auth.

The list command now shows a RESETS column with the number of banked reset credits returned by the usage API. This is especially useful on Linux, where there is currently no easy way to view or spend these banked rate limit resets because they are not shown in codex-cli.

This also adds:

  • codex-auth reset <query> --yes to consume one reset credit for a stored ChatGPT account
  • parsing for rate_limit_reset_credits.available_count
  • guarded reset consumption using ChatGPT auth only
  • docs and tests for the new command and list output

The consume command requires --yes so reset credits are not spent accidentally.

@greptile-apps

greptile-apps Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a codex-auth reset <query> --yes command that consumes one ChatGPT rate-limit reset credit, and adds a RESETS column to the list output showing the stored banked-credit count.

  • New reset command (src/workflows/reset.zig, src/cli/commands/reset.zig): resolves an account locally, guards against non-ChatGPT auth, POSTs to the ChatGPT consume endpoint with a generated UUID v4 redeem request ID, and decrements the locally-stored credit count by 1 on success.
  • RESETS column added across both the static CLI list table (src/tui/table.zig) and the live TUI table (src/cli/table_layout.zig, src/cli/rows.zig); reset_credits: ?i64 added to RateLimitSnapshot and parsed from both the live usage API and the registry.
  • parseUsageResponse null-guard widened to preserve snapshots that contain only reset_credits (no rate-limit windows), so accounts surfacing only that field are no longer silently dropped.

Confidence Score: 4/5

Safe to merge; the reset command is gated behind --yes, only operates on ChatGPT accounts, and the new RESETS column is additive with proper memory management throughout.

The core reset flow, HTTP POST helper, UUID generation, and column rendering are all correct. Two minor issues: error.InvalidResponse from the JSON parser is missing from isHandledCliError (unlike the other new reset errors), and the removal of the plan/last initial-width caps in boundWidths silently changes narrow-terminal column layout beyond what the new column requires.

src/cli/table_layout.zig (boundWidths cap changes) and src/workflows/preflight.zig (missing error.InvalidResponse in isHandledCliError)

Important Files Changed

Filename Overview
src/api/usage.zig Adds consumeResetForToken/consumeResetForAuthPath, parseResetConsumeResponse, parseResetCredits, and randomRedeemRequestIdAlloc (UUID v4). Memory ownership is handled correctly; the placeholder redeem_request_id allocation is immediately freed and replaced with the caller-generated UUID.
src/workflows/reset.zig New reset workflow: resolves account via local query, guards against apikey auth, calls consumeResetForAuthPath, and decrements the stored reset-credit count by 1. Error messages are printed before re-raising, but error.InvalidResponse from the API parser isn't covered by isHandledCliError.
src/cli/table_layout.zig Adds RESETS as the 3rd column (column_count 5 to 6) and wires it through accountTable/boundWidths. Also removes the old caps on plan and last initial-width allocation, which subtly changes column sizing on narrow terminals beyond the new column addition.
src/tui/table.zig Inserts RESETS column at index 2 in the static CLI list table. Width tracking, header rendering, cell rendering, and width-adjustment reduction order are all updated consistently for the new 6-column layout.
src/api/http_curl.zig Adds runCurlPostJsonCommand by factoring the existing GET path through a new runCurlJsonCommandWithExecutableAndBody helper that appends request=POST and data=body when a body is present.
src/registry/common.zig Adds reset_credits: ?i64 = null to RateLimitSnapshot; cloneRateLimitSnapshot and rateLimitSnapshotEqual updated accordingly.
src/cli/rows.zig Adds resets field to SwitchRow and SwitchWidths; memory management uses the len!=0 guard to distinguish the default empty-string literal from heap-allocated cells, consistent with how plan is handled.
src/workflows/preflight.zig Adds five new reset-related errors to isHandledCliError; error.AccountNotFound was already present. error.InvalidResponse from parseResetConsumeResponse is missing from the list.
src/cli/output.zig Adds five new print functions for reset error/success output, following the existing stderr/stdout pattern. printResetConsumed prints the consumed-credit confirmation including optional code and redeem_request_id.
tests/api_usage_test.zig Adds tests for reset_credits parsing from API response and for parseResetConsumeResponse (code + windows_reset fields).

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant CLI as codex-auth reset
    participant Registry
    participant AuthFile as auth.json
    participant API as ChatGPT API

    User->>CLI: codex-auth reset work --yes
    CLI->>Registry: loadRegistry + syncActiveAccount
    CLI->>Registry: resolveSwitchQueryLocally(selector)
    Registry-->>CLI: account_key
    CLI->>AuthFile: parseAuthInfo(auth_path)
    AuthFile-->>CLI: access_token, chatgpt_account_id
    CLI->>API: POST /wham/rate-limit-reset-credits/consume
    API-->>CLI: code, windows_reset
    CLI->>Registry: decrement reset_credits by 1
    CLI->>Registry: saveRegistry
    CLI->>User: Consumed reset credit for work
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant CLI as codex-auth reset
    participant Registry
    participant AuthFile as auth.json
    participant API as ChatGPT API

    User->>CLI: codex-auth reset work --yes
    CLI->>Registry: loadRegistry + syncActiveAccount
    CLI->>Registry: resolveSwitchQueryLocally(selector)
    Registry-->>CLI: account_key
    CLI->>AuthFile: parseAuthInfo(auth_path)
    AuthFile-->>CLI: access_token, chatgpt_account_id
    CLI->>API: POST /wham/rate-limit-reset-credits/consume
    API-->>CLI: code, windows_reset
    CLI->>Registry: decrement reset_credits by 1
    CLI->>Registry: saveRegistry
    CLI->>User: Consumed reset credit for work
Loading

Fix All in Codex

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
src/workflows/preflight.zig:27-31
`error.InvalidResponse` missing from handled errors

`parseResetConsumeResponse` can return `error.InvalidResponse` when the API response body isn't a JSON object. This error propagates through `handleReset` (which does print a user-facing message via `printResetConsumeFailedError`) but isn't listed in `isHandledCliError`, unlike `error.RequestFailed`. Depending on how the root handles unrecognised errors, the user could see an internal "BUG: unhandled error" trace after the already-printed friendly message. Adding `error.InvalidResponse` here would keep the treatment consistent with the rest of the new reset errors.

### Issue 2 of 2
src/cli/table_layout.zig:129-134
Silent behavior change to `plan` and `last` initial-allocation caps

The previous code capped `plan` at `"PLAN".len` (4) and `last` at 3 chars during the first `growBoundedWidth` pass; both caps have been removed and replaced with the full column widths. On narrow terminals where space is limited, this allows longer plan names (e.g., "Business") and longer last-activity strings to consume space in the first pass, leaving less room for the email column — confirmed by the test expectation changing from `"very-l.mple.com"` to `"very-l.com"`. The change seems intentional (the test was updated), but it is an observable live-TUI layout change beyond just adding the new RESETS column, and it isn't mentioned in the PR description.

Reviews (1): Last reviewed commit: "feat: show and consume reset credits" | Re-trigger Greptile

Comment on lines +27 to +31
err == error.ResetRequiresConfirmation or
err == error.MultipleAccountsMatched or
err == error.UnsupportedAuthMode or
err == error.MissingAuth or
err == error.RequestFailed or

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 error.InvalidResponse missing from handled errors

parseResetConsumeResponse can return error.InvalidResponse when the API response body isn't a JSON object. This error propagates through handleReset (which does print a user-facing message via printResetConsumeFailedError) but isn't listed in isHandledCliError, unlike error.RequestFailed. Depending on how the root handles unrecognised errors, the user could see an internal "BUG: unhandled error" trace after the already-printed friendly message. Adding error.InvalidResponse here would keep the treatment consistent with the rest of the new reset errors.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/workflows/preflight.zig
Line: 27-31

Comment:
`error.InvalidResponse` missing from handled errors

`parseResetConsumeResponse` can return `error.InvalidResponse` when the API response body isn't a JSON object. This error propagates through `handleReset` (which does print a user-facing message via `printResetConsumeFailedError`) but isn't listed in `isHandledCliError`, unlike `error.RequestFailed`. Depending on how the root handles unrecognised errors, the user could see an internal "BUG: unhandled error" trace after the already-printed friendly message. Adding `error.InvalidResponse` here would keep the treatment consistent with the rest of the new reset errors.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Codex

Comment thread src/cli/table_layout.zig
Comment on lines 129 to +134
growBoundedWidth(&remaining, &bounded.email, @min(widths.email, live_account_ident_width));
growBoundedWidth(&remaining, &bounded.rate_5h, @min(widths.rate_5h, @max(@as(usize, 4), "5H".len)));
growBoundedWidth(&remaining, &bounded.rate_week, @min(widths.rate_week, "WEEKLY".len));
growBoundedWidth(&remaining, &bounded.plan, @min(widths.plan, "PLAN".len));
growBoundedWidth(&remaining, &bounded.last, @min(widths.last, @as(usize, 3)));
growBoundedWidth(&remaining, &bounded.plan, widths.plan);
growBoundedWidth(&remaining, &bounded.last, widths.last);
growBoundedWidth(&remaining, &bounded.resets, @min(widths.resets, "RESETS".len));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Silent behavior change to plan and last initial-allocation caps

The previous code capped plan at "PLAN".len (4) and last at 3 chars during the first growBoundedWidth pass; both caps have been removed and replaced with the full column widths. On narrow terminals where space is limited, this allows longer plan names (e.g., "Business") and longer last-activity strings to consume space in the first pass, leaving less room for the email column — confirmed by the test expectation changing from "very-l.mple.com" to "very-l.com". The change seems intentional (the test was updated), but it is an observable live-TUI layout change beyond just adding the new RESETS column, and it isn't mentioned in the PR description.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cli/table_layout.zig
Line: 129-134

Comment:
Silent behavior change to `plan` and `last` initial-allocation caps

The previous code capped `plan` at `"PLAN".len` (4) and `last` at 3 chars during the first `growBoundedWidth` pass; both caps have been removed and replaced with the full column widths. On narrow terminals where space is limited, this allows longer plan names (e.g., "Business") and longer last-activity strings to consume space in the first pass, leaving less room for the email column — confirmed by the test expectation changing from `"very-l.mple.com"` to `"very-l.com"`. The change seems intentional (the test was updated), but it is an observable live-TUI layout change beyond just adding the new RESETS column, and it isn't mentioned in the PR description.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex

@Loongphy

Copy link
Copy Markdown
Owner

Thanks, this is useful. I think we should narrow the scope of this PR.

For now, I think we only need to surface the available count in list. I would not add it to other account-selection surfaces in this PR, and the column label should be RESET CREDITS.

I would prefer not to add the reset command to codex-auth. The official CLI/TUI can own the reset-consumption flow later, so I do not think we need to implement it here as well. There is already follow-up work on the Codex side: openai/codex#27915 (comment)

@johnknott

johnknott commented Jun 18, 2026 via email

Copy link
Copy Markdown
Author

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