Skip to content

Add Codex API rate limit monitoring and notifications#261

Merged
AnnatarHe merged 1 commit intomainfrom
claude/daemon-api-usage-OmEhr
Apr 6, 2026
Merged

Add Codex API rate limit monitoring and notifications#261
AnnatarHe merged 1 commit intomainfrom
claude/daemon-api-usage-OmEhr

Conversation

@AnnatarHe
Copy link
Copy Markdown
Contributor

@AnnatarHe AnnatarHe commented Apr 6, 2026

Summary

This PR adds support for monitoring OpenAI Codex API rate limits and sending usage data to the ShellTime server for scheduling push notifications when rate limits reset.

Key Changes

  • New file daemon/codex_ratelimit.go: Implements Codex rate limit monitoring with:

    • loadCodexAuth(): Reads authentication credentials from ~/.codex/auth.json
    • fetchCodexUsage(): Calls the Codex usage API to retrieve current rate limit data
    • shortenCodexAPIError(): Converts API errors to short status strings for display
    • Data structures for parsing Codex API responses and caching rate limit information
  • Updated daemon/cc_info_timer.go:

    • Added codexRateLimitCache field to CCInfoTimerService for caching rate limit data
    • Implemented fetchCodexRateLimit(): Periodically fetches Codex rate limits with 10-minute TTL caching
    • Implemented sendCodexUsageToServer(): Sends rate limit data to ShellTime server via /api/v1/codex-usage endpoint
    • Added GetCachedCodexRateLimit() and GetCachedCodexRateLimitError() accessors for retrieving cached data
    • Integrated Codex rate limit fetching into the timer loop alongside existing Anthropic rate limit monitoring
    • Cache is cleared when the timer stops due to inactivity

Implementation Details

  • Rate limit data is cached for 10 minutes to avoid excessive API calls
  • Codex auth is loaded from the standard ~/.codex/auth.json location used by OpenAI CLI tools
  • API requests include proper headers (Authorization bearer token, ChatGPT-Account-Id, User-Agent)
  • Rate limit reset times are converted to RFC3339 format when sending to the server
  • Monitoring is only active on macOS and Linux platforms
  • Failed fetches are retried after the same TTL period
  • Server communication is fire-and-forget to avoid blocking the timer loop

https://claude.ai/code/session_01FPxeHEA8w2cC136GFpi3to


Open with Devin

Add support for fetching Codex rate limit usage data from the OpenAI API
(~/.codex/auth.json) alongside existing Anthropic usage tracking. The daemon
now loads Codex auth credentials, fetches usage every 10 minutes (same cache
TTL as Anthropic), and sends the data to the server via POST /api/v1/codex-usage.

https://claude.ai/code/session_01FPxeHEA8w2cC136GFpi3to
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces Codex rate limit tracking to the CCInfoTimerService. It adds functionality to load local authentication, fetch usage data from the OpenAI API, cache the results, and report them to a central server. The review feedback suggests clearing the lastError field when the timer stops to prevent stale error displays and recommends parallelizing the Anthropic and Codex rate limit fetches to improve efficiency and robustness against timeouts.

Comment thread daemon/cc_info_timer.go
Comment on lines +159 to +163
s.codexRateLimitCache.mu.Lock()
s.codexRateLimitCache.usage = nil
s.codexRateLimitCache.fetchedAt = time.Time{}
s.codexRateLimitCache.lastAttemptAt = time.Time{}
s.codexRateLimitCache.mu.Unlock()
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.

medium

When stopping the timer due to inactivity, the lastError field in the Codex rate limit cache should be cleared along with other fields. This ensures that stale error messages aren't displayed when the service resumes activity and a new fetch is pending.

Suggested change
s.codexRateLimitCache.mu.Lock()
s.codexRateLimitCache.usage = nil
s.codexRateLimitCache.fetchedAt = time.Time{}
s.codexRateLimitCache.lastAttemptAt = time.Time{}
s.codexRateLimitCache.mu.Unlock()
s.codexRateLimitCache.mu.Lock()
s.codexRateLimitCache.usage = nil
s.codexRateLimitCache.fetchedAt = time.Time{}
s.codexRateLimitCache.lastAttemptAt = time.Time{}
s.codexRateLimitCache.lastError = ""
s.codexRateLimitCache.mu.Unlock()

Comment thread daemon/cc_info_timer.go
Comment on lines 182 to +183
s.fetchRateLimit(ctx)
s.fetchCodexRateLimit(ctx)
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.

medium

The Anthropic and Codex rate limit fetches are executed sequentially within a shared 10-second timeout. If the first fetch takes a significant amount of time or times out, the second fetch may be delayed or skipped entirely due to context expiration. Since these fetches are independent, running them in parallel would be more efficient and robust. This also applies to the fetch logic inside the ticker loop (lines 206-207).

		var wg sync.WaitGroup
		wg.Add(2)
		go func() { defer wg.Done(); s.fetchRateLimit(ctx) }()
		go func() { defer wg.Done(); s.fetchCodexRateLimit(ctx) }()
		wg.Wait()

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 6, 2026

Codecov Report

❌ Patch coverage is 20.26144% with 122 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
daemon/cc_info_timer.go 26.80% 70 Missing and 1 partial ⚠️
daemon/codex_ratelimit.go 8.92% 50 Missing and 1 partial ⚠️
Flag Coverage Δ
unittests 39.42% <20.26%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
daemon/codex_ratelimit.go 8.92% <8.92%> (ø)
daemon/cc_info_timer.go 65.44% <26.80%> (-12.13%) ⬇️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

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

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: 16df58bdd1

ℹ️ 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 daemon/codex_ratelimit.go
Comment on lines +78 to +80
if auth.TokenData == nil || auth.TokenData.AccessToken == "" {
return nil, fmt.Errorf("no access token found in codex auth")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Support API-key auth mode in loadCodexAuth

This loader always errors unless tokenData.accessToken is present, even though the parsed schema includes authMode and apiKey. When ~/.codex/auth.json is in API-key mode (no tokenData), Codex usage fetching will always return auth and the daemon never sends Codex usage to /api/v1/codex-usage for those users.

Useful? React with 👍 / 👎.

Comment thread daemon/cc_info_timer.go
Comment on lines 204 to +207
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.fetchRateLimit(ctx)
s.fetchCodexRateLimit(ctx)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Decouple Codex fetch timeout from Anthropic fetch

The periodic loop runs Anthropic and Codex fetches sequentially under a single 10-second context. If the Anthropic request stalls or consumes most of that budget, fetchCodexRateLimit starts with an already-expired (or near-expired) context and fails; because lastAttemptAt is still updated, Codex retries are suppressed for the full cache TTL. This makes Codex monitoring unreliable during Anthropic latency/outage windows.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment thread daemon/cc_info_timer.go
Comment on lines 182 to +183
s.fetchRateLimit(ctx)
s.fetchCodexRateLimit(ctx)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Shared 10-second context timeout insufficient for two sequential network calls

Both fetchRateLimit(ctx) and fetchCodexRateLimit(ctx) are called sequentially within the same goroutine sharing a single 10-second context timeout (daemon/cc_info_timer.go:180-183 and daemon/cc_info_timer.go:204-207). Each function makes an HTTP call with a 5-second client timeout, so in the worst case both calls together need ~10 seconds just for HTTP, leaving zero margin for other operations (macOS Keychain exec.Command in anthropic_ratelimit.go:76, file I/O in codex_ratelimit.go:68). If fetchRateLimit consumes most of the context budget, fetchCodexRateLimit will fail with a context deadline exceeded error. Critically, fetchCodexRateLimit records lastAttemptAt before the API call (daemon/cc_info_timer.go:582-584), so a context-induced failure triggers the 10-minute TTL backoff (codexUsageCacheTTL), preventing retry and leaving codex rate limit data stale for up to 10 minutes.

Prompt for agents
The fetchRateLimit and fetchCodexRateLimit calls are sequential inside a single goroutine sharing one 10-second context. Each has an internal 5-second HTTP client timeout, so in the worst case, the budget is exactly consumed by the first call, leaving nothing for the second. This is made worse because fetchCodexRateLimit records lastAttemptAt before attempting the fetch, so a context-deadline failure triggers a 10-minute TTL backoff.

Approach 1: Give each fetch its own independent context with a 10-second timeout. Replace the single shared context with two separate context.WithTimeout calls, one for fetchRateLimit and one for fetchCodexRateLimit.

Approach 2: Increase the shared context timeout to 20 seconds to accommodate both calls.

Approach 3: Run both fetches concurrently (each with its own context) inside the goroutine, since they are independent operations. This would also improve latency.

The same pattern appears twice: once in the immediate-fetch block at lines 175-184 and once in the ticker loop at lines 199-208. Both should be updated.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@AnnatarHe AnnatarHe merged commit 1fec422 into main Apr 6, 2026
10 of 11 checks passed
@AnnatarHe AnnatarHe deleted the claude/daemon-api-usage-OmEhr branch April 6, 2026 13:55
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