Skip to content

Fix Copilot free-plan usage parsing#393

Open
emanuelst wants to merge 2 commits intosteipete:mainfrom
emanuelst:codex/copilot-free-plan-usage-only
Open

Fix Copilot free-plan usage parsing#393
emanuelst wants to merge 2 commits intosteipete:mainfrom
emanuelst:codex/copilot-free-plan-usage-only

Conversation

@emanuelst
Copy link

@emanuelst emanuelst commented Feb 18, 2026

Summary

This PR fixes Copilot usage handling for free-plan accounts where the API response shape differs from paid-plan payloads.

Related Issue

possibly fixes #162

What changed

  • Added parsing support for Copilot quota data from:
    • monthly_quotas
    • limited_user_quotas
  • Updated Copilot fetch behavior to:
    • use chat quota as primary when premium/completions quota is unavailable
    • avoid defaulting to a fake 0%-used primary window

Why

Free-plan Copilot responses can omit usable quota_snapshots, which previously caused incorrect UI output (for example: Copilot error: The data couldn’t be read because it is missing).

Note

AI-assisted, tested locally.

Copy link

@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: 46eaf23711

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 48 to 49
primary = chat
secondary = nil

Choose a reason for hiding this comment

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

P2 Badge Keep Copilot chat quota in the chat slot

When premium_interactions is missing, this branch moves the chat window into primary and clears secondary. The menu UI labels primary as Copilot sessionLabel ("Premium") and secondary as weeklyLabel ("Chat") in MenuDescriptor/CopilotProviderDescriptor, so free-plan accounts end up seeing chat usage mislabeled as Premium. Preserve chat in secondary (or make labels dynamic) so the displayed quota type stays accurate.

Useful? React with 👍 / 👎.

@emanuelst emanuelst force-pushed the codex/copilot-free-plan-usage-only branch from 46eaf23 to 2948e25 Compare February 18, 2026 03:36
@emanuelst emanuelst force-pushed the codex/copilot-free-plan-usage-only branch from 2948e25 to 4d8a0c2 Compare February 18, 2026 03:42
@emanuelst
Copy link
Author

@codex review

Copy link

@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: 4d8a0c2dcf

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 224 to 227
let entitlement = max(0, monthly ?? 0)
let remaining = max(0, limited ?? monthly ?? 0)
let percentRemaining: Double = if entitlement > 0 {
max(0, min(100, (remaining / entitlement) * 100))

Choose a reason for hiding this comment

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

P1 Badge Avoid treating limited-only quotas as fully exhausted

If the API returns limited_user_quotas without monthly_quotas, this code sets entitlement to 0 and then forces percentRemaining to 0, which later becomes usedPercent = 100. In that scenario users with non-zero limited quota are displayed as fully depleted. Because both fields are decoded as optional, this input is already considered valid by the parser, so the snapshot builder should avoid emitting a 100%-used window when no denominator is available.

Useful? React with 👍 / 👎.

Comment on lines 48 to 49
primary = nil
secondary = chat

Choose a reason for hiding this comment

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

P2 Badge Set primary window when Copilot only reports chat quota

When premium quota is absent but chat quota exists, this branch stores chat in secondary and leaves primary nil. Downstream paths that rely on snapshot.primary (for example UsageStore.handleSessionQuotaTransition) then skip processing entirely, so chat-only Copilot accounts lose quota-transition behavior despite having usage data. Populating primary in this fallback avoids that regression.

Useful? React with 👍 / 👎.

@emanuelst emanuelst force-pushed the codex/copilot-free-plan-usage-only branch 3 times, most recently from e9fbae3 to a239f0f Compare February 18, 2026 03:57
@emanuelst
Copy link
Author

@codex review

Copy link

@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: a239f0f472

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

func handleSessionQuotaTransition(provider: UsageProvider, snapshot: UsageSnapshot) {
guard let primary = snapshot.primary else { return }
let currentRemaining = primary.remainingPercent
guard let sessionWindow = snapshot.primary ?? snapshot.secondary else { return }

Choose a reason for hiding this comment

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

P2 Badge Keep session notifications tied to primary quota only

This fallback now treats secondary as the session window when primary is missing, which happens for Copilot chat-only plans after this change (CopilotUsageFetcher explicitly sets primary = nil, secondary = chat). In that case, monthly chat quota changes will trigger "session depleted/restored" notifications, so users with session notifications enabled can receive misleading alerts for a non-session limit.

Useful? React with 👍 / 👎.

@emanuelst emanuelst force-pushed the codex/copilot-free-plan-usage-only branch from a239f0f to 7d45086 Compare February 18, 2026 04:15
@emanuelst
Copy link
Author

@codex review

Copy link

@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: 7d45086401

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 226 to 227
let entitlement = max(0, monthly ?? limited ?? 0)
let remaining = max(0, limited ?? monthly ?? 0)

Choose a reason for hiding this comment

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

P1 Badge Avoid forcing 0% usage when monthly quota is missing

In makeQuotaSnapshot, the monthly == nil && limited != nil path sets both entitlement and remaining from limited, which guarantees percentRemaining == 100 and therefore reports usedPercent == 0 for every such response. That fabricates an always-unused window for free-plan payloads that only include limited_user_quotas, so the menu/highest-usage ranking can underreport real pressure and skip warning scenarios.

Useful? React with 👍 / 👎.

Comment on lines +49 to +50
primary = nil
secondary = chatWindow

Choose a reason for hiding this comment

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

P2 Badge Map chat-only Copilot quota into primary window

When premium data is absent, this branch leaves primary nil and stores chat quota only in secondary. The session notification pipeline (UsageStore.handleSessionQuotaTransition) is keyed to snapshot.primary, so chat-only Copilot accounts never produce depleted/restored session transitions even when their only quota window changes state. Falling back to chat as primary (or adding a transition fallback) avoids disabling notifications for free-plan users.

Useful? React with 👍 / 👎.

GitHub's /copilot_internal/user API returns null for assigned_date and
quota_reset_date on trial/free Copilot plans, causing a
DecodingError.valueNotFound when decoding the JSON response.

This change makes both fields optional (String?) to handle null values
gracefully.

Fixes steipete#162

(cherry picked from commit 8a470bd)
@emanuelst emanuelst force-pushed the codex/copilot-free-plan-usage-only branch from 7d45086 to e642584 Compare February 18, 2026 04:26
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.

Copilot error: The data couldn’t be read because it is missing.

2 participants

Comments