{"record":{"id":"17c6b368e7c1de15","repo":"stablyai/orca","slug":"targetchanged","errorCode":"targetChanged","errorMessage":"The active Codex rate-limit target changed before reset.","messagePattern":"The active Codex rate-limit target changed before reset\\.","errorType":"exception","errorClass":"CodexResetCreditScopeRejection","httpStatus":null,"severity":"warning","filePath":"src/main/codex-accounts/service.ts","lineNumber":545,"sourceCode":"        attempt.promise = null\n      },\n      () => {\n        attempt.promise = null\n        if (attempt.state === 'fresh') {\n          this.releaseFreshResetAttempt(idempotencyKey, attempt)\n        }\n      }\n    )\n    return promise\n  }\n\n  private validateResetCreditScope(\n    expectedScope: CodexResetCreditExpectedScope,\n    requireCurrentOffer: boolean\n  ): { managedHomePath: string; rateLimits: RateLimitState } {\n    const rateLimitState = this.rateLimits.getState()\n    if (!sameRateLimitTarget(rateLimitState.codexTarget, expectedScope.target)) {\n      throw new CodexResetCreditScopeRejection(\n        'targetChanged',\n        rateLimitState,\n        'The active Codex rate-limit target changed before reset.'\n      )\n    }\n\n    const settings = this.store.getSettings()\n    if (\n      getSelectedCodexAccountIdForTarget(settings, expectedScope.target) !== expectedScope.accountId\n    ) {\n      throw new CodexResetCreditScopeRejection(\n        'accountChanged',\n        rateLimitState,\n        'The selected Codex account changed before reset.'\n      )\n    }\n    const account = settings.codexManagedAccounts.find(\n      (candidate) => candidate.id === expectedScope.accountId","sourceCodeStart":527,"sourceCodeEnd":563,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/codex-accounts/service.ts#L527-L563","documentation":"A CodexResetCreditScopeRejection with reason 'targetChanged', thrown by validateResetCreditScope. The runtime target currently reported by rateLimits.getState().codexTarget (its runtime + wslDistro) no longer equals expectedScope.target. The reset credit is bound to a specific runtime, so the provider call is refused before it starts to avoid charging the wrong target's credit.","triggerScenarios":"Consuming a reset credit (consumeRateLimitResetCredit replay, or a fresh startResetCreditAttempt) after the active Codex rate-limit target changed between when the scope was captured and when validation runs — e.g. the user switched the active WSL distro or moved from host to WSL runtime mid-flow.","commonSituations":"User toggles the runtime/distro selector, or an external CODEX_RUNTIME/WSL_DISTRO change flips codexTarget while a reset is queued behind the mutation queue. Also after an app restart that rehydrates a providerPending attempt whose target is no longer active.","solutions":["Rebuild the expectedScope from the CURRENT rateLimits.getState().codexTarget and start a fresh attempt with a new idempotency key.","If the target change was unintentional, restore the previously-active target (distro/runtime) and replay the same idempotency key.","For fresh attempts, the rejection is returned as a rejectedBeforeProvider result with retryDisposition 'discardAttempt' (service.ts:476) — discard and let the user re-trigger from the current target."],"exampleFix":"// before: replaying a stale scope after runtime switched\nservice.consumeRateLimitResetCredit(oldKey, staleScope) // rejects targetChanged\n\n// after: rebuild scope from current target\nconst state = rateLimits.getState()\nconst scope = buildCodexResetCreditExpectedScope({ target: state.codexTarget, account, limits: state.codex })!\nawait service.consumeRateLimitResetCredit(crypto.randomUUID(), scope)","handlingStrategy":"validation","validationCode":"// Rebuild the scope from the CURRENT target right before consuming.\nconst { codexTarget } = rateLimits.getState()\nconst scope = buildCodexResetCreditExpectedScope({ target: codexTarget, account, limits: rateLimits.getState().codex })\nif (!sameRateLimitTarget(codexTarget, scope!.target)) {\n  throw new Error('target drifted; refresh and retry')\n}\nawait service.consumeRateLimitResetCredit(crypto.randomUUID(), scope!)","typeGuard":"const isSameTarget = (a: RateLimitRuntimeTarget, b: RateLimitRuntimeTarget): boolean =>\n  a.runtime === b.runtime && a.wslDistro === b.wslDistro","tryCatchPattern":"try {\n  await service.consumeRateLimitResetCredit(key, scope)\n} catch (error) {\n  if (error instanceof CodexResetCreditScopeRejection && error.reason === 'targetChanged') {\n    // rebuild scope from current target and retry once with a fresh key\n  } else throw error\n}","preventionTips":["Capture the scope immediately before consuming it, not from a cached snapshot.","Block target/runtime switching while a reset is pending."],"tags":["codex-accounts","rate-limit-reset","scope-rejection","wsl","runtime"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}