Skip to content

Latest commit

 

History

History
307 lines (240 loc) · 13.7 KB

File metadata and controls

307 lines (240 loc) · 13.7 KB

Architecture and runtime behavior

This document describes the behavior currently implemented by codex-quota-linux. It is a maintainer reference, not a roadmap. User commands stay in the README; proposed behavior should stay in issues or pull requests until it is implemented.

System overview

flowchart LR
    UI["Tray and CLI"] --> APP["app.py orchestration"]
    APP --> CACHE["Project-local caches"]
    APP --> DIRECT["Authenticated direct HTTPS clients"]
    DIRECT -->|"success"| CACHE
    DIRECT -->|"auth or schema failure"| SERVER["Codex app-server"]
    SERVER --> HOME["Isolated temporary CODEX_HOME"]
    SERVER -->|"parsed fallback"| CACHE
    CACHE --> FORMAT["Quota and reset-credit formatters"]
    FORMAT --> UI
Loading

The application keeps saved accounts and caches under .runtime/. Query code never switches the main Codex login. Only the explicit switch action writes the main Codex auth.json.

Main components

Module Responsibility
cli.py Command parsing and terminal output
indicator.py GTK/AppIndicator state, timers, menus, and background workers
app.py Account discovery, query orchestration, caching, and fallback policy
client.py Direct HTTPS clients and Codex app-server JSON-RPC transport
quota.py Quota parsing, cache model, deadlines, labels, and icon status
reset_credits.py Reset-credit parsing, cache model, and display rows
auth_store.py Isolated login and account-slot creation
switcher.py Explicit main-auth replacement
activation.py Isolated rolling-window activation requests
runtime.py, auth_sync.py Permissions, locks, temporary homes, atomic writes, and safe auth copy-back

Query and fallback policy

Both query features use the same policy: prefer the faster direct endpoint and use app-server only when direct authentication or response compatibility is the problem.

Resource Primary path Direct auth/schema failure Direct transient failure App-server call failure
Quota usage Direct usage endpoint Parse app-server rate limits Keep cached quota with an error Keep cached quota with the original direct error
Reset credits Direct reset-credit endpoint Parse app-server rateLimitResetCredits Keep cached reset credits with an error Keep cached reset credits with the original direct error

A direct authentication failure first gets one bounded local recovery attempt. The query compares the main Codex auth.json with the account slot under the runtime lock. It copies the main credential into the slot only when the credentials belong to the same account and the main credential is newer, then retries the direct request once. If no safe copy is possible or the retry still fails authentication, the existing app-server fallback runs. Schema and transient failures do not trigger this main-auth recovery path.

A transient timeout, TLS failure, HTTP 408/429, or server-side 5xx response does not start app-server. It is usually faster and more stable to preserve the cache and try the direct endpoint again on a later refresh.

Shared app-server path

app.py uses one helper for quota and reset-credit fallbacks:

  1. Create an isolated temporary Codex home containing a copy of the account slot's auth.json.
  2. Start codex app-server --stdio with both CODEX_HOME and CODEX_SQLITE_HOME pointing to that home.
  3. Complete the standard handshake: initialize request, initialize response, initialized notification, then the business request.
  4. Parse the business response for the requested resource.
  5. Copy refreshed authentication back only if it is valid, belongs to the same account, and passes the freshness rules below.
  6. Stop app-server and remove the temporary home.

Each fallback starts a new app-server process; the project does not maintain a persistent app-server connection.

Reset-credit response variants

The app-server summary always needs a valid availableCount to be useful. credits has two supported states:

  • A list provides individual status, title or description, grant time, and optional expiration time.
  • null or a missing field means only the count is known. The snapshot remains successful and the CLI reports details unavailable when the count is positive.

App-server timestamps are Unix seconds and are normalized to UTC ISO strings before entering the shared reset-credit cache model. Opaque credit IDs are not stored.

Feature behavior

1. Query quota usage

fetch_state() discovers every valid account slot, puts the selected account first, and queries quota snapshots in parallel with at most four workers. Successful responses replace each account's cache.json. A failed query uses the previous cache when one exists.

once fetches every saved account and prints the selected account first. The tray uses the same snapshots for its current-account section and standby submenu.

2. Query reset credits

Reset credits are queried per account and cached separately from quota usage. Unless a refresh is forced, a cache is reusable until either:

  • reset_credits_refresh_interval_seconds elapses; or
  • an available credit reaches its recorded expiration time.

The CLI command forces a refresh. The tray can either reuse the reset-credit cache or force the network path, depending on the trigger described below.

3. Switch saved accounts

add runs codex login in an isolated temporary home, validates the resulting token fields, and stores the credential under .runtime/accounts/<Alias>/auth.json. It does not modify the main Codex login.

switch runs under the project runtime lock:

  1. Safely copy any refreshed main credential back to the currently selected slot.
  2. Atomically replace the main Codex auth.json with the target slot.
  3. Save the new selected_alias.

Tray switches are background operations, but auth replacements run in click order. Each click receives a generation number. A slow result from an older generation may update its account-specific cache, but it cannot overwrite the tray state after a newer account has been selected.

4. Activate quota windows

Activation runs one account at a time in an isolated temporary home. It invokes an ephemeral, read-only codex exec using gpt-5.6-luna, low reasoning effort, and the prompt Reply exactly: OK. Refreshed auth may be copied back to the same saved slot, but the main Codex login is not replaced.

The command may consume a small number of tokens. Temporary homes are removed after each account, including timeout and failure paths.

Refresh schedule

Defaults come from AppConfig and are loaded when the tray starts. Restart the tray after editing timer values.

Trigger Quota usage Reset credits Network behavior
Tray startup All saved accounts All saved accounts Show cache first, then force both query paths in the background
Menu text timer No query No query Every 60 seconds; rebuild text from in-memory state only
Active timer Selected account No query Default every 120 seconds
Standby timer All saved accounts, including selected Refresh only when cache is stale Default every 600 seconds
Refresh all All saved accounts All saved accounts Force both query paths
Tray switch All saved accounts after the switch Cache only Reorder cached UI immediately, then refresh quota
Activate all All saved accounts after activation Cache only Activation requests, followed by quota refresh
once All saved accounts Cache only Quota network requests only
check-reset-credits No query Selected, named, or all accounts Always force reset-credit refresh

The 60-second menu timer is strictly a display timer. It updates relative text such as Updated 4m ago, reset in 42m, or expires in 3d; it must not call a network client.

GUI behavior contract

User-visible strings are produced by pure helpers in quota.py and reset_credits.py; GTK code only places those strings into menus.

Top-bar label and icon

The label joins remaining percentages without percent signs, for example:

H68 · W43

The 5-hour window (H) controls the icon when present; otherwise the first window is used.

State Icon
No usable quota windows, or failed refresh with cache at least one hour old Gray
Less than 30% remaining Red
30% through 70% remaining Yellow
More than 70% remaining Green

Account menu

The selected account begins with ✓ <Alias> · <Plan>, followed by email, quota-window lines, reset-credit status, and freshness. A standby entry begins with the clickable Switch to <Alias> · <Plan> line and uses the same remaining status lines.

Quota freshness text follows this order:

Condition Text
Authentication-related error Auth needed
Incompatible response schema Backend changed
No usable timestamp Stale
Updated less than 60 seconds ago Updated right now
Updated from 1 minute to less than 1 hour ago Updated {n}m ago
Updated at least 1 hour ago Stale

A generic transport failure keeps the cached timestamp and therefore continues to show its age until the one-hour freshness boundary. Authentication and schema errors override the age text immediately.

Reset-credit menu text follows these templates:

Condition Text
No snapshot Resets unknown
Authentication error Resets Auth needed
Schema error Resets Backend changed
Other failed refresh with a positive cached count Resets {count} · stale
Other failed refresh without a positive cached count Resets stale
Successful snapshot with a known nearest expiration Resets {count} · expires ...
Successful snapshot without expiration details Resets {count}

For detailed snapshots, the tray gives an expired available credit a 60-second grace period. On the next menu redraw after that grace period, it subtracts the known expired credit from the displayed count and advances to the next known expiration. This is a display-only adjustment; cached backend data and count-only snapshots remain unchanged.

In terminal output, a positive count without credit rows is details unavailable; zero without rows is none. Rows with expiration times are sorted by nearest expiration across accounts.

Relative deadline text

Quota resets and reset-credit expirations share one formatter:

  • at most 60 seconds: {verb} now;
  • under 1 hour: {verb} in {n}m;
  • under 6 hours: {verb} in {h}h {m}m;
  • later today or tomorrow: local clock time;
  • 2 through 6 calendar days: weekday and local time;
  • 7 through 31 days: {verb} in {n}d;
  • later dates: abbreviated month and day, plus year when needed.

Runtime and security invariants

Path Purpose
.runtime/config.toml Selected alias and configurable intervals/timeouts
.runtime/accounts/<Alias>/auth.json Saved account credential
.runtime/accounts/<Alias>/cache.json Last quota snapshot
.runtime/accounts/<Alias>/reset_credits_cache.json Last reset-credit snapshot
.runtime/app.lock Cross-thread/process auth and configuration lock
.runtime/tmp/codex-homes/ Isolated Codex homes for login, activation, and fallback

The following rules are part of the design:

  • Runtime directories use mode 0700; credentials, config, locks, and caches use mode 0600.
  • Account slots and protected files must not be symbolic links.
  • Sensitive files are written through a same-directory temporary file followed by atomic replacement.
  • Temporary homes are deleted on normal exit and failure. Inactive leftovers older than one hour are pruned later; the entire .runtime/ tree must still be treated as secret.
  • Refreshed auth is copied back only when JSON is valid, required token fields exist, and account_id matches the destination slot. A timestamped destination is preserved when the source last_refresh is missing, older, equal, or malformed. A newer timestamped source may replace a legacy destination; two legacy files without timestamps retain the previous compatibility behavior. RFC 3339 timestamps are compared in UTC with up to nanosecond precision; any malformed timestamp prevents copy-back.
  • Direct authentication recovery may read the main Codex auth and copy it into a matching saved slot under the runtime lock before one retry. It never writes the main Codex auth.
  • Direct authenticated requests require HTTPS and follow redirects only when scheme, hostname, and effective port are unchanged.
  • Only explicit switch replaces the main Codex auth file.

See the security policy for publication and vulnerability-reporting guidance.

Maintenance map

Behavior Implementation Main tests
Direct transport and app-server handshake client.py tests/test_client.py
Query, cache, and fallback orchestration app.py tests/test_app.py
Quota parsing and GUI strings quota.py tests/test_quota.py
Reset-credit parsing and GUI strings reset_credits.py tests/test_reset_credits.py
Tray timers, menu, and switch generations indicator.py tests/test_indicator.py
Account login, switch, and auth safety auth_store.py, switcher.py, auth_sync.py, runtime.py Matching tests/test_*.py files
Activation command and cleanup activation.py tests/test_activation.py

When changing an endpoint, fallback condition, timer, cache rule, auth write, or user-visible status string:

  1. Update the corresponding behavior test.
  2. Update this document if the intended contract changes.
  3. Update the README only when commands or user-facing capabilities change.
  4. Run python3 -m unittest discover -s tests.