Skip to content

feat(codex): support multiple Codex accounts across Codex homes and pi logins - #1266

Open
jal-co wants to merge 8 commits into
robinebers:mainfrom
jal-co:multi-codex
Open

jal-co wants to merge 8 commits into
robinebers:mainfrom
jal-co:multi-codex

Conversation

@jal-co

@jal-co jal-co commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

TL;DR

Adds account-aware Codex cards for Codex Swap, custom Codex homes, and pi logins. Each account keeps its own limits and reset actions, while spending without a reliable owner stays out of multi-account cards and sync.

Fixes #1265

What was happening

  • OpenUsage treated Codex as one login, so saved Swap accounts and concurrent custom homes did not get stable cards.
  • Matching credentials could not fail over cleanly, and some Swap-managed homes could be treated as writable.
  • Unattributed local or synced spending could appear on the wrong account when several Codex accounts were known.

What this changes

  • Discovers Swap accounts, configured/default Codex homes, hidden sibling Codex homes, Keychain logins, and pi OAuth logins.
  • Merges matching credentials by ChatGPT workspace and email, with stable card IDs and labels.
  • Keeps every Swap-managed home and pi credential read-only. Independent Codex homes can refresh their own auth.json, and rejected credentials fall through to the next matching login.
  • Preserves complete Codex account identities in account-aware iCloud history. Multi-account cards exclude Codex, pi, and OpenCode spending that cannot prove its owner.
  • Keeps reset-credit actions bound to the card whose credentials fetched them.
  • Documents account discovery, ownership rules, refresh behavior, and troubleshooting.

Heads-up

Restart OpenUsage after adding, removing, or renaming an account. Close active Codex sessions before using xswap switch, as required by Codex Swap.

Tests

  • swift build
  • swift test --filter CodexPR1266ReviewTests (4 passed)
  • CFFIXED_USER_HOME=/tmp/openusage-test-home swift test (1,396 passed, 3 skipped)

Screenshots

The current revision shows both Codex cards and no unattributed cost data. The popover sizes to its content, so the two-account view is taller.

Before After
Single Codex card in light mode Two account-specific Codex cards in light mode
Single Codex card in dark mode Two account-specific Codex cards in dark mode

Copilot AI lite review requested due to automatic review settings September 15, 2026 20:01
@github-actions

Copy link
Copy Markdown

Thanks for your interest in contributing to OpenUsage!

External pull requests must reference an open issue that:

  1. Has been approved by a maintainer with the approved label.
  2. Is assigned to the pull request author (@jal-co).

Please discuss the change on an issue first, wait for a maintainer to approve and assign it to you, then reopen this pull request with Fixes #123 in its description.

Read the contribution guidelines.

Copilot AI 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.

🟡 Changes recommended

Nine unresolved moderate findings affect account discovery, credential fallback, provider scoping, sync identity handling, and per-card pricing refresh.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds account-scoped Codex support across Codex homes and pi logins, with separate cards, credentials, usage, reset claims, sync identities, and UI state.

Changes:

  • Discovers and merges Codex accounts by account ID.
  • Scopes authentication, usage, history, reset claims, and layouts per card.
  • Updates shell parsing, UI integration, documentation, and regression tests.
File summaries
File Reviewed change
Tests/OpenUsageTests/UsageHistoryAggregatorTests.swift Tests identity-aware history synchronization.
Tests/OpenUsageTests/LoginShellEnvironmentTests.swift Covers banner-tolerant shell parsing.
Tests/OpenUsageTests/CodexMultiAccountTests.swift Tests discovery, assembly, scoping, expiry, and spend splitting.
Sources/OpenUsage/Views/WidgetRowView.swift Routes reset claims by provider card.
Sources/OpenUsage/Views/WidgetGroupedListView.swift Passes provider IDs to widget rows.
Sources/OpenUsage/Views/CustomizeProviderDetailView.swift Adds Codex pricing controls. Finding (moderate; 3 votes): refresh state remains hardcoded to the default codex card.
Sources/OpenUsage/Stores/WidgetDataStore.swift Exports account-aware history. Finding (moderate; 1 vote): schema selection is based on inserted histories rather than registered cards.
Sources/OpenUsage/Stores/ProviderAccountsStore.swift Adds Codex and pi source types.
Sources/OpenUsage/Stores/DefaultLayout.swift Translates default layouts for additional cards.
Sources/OpenUsage/Services/UsageReader.swift Builds scoped Codex providers.
Sources/OpenUsage/Services/UsageHistoryAggregator.swift Matches history by account identity.
Sources/OpenUsage/Services/ProviderAccountAssembly+Codex.swift Assembles Codex cards. Findings (moderate; 1 vote each): only the first pi credential is retained; codex mapping cleanup can lose swapped-account history; absent default identity assigns unscoped sources by ordering.
Sources/OpenUsage/Services/ProviderAccountAssembly.swift Integrates Codex account assembly.
Sources/OpenUsage/Services/LoginShellEnvironment.swift Handles prefixed begin markers.
Sources/OpenUsage/Providers/ProviderCatalog.swift Registers scoped Codex providers.
Sources/OpenUsage/Providers/Pi/PiUsageScanner.swift Filters pi usage by provider ID.
Sources/OpenUsage/Providers/Pi/PiProviderMapping.swift Maps multiple Codex pi logins.
Sources/OpenUsage/Providers/ErrorCategory.swift Categorizes expired pi tokens.
Sources/OpenUsage/Providers/Codex/CodexResetClaimService.swift Supports per-card reset claims.
Sources/OpenUsage/Providers/Codex/CodexProvider.swift Scopes Codex refresh and usage. Finding (moderate; 1 vote): empty pi ID sets can produce a false pi source note.
Sources/OpenUsage/Providers/Codex/CodexLogUsageScanner.swift Scans account-specific Codex homes.
Sources/OpenUsage/Providers/Codex/CodexAuthStore.swift Supports scoped and read-only pi credentials.
Sources/OpenUsage/Providers/Codex/CodexAccountDiscovery.swift Discovers Codex homes and pi accounts. Findings (moderate; 1 vote each): observer lacks the access-token identity fallback; comma-separated CODEX_HOME values are not shared with observer logic; JWT-derived IDs are not propagated into provider and claim requests.
Sources/OpenUsage/Models/UsageHistoryDocument.swift Validates account identities in v2 sync.
Sources/OpenUsage/App/StatusItemController.swift Injects per-card reset services.
Sources/OpenUsage/App/AppContainer.swift Wires scoped providers, layouts, and claims.
docs/research/account-first-plan.md Marks Codex account support as shipped.
docs/providers/codex.md Documents multi-account Codex behavior.
docs/icloud-sync.md Documents account-aware synchronization.
Review details

Suppressed comments (8)

Sources/OpenUsage/Providers/Codex/CodexAccountDiscovery.swift:96

  • Discovery accepts an access-token JWT claim when id_token has no account identity, but DefaultAccountObserver.observeCodex() only examines id_token (lines 157-164). A default auth.json with the account claim only in access_token is therefore discovered here but leaves assembly unresolved, so no scoped cards are created and the provider falls back to the unscoped Codex card. Use the same access-token fallback in the observer.
            let payload = auth.tokens?.idToken.flatMap { ProviderParse.jwtPayload($0) }
                ?? auth.tokens?.accessToken.flatMap { ProviderParse.jwtPayload($0) }
            let accountID = auth.tokens?.accountID?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty
                ?? DefaultAccountObserver.chatGPTAccountID(inIDTokenPayload: payload)

Sources/OpenUsage/Providers/Codex/CodexAccountDiscovery.swift:60

  • This parser now treats CODEX_HOME as a comma-separated list, but DefaultAccountObserver.observeCodex() still wraps the raw value in a single-element array. With a configured list, the observer reads a path containing the comma and returns .absent, so assembly cannot mark the default account/anchor and can assign the bare ID and unattributed sources to the wrong discovered account. Share the same splitting and expansion logic with the observer.
        if let raw = environment.value(for: "CODEX_HOME")?.trimmingCharacters(in: .whitespacesAndNewlines),
           !raw.isEmpty {
            homes += raw.split(separator: ",")
                .map { $0.trimmingCharacters(in: .whitespaces) }
                .filter { !$0.isEmpty }

Sources/OpenUsage/Providers/Codex/CodexAccountDiscovery.swift:96

  • This fallback lets an auth file create a card from the JWT's account claim when tokens.account_id is absent, but the derived ID is not carried into the auth state used by CodexProvider and CodexResetClaimService. Those requests will consequently omit ChatGPT-Account-Id for these otherwise supported logins, despite the endpoint contract requiring it. Propagate the discovered account ID into the provider/claim-service request state.
            let accountID = auth.tokens?.accountID?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty
                ?? DefaultAccountObserver.chatGPTAccountID(inIDTokenPayload: payload)

Sources/OpenUsage/Providers/Codex/CodexProvider.swift:189

  • For a scoped card with no matching pi login, piProviderIDs is an empty set, but this still starts a pi scan. When any pi session file exists, the scanner returns a non-nil empty result after filtering, and the later source-note check reports pi even though this card has no pi source. Skip the scan for an empty scoped ID set (or otherwise base the note on matching entries).
        async let pi = PiUsageScanner.shared.scan(
            cardID: "codex", piProviderIDs: piProviderIDs, now: now(), pricing: pricing,
            estimateCost: { CodexUsagePricing.estimatedCost(pricing: pricing, model: $0, tokens: $1) }
        )

Sources/OpenUsage/Services/ProviderAccountAssembly+Codex.swift:92

  • pis.first is the only pi credential retained for a card even though piProviderIDs includes every matching login. If one ChatGPT account is signed into pi under multiple provider IDs and the first token is expired or rejected while another is valid, this card reports the pi-expired/error state without trying the usable login. Preserve the pi credentials as candidates and fall back across them while keeping all provider IDs for spend filtering.
                piProviderIDs: pis.map(\.providerID),
                piLogin: pis.first,
                ownsUnattributedSources: identity == unattributedOwner

Sources/OpenUsage/Services/ProviderAccountAssembly+Codex.swift:98

  • When the default-home account changes, this removes the codex identity mapping even though the loop above has already assigned that bare ID to the other account's card. The swap case therefore leaves that card's history unattributed, so it can be omitted or merged incorrectly on peers. The cleanup block should not remove the mapping created for the card that still owns codex.
        if let defaultIdentity, let card = cards.first(where: { $0.identityKey == defaultIdentity }), card.id != "codex" {
            identityKeys.removeValue(forKey: "codex")
        }

Sources/OpenUsage/Services/ProviderAccountAssembly+Codex.swift:75

  • When observeCodex() is .absent (no login in either default home), defaultIdentity is nil and this fallback assigns all unscoped sources to identities.first. With accounts only in sibling homes, filesystem/discovery order therefore decides which account receives unattributable OpenCode usage even though no source has a default badge. Leave these sources unowned unless a default-home identity was resolved, or define and test an explicit default-source rule instead of using ordering.
        let unattributedOwner = defaultIdentity ?? identities.first

Sources/OpenUsage/Stores/WidgetDataStore.swift:446

  • hasAccountCards is derived from histories already inserted into providers, not from the registered Codex cards. If one card on a multi-account Mac has no local history yet, this can see only the other card and downgrade the export to v1 while stripping its identity; peers with both cards then cannot attribute that history. Base the schema/remapping decision on the enabled account-card registry rather than history presence.
        let hasAccountCards = providers.keys.contains { $0.contains("@") }
        if !hasAccountCards {
            identities = identities.filter { ProviderAccountID.family(of: $0.key) == "claude" }
        }
        return UsageHistoryDocument(
  • Files reviewed: 29/33 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +36 to 38
if ProviderAccountID.family(of: providerID) == "codex" {
CodexPricingSection()
}
@robinebers robinebers reopened this Sep 17, 2026

@robinebers robinebers left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

AI Review (Codex) — Request Changes

Posted at the maintainer’s request after reviewing the approved issue, PR implementation, existing discussion, and current main.

Reviewed PR head 835265ec08b38fa24587db40ebbbecd6a7e46519 against remote main at 56378e5765f85d38ff413036fd984afe3d4664e4, verified unchanged at the end of the review. The common ancestor is 86df736e. This is a review of the submitted implementation and its compatibility with current main; no conflict-resolved merged implementation exists yet.

Issue #1265 is open, approved, and assigned to the PR author. Its additional discovery scope remains useful. The earlier policy-bot comment is stale; the current approved-issue check succeeds.

PR #1264 was merged after this branch diverged. Main already supports Codex Swap account cards, workspace/user identities, matching file and Keychain fallback, per-card reset claims, account layout defaults, and account-aware sync. It also protects against changed credentials, token writes to Swap snapshots, and ambiguous local/cached spend. Main does not yet provide #1265’s general sibling-home and pi-auth discovery.

The PR is marked DIRTY. A three-way comparison found conflict markers in 14 files. This requires an integration rewrite around main’s existing account model, beyond resolving text conflicts.

Findings

  1. [P1] Bind each credential read and action to the card’s expected identity. CodexProvider.swift:20
    The card’s identity is discarded when constructing its auth store; only its launch-time file paths remain. A subsequent login switch makes those paths return another account’s credential. The original card then displays that account’s limits, and its reset service can consume that other account’s credit. Mocked tests reproduce both: A’s card sends B’s account header and successfully consumes B’s fake credit. A separate pending-refresh test also replaces auth.json with B’s login during token renewal; the PR overwrites that replacement with A’s refreshed credentials. Main’s identity filtering, current-credential rechecks, and read-only Swap handling prevent these cases. Preserve those protections, including checks around network awaits and source-specific write policy.

  2. [P1] Preserve main’s workspace-plus-user identity contract. ProviderAccountAssembly+Codex.swift:40
    Discovery and reconciliation use the bare ChatGPT workspace/account id. Main’s Codex identity key is accountID|email, deliberately keeping two users in one workspace separate. The PR collapses that fixture into one card. Seeding a registry with main’s existing acct-a|a@example.test record then running PR discovery mints a new codex@… card for the same login, breaking the binding to its existing layout/pins. A valid main v2 document using the composite identity also fails to match the PR’s bare identity. Reuse CodexAccountIdentity and extend its validated credential parsing for the new sources.

  3. [P1] A home’s current login does not own its historical spending. ProviderAccountAssembly+Codex.swift:89
    The PR assigns every rollout under a home to the account currently logged into that home. The regression fixture writes 120 tokens while A occupies the default home, then swaps only the auth files. B’s card receives those unchanged historical 120 tokens. Shared or copied session roots can also contribute to multiple cards because event deduplication is per scan/card. OpenCode’s unattributed history is always assigned to the current default, or to the first discovered identity when no default resolves. Main intentionally excludes these histories when multiple accounts are known and strips invalidated cached spend before display/export. Keep that exclusion unless durable ownership is proven. The same history caveat applies to pi provider IDs if their login identity is replaced or pool slots are reused.

  4. [P2] Keep the bare card’s identity mapping after the default changes. ProviderAccountAssembly+Codex.swift:96
    In the swap fixture A permanently owns codex, while B permanently owns its hashed id. The loop correctly installs both identity mappings, then this cleanup deletes A’s codex mapping because B is now the default. That bypasses A’s cache ownership check and omits its history from multi-card export. The same assembly returns cards in current-default observation order, reversing their registry order after a switch. Main returns persistent record order. The PR’s existing swap test checks IDs and the default-source flag, but neither the ownership map nor order.

  5. [P2] Reload pi credentials when pi renews them. CodexAuthStore.swift:141
    loadPiAuth() reconstructs auth from the PiCodexLogin snapshot captured at launch; it never rereads pi’s auth.json. Expiry checks also use that captured expiry. Renewing the token in pi and pressing Refresh therefore keeps returning authExpired with no HTTP request. This contradicts the new troubleshooting instruction to use the account in pi and refresh. Keep pi read-only, but reread the matching entry and validate its identity on each refresh.

  6. [P2] Retain all matching pi credentials for fallback. ProviderAccountAssembly+Codex.swift:91
    All matching provider IDs are retained for spending, but only pis.first supplies credentials. With one expired openai-codex login and a valid openai-codex-2 login for the same account, the card reports authExpired without trying the valid login. Preserve the candidate set and try matching usable access tokens without rotating pi credentials.

  7. [P2] Do not strip ownership based on which cards have history today. WidgetDataStore.swift:442
    Export decides account awareness from the histories actually inserted, remaps a single exported account to the bare family id, and drops all Codex identities if no hashed provider remains. Two registered Codex accounts with only A’s history export an unowned v1 document. A peer with two matching cards then imports none of A’s spending. A single-account peer can instead accept unowned spending without proving an account match. Main retains complete Codex ownership even when only one account exports. Preserve its export/import identity rules.

  8. [P2] Keep account-aware exports valid when Claude is unresolved. WidgetDataStore.swift:447
    Two Codex histories cause v2 export, but an unresolved bare Claude history is still included without its required identity. The produced document throws invalidIdentity("claude"), blocking the whole device’s history export. Main explicitly omits only unresolved Claude history so other providers can sync. Preserve that behavior.

  9. [P2] Make default identity observation and discovery use compatible parsing. ProviderAccountAssembly+Codex.swift:22
    Discovery accepts an access-token JWT identity, while the unchanged DefaultAccountObserver only checks the id_token and stored account_id. An access-token-only default login yields unresolved, and this early return suppresses even independently identifiable sibling/pi cards. A controlled fixture produces zero cards instead of two. Comma-separated CODEX_HOME is also split by discovery but treated as one literal path by the observer, so the promised list support cannot reliably establish a default-source owner. The discovery code’s payload fallback is only used when the id_token cannot be decoded; a decodable id_token missing the claim does not trigger access-token fallback.

  10. [P2] Carry a discovered JWT account id into usage/reset request credentials. CodexAccountDiscovery.swift:95
    A login with an id_token account claim but no stored tokens.account_id creates a correctly identified card. The auth store then returns the original unmodified auth, and usage/reset requests omit ChatGPT-Account-Id. A mocked request test reproduces the missing header. Main’s scoped auth store fills the selected workspace into the request auth state. Discovery and request routing must use the same resolved identity.

Existing main issue, rather than a new PR regression

The Copilot comment about CodexPricingSection is valid behaviorally: every Codex card exposes the global fallback-model preference, but the wait/backoff/refresh code still targets literal codex. Those view files are identical on current main and PR head. Track this as an existing main follow-up. Because the preference is global, recalculation should reach every applicable Codex card; changing only the selected card’s refresh id would still leave other cards stale.

The empty pi-ID scan can also produce a misleading pi source note. It is a smaller presentation issue, not evidence of token/spend leakage by itself. Skip empty scoped scans or derive the note from matching entries.

Verification

Scope Result
PR’s new multi-account, shell, aggregation, and document suites 36 passed
Existing affected PR auth, resets, native/pi scanners, account registry/cache, fallback pricing, and CLI suites 104 passed, 1 skipped
Current main’s Codex Swap, history, shell, aggregation, and document suites 43 passed
Additional isolated review reproducers 15 failed tests, 21 assertion failures reproducing the findings above

Both isolated revisions compiled through swift test. A local Xcode beta/Sparkle test-runner setup issue was resolved before executing the suites. Review reproducers were added only to the temporary review checkout; repository source was unchanged.

These are focused suites, not a new full-suite run. GitHub’s CI run reports failure but contains zero jobs and zero check runs; it provides no build/test result to interpret. A fresh Build and Test run is needed after rebasing. The current approved-issue check is successful.

All account requests and reset consumption in review reproducers use fake HTTP clients. Real credentials were not edited and no live credit was consumed. iCloud transport and a fresh live UI session were not exercised. The supplied screenshot was inspected; it documents the intended cards but cannot verify credential/history ownership.

Recommended revision

Rebase onto current main and add sibling-home/pi discovery as additional source types. Keep xswap discovery, existing composite identities and record IDs, background Keychain reads, matching-only fallback, read-only Swap/pi credentials, current-credential checks, cache history exclusion, and main’s valid account-aware sync export. Add live pi token reload and complete fallback candidates. Do not claim per-account historical spending merely from the current home or provider-slot assignment. Keep the shell-banner parser fix: it is small, independently tested, and still absent from main. Re-run the existing Codex Swap regression suites alongside the new source tests and obtain fresh CI after integration.

Each ChatGPT account signed in on the Mac gets its own Codex card. Logins
come from every Codex home (CODEX_HOME, ~/.codex, ~/.config/codex, and
sibling ~/.codex-* / ~/.config/codex-* directories) and from pi's
auth.json openai-codex* entries, merged by ChatGPT account id. The account
at the default home keeps the bare `codex` id; other accounts mint
codex@<hash8> cards, seeded with the same layout defaults.

Each card scopes its credentials, rollout homes, pi session spend (by pi
provider id), and reset-credit claims to its own account. pi credentials
are read-only: an expired pi token asks the user to refresh it in pi
rather than rotating tokens pi owns under its lockfile.

Sync documents now carry Codex identities under the v2 schema so peer
Codex history merges by account, while single-account Macs keep writing
v1 files older builds accept.
…ling newline

A login shell that prints a banner (fastfetch, MOTD) right before the
capture command leaves the begin marker in the same NUL-delimited token
as the banner, so the parse never found it and every launch kept the
previous snapshot. The launch account pass then never ran on such
machines. Match the marker as a token suffix instead.
@jal-co

jal-co commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

@robinebers fixed, re requested

swifthu added a commit to swifthu/openusage that referenced this pull request Sep 19, 2026
…sion

Upstream ClaudeSwapAccountTests.swift (added in robinebers#1266 Claude Swap
account support) pattern-matches MetricLine.badge with 4 tuple
elements, but our fork extended the case to 5 by adding an optional
`resetsAt: Date?` field for the MiniMax session-reset bar (see
`33926a0 refactor(minimax): drive menu-bar reset via badge
resetsAt and split provider`).

Swift 6.3.2 emits "failed to produce diagnostic for expression"
on the unmatched pattern, which surfaces as a fatal error during
`swift test`. Adding the missing placeholder restores compilation
without changing the asserted behavior.

Local-only: not propagated to robinebers/openusage per the user's
fork-only convention.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@robinebers

Copy link
Copy Markdown
Owner

@jal-co thank you

@pullfrog review

@pullfrog pullfrog 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.

Important

Changes are still needed in credential rotation, sibling-home discovery, refresh fallback, and unresolved default-account handling. Four findings are inline.

Reviewed changes across the complete current PR diff, including the integration with the existing Codex Swap account model.

  • Account discovery: Additional Codex homes and pi logins join the existing workspace-and-user identity model and persistent card registry.
  • Credential handling: Pi credentials reload read-only, matching candidates remain identity-scoped, and selected Codex homes gain token refresh and write-back.
  • Account integration: Catalog construction supplies the new credential sources while retaining existing ambiguous-history exclusion and account-aware sync behavior.
  • Shell capture and coverage: Banner-tolerant environment parsing, new account regression tests, provider documentation, and supplied screenshots.

ℹ️ PR Evidence Describes the Earlier Implementation

The description and screenshots still claim per-account local spending and single-account v1 sync. The current revision instead excludes unattributed spending when multiple accounts are known and preserves complete Codex identities in account-aware exports, so that evidence no longer describes the implementation being reviewed.

Technical details
# Update the evidence for the integrated revision

## Affected sites
- PR description: What this changes, Heads-up, Tests, and Screenshots.
- The screenshot links reference 835265ec, before the integration commit.

## Required outcome
- Describe the current spending exclusions and sync compatibility rather than the superseded implementation.
- Refresh the live screenshots and macOS verification results for the revised behavior.

## Review verification
- An isolated Linux Swift check reproduced the hidden-folder omission using the production FileManager options.
- The production CodexProvider+Accounts.swift extension, compiled with minimal injected test doubles, returned an error after a rejected token refresh without trying a valid second credential; that credential succeeded alone.
- Credential ownership was traced through discovery, assembly, auth scoping, and refresh, with the Codex Swap contract checked against upstream source.
- Full SwiftPM tests and a live app rebuild/run were not performed: this runner is Linux and the package requires macOS frameworks. No Build and Test result was listed among this PR's current checks.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using GPT Astra𝕏

Comment thread Sources/OpenUsage/Services/ProviderAccountAssembly+Codex.swift
Comment thread Sources/OpenUsage/Providers/Codex/CodexAccountDiscovery.swift Outdated
Comment thread Sources/OpenUsage/Providers/Codex/CodexProvider+Accounts.swift
Comment thread Sources/OpenUsage/Providers/DefaultAccountObserver.swift Outdated
@github-actions

Copy link
Copy Markdown

This pull request adds 1,176 lines.

Pull requests with more than 1,000 added lines are rarely merged because they are difficult to review and often extend beyond the approved issue. Please consider breaking this into a smaller, more surgical contribution.

@pullfrog

pullfrog Bot commented Sep 20, 2026

Copy link
Copy Markdown

Run failed. View the logs →

Pullfrog  | Rerun failed job ➔View workflow run | via Pullfrog | Using GPT Astra𝕏

@pullfrog

pullfrog Bot commented Sep 20, 2026

Copy link
Copy Markdown

Run failed. View the logs →

Pullfrog  | Rerun failed job ➔View workflow run | via Pullfrog | Using GPT Astra𝕏

@pullfrog

pullfrog Bot commented Sep 20, 2026

Copy link
Copy Markdown

Run failed. View the logs →

Pullfrog  | Rerun failed job ➔View workflow run | via Pullfrog | Using GPT Astra𝕏

@pullfrog

pullfrog Bot commented Sep 20, 2026

Copy link
Copy Markdown

Run failed. View the logs →

Pullfrog  | Rerun failed job ➔View workflow run | via Pullfrog | Using GPT Astra𝕏

@pullfrog

pullfrog Bot commented Sep 20, 2026

Copy link
Copy Markdown

Warning

Your Codex subscription hit its usage limit, so every run stops before the agent starts.

Every Pullfrog run on robinebers has failed since September 20 (5 runs, no successes), so this review did not happen.

Codex returns "The usage limit has been reached" for your ChatGPT subscription, which means you've used all messages available in the current limit window. No OpenAI API key is stored to fall back on, and the Router balance is empty.

To fix it:

  1. Check your current usage and when the limit resets at OpenAI's usage page — the window may be hourly or daily.
  2. Add an OpenAI API key on the BYOK tab of your Billing card to keep runs working when the subscription is capped — the setup docs cover OpenAI.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support multiple Codex accounts across Codex homes and pi logins

3 participants