Skip to content

Copilot long_context window reverts on restart: startup OAuth reconciliation deletes providers.github-copilot.modelContextWindows #7092

Open

Description

@RobinHe429

Client or integration

Codex App

Area

Catalog / models

Summary

The documentation tells users to opt a GitHub Copilot model into the upstream long-context tier with providers.github-copilot.modelContextTiers.<model> = "long_context" plus a per-model window in providers.github-copilot.modelContextWindows ("It raises the advertised window only when that exact model has a known per-model window in modelContextWindows (for example, 1000000)" — docs-site/.../guides/model-routing.md).

The tier survives restarts, but the per-model window does not. On every startup reconcileOAuthProviders() → projectOAuthProviderReconciliation() → applyOAuthPresetCatalog() runs for github-copilot (authMode: "oauth"), and because the registry preset defines no modelContextWindows and the field is not in OAUTH_PRESERVE_WHEN_PRESET_UNSET, the delete branch removes the operator's map from config.json.

Result: on every start the Codex catalog row for github-copilot/gpt-6.1-sol falls back to GitHub Copilot's live capabilities.limits.max_context_window_tokens (400000), while requests still ride contextTier: "long_context" upstream. The only way to keep the advertised 1.05M window is to re-run ocx models set github-copilot/gpt-6.1-sol --context-window 1050000 after every start (I currently automate that with an external scheduled task).

The src/oauth/index.ts comment states this is deliberate ("The input side (contextWindow, modelContextWindows) describes what the account's models are, which the preset and live discovery do own"), but for github-copilot the preset owns nothing here and live discovery only ever reports the default-tier window, so the documented long-context workflow has no persistent path. modelContextTiers is preserved across restarts (and across OAuth upserts), which makes the asymmetry look unintended.

Expected: an explicit operator-set per-model window — at minimum the per-model evidence for a long_context tier the operator enabled — should survive startup reconciliation; otherwise the docs should say it does not and describe the supported path.

Reproduction

  1. ocx provider edit github-copilot --model-context-tier gpt-6.1-sol=long_context
  2. ocx models set github-copilot/gpt-6.1-sol --context-window 1050000
  3. Confirm the live state: /api/models and the Codex catalog (~/.codex/models_cache.json, cc-switch-model-catalog.json) report contextWindow: 1050000 for github-copilot/gpt-6.1-sol.
  4. Restart OpenCodex (or re-run ocx start).
  5. ~/.opencodex/config.json no longer contains providers.github-copilot.modelContextWindows (only modelContextTiers remains), and the catalog row is back to contextWindow: 400000.

Reproduced on 2.81.0. The same reconcile code is present at the v2.84.0 target commit (6955343008), so I expect it to still reproduce there.

Version

2.81.0

Operating system

Windows 11 24H2

Provider and model

github-copilot / gpt-6.1-sol (Copilot Business seat; live /models reports capabilities.limits.max_context_window_tokens: 400000)

Logs or error output

# after a restart, config diff:
- "modelContextWindows": { "gpt-6.1-sol": 1050000 }   # removed by startup reconciliation
  "modelContextTiers":   { "gpt-6.1-sol": "long_context", "gpt-6-sol": "long_context" }  # kept

Redacted configuration

{
  "providers": {
    "github-copilot": {
      "adapter": "openai-chat",
      "baseUrl": "https://api.githubcopilot.com",
      "authMode": "oauth",
      "liveModels": true,
      "modelContextTiers": {
        "gpt-6.1-sol": "long_context",
        "gpt-6-sol": "long_context"
      },
      "modelContextWindows": {
        "gpt-6.1-sol": 1050000
      }
    }
  }
}

(before restart — modelContextWindows is removed by the startup reconcile)

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. added
    bugSomething isn't working
    account-poolOAuth, credentials, Codex pool, quota, failover, plans
    catalogModel catalog, slugs, visibility, routed entries
    on Oct 14, 2026
  2. geekxm commented on Oct 14, 2026

    @geekxm

    Confirming this reproduces on a different OAuth provider and a different OS, and adding
    one detail I think changes the severity: on this provider the operator's map is not merely
    removed — it is replaced by stale registry seeds that silently cap the advertised window.

    Environment

    • @bitkyc08/opencodex 2.84.0, macOS 27.0.1 (build 26A434), Apple Silicon arm64
    • Provider: xai (authMode: "oauth", OAuth/Grok-subscription lane) — not github-copilot
    • Model: xai/grok-composer-2.5-fast (in the registry roster XAI_MODELS, but with no live
      context metadata, so it has no seed and no discovery value)

    Exact code path (matches your diagnosis)

    src/oauth/index.ts:

    const OAUTH_RECONCILE_FIELDS: (keyof OcxProviderConfig)[] = [
      "models",
      "contextWindow",
      "modelContextWindows",   // line 1216  ← reconciled
      "defaultMaxOutputTokens",
      "modelMaxOutputTokens",  // line 1218
      ...
    ];
    
    const OAUTH_PRESERVE_WHEN_PRESET_UNSET: ReadonlySet<keyof OcxProviderConfig> = new Set([
      "defaultMaxOutputTokens",
      "modelMaxOutputTokens",  // lines 1257-1260  ← only these two are preserved
    ]);
    for (const field of OAUTH_RECONCILE_FIELDS) {
      if (JSON.stringify(provider[field]) === JSON.stringify(preset[field])) continue;
      if (preset[field] !== undefined) {
        provider[field] = cloneProviderField(preset[field]) as never;
      } else if (!OAUTH_PRESERVE_WHEN_PRESET_UNSET.has(field)) {
        delete provider[field];   // line 1302 — modelContextWindows lands here
      }
    }

    The xai preset defines models: XAI_MODELS but no modelContextWindows
    (src/providers/registry/entries-core.ts:320), so the delete branch fires.

    The asymmetry you observed is therefore structural, not Copilot-specific:
    modelMaxOutputTokens is in OAUTH_PRESERVE_WHEN_PRESET_UNSET and survives, while
    modelContextWindows is reconciled and has no preset value. I see exactly that split on xai:
    after repeated restarts modelMaxOutputTokens still held 17 operator entries while
    modelContextWindows held only the seed set.

    What makes it worse than "the field is removed"

    The field is not left absent — enrichProviderFromRegistry re-seeds it (fill-only, as the
    comment at lines 1233-1237 describes). So the operator's map is overwritten by seed values,
    and some of those seeds are larger than what the provider actually serves:

    stage xai.modelContextWindows /v1/models for grok-composer-2.5-fast
    after ocx config set providers.xai.modelContextWindows '{...}' 17 entries, both grok-composer-2.5-fast keys present at 200000 context_window: 200000
    after restart #1 back to the 9 seed entries; both added keys gone still 200000 (router kept the loaded value)
    after restart #2 9 seed entries context_window absent

    Because modelContextWindows caps rather than raises, the stale seed is usually harmless —
    but it is still wrong state the operator cannot correct durably. xai/grok-4.5 is seeded as
    500000 while live discovery reports 256000, so the saved row disagrees with what is served,
    and only the cap keeps the advertised value correct. On a model with neither a seed nor live
    metadata (grok-composer-2.5-fast) the field is simply gone, and every client sees no
    context_window at all.

    Reproduction on macOS (deterministic)

    1. Proxy running with the launchd service, xai on the OAuth lane.
    2. ocx config set providers.xai.modelContextWindows '{"grok-composer-2.5-fast":200000,"grok-composer-2.5-fast--fast":200000, ...}'
      (also reproducible via POST /api/providers {name:"xai", provider:{...}})
    3. ~/Library/Application Support/.../config.json (and ~/.opencodex/config.json) now shows
      the added keys; GET /v1/models reports context_window: 200000.
    4. Restart the proxy. The added keys are gone from config.json; GET /v1/models still reports
      200000 for that session.
    5. Restart again. GET /v1/models no longer reports context_window for that model.

    This reproduces even when the proxy is stopped before the edit, so it is the startup
    reconcile rather than the shutdown flush in #488.

    Suggested direction

    The file already contains the fix pattern for this exact hazard — lines 1233-1237 explain that
    requiresReasoningPlaceholderModels is deliberately kept out of OAUTH_RECONCILE_FIELDS
    because "the delete-when-preset-undefined branch would wipe an explicit user opt-out ([]) on
    every startup".

    modelContextWindows has the same property today: no OAuth preset seeds it, yet operators are
    expected to set it (the long-context workflow in your issue, and per-model fallbacks for models
    whose provider publishes no window). Either excluding it from OAUTH_RECONCILE_FIELDS, or
    adding it to OAUTH_PRESERVE_WHEN_PRESET_UNSET, would make it behave like
    modelMaxOutputTokens already does.

    Workaround in use

    A small script re-applies the operator's modelContextWindows entries and re-verifies
    /v1/models after each restart. Happy to test a patch if you want a second environment.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansbugSomething isn't workingcatalogModel catalog, slugs, visibility, routed entries

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions