Skip to content

[Feature]: opt-in crash-safe reset-credit auto-redemption before expiry #822

Closed

Description

@whitesarum

Area

Authentication / account pools / reset-credit lifecycle / background scheduling

Goal

Automatically redeem an unused reset credit shortly before it expires, when the user has explicitly opted in, without risking duplicate or stale irreversible redemption.

This is a proactive, time-based lifecycle feature.

Its trigger is:

reset credit approaching expiry

not:

upstream quota rejection

Automatic redemption must remain disabled by default.

Existing foundation

OpenCodex already has:

  • reset-credit balance/status visibility;
  • credit expiry information where the upstream provides it;
  • manual reset-credit redemption.

What is missing is the safe background policy that turns that information into an irreversible automatic action.

This must not be implemented as a timer that blindly presses the existing manual redemption path.

Core safety rule

Immediately before any automatic redemption is dispatched, OpenCodex must refresh the authoritative credit state.

Redeem only if the fresh state proves all of the following:

account is the intended account
credit still exists
credit is still unused
expiry is known
expiry is still approaching
the same credit generation is still current
auto-redemption is still enabled

If the refresh fails or any required state is missing, stale, contradictory, or ambiguous:

do not redeem

Fail closed.

Safe lead time

Do not schedule redemption exactly at the recorded expiry timestamp.

At the nominal expiry time, the upstream may already consider the credit expired.

Expose a deliberate lead-time policy, for example conceptually:

{
  "resetCreditAutoRedeem": {
    "enabled": true,
    "leadTimeMinutes": 10
  }
}

The exact configuration name and default lead time can be decided during implementation.

The important contract is:

redeem shortly before expiry

rather than:

redeem at expiry

The lead time must be bounded and validated.

Scheduling model

When fresh authoritative state identifies an eligible credit:

credit generation G
expires at T

↓
schedule candidate dispatch at T - leadTime

The scheduled item must remain tied to:

  • the specific account;
  • the specific credit generation;
  • the observed expiry.

A later refresh that reports a different credit generation or expiry invalidates the previous schedule.

Do not treat an old timer as authority to spend a newer credit.

Pre-dispatch revalidation

A timer firing means only:

re-check now

It does not mean:

redeem now

Required sequence:

timer fires
    ↓
fresh authoritative credit refresh
    ↓
validate account + generation + balance + expiry
    ↓
still eligible?
    ├─ no  -> stop
    └─ yes -> begin one idempotent redemption

No automatic consume call should be authorized solely from cached state.

Idempotency

Each logical automatic redemption must use one stable idempotency identity.

If the redemption request itself is retried because of:

  • connection loss;
  • timeout;
  • uncertain response delivery;
  • process recovery;

reuse the same logical idempotency identity rather than generating a new one.

An uncertain result must never result in blind repeated credit consumption.

Single-flight

Only one automatic redemption may run for the same:

account + credit generation

at a time.

For example:

scheduler callback A ─┐
refresh callback B   ─┼─> one redemption operation
restart recovery C   ─┘

Concurrent timers, refreshes, Dashboard activity, or other background work must not race into several consume requests.

Manual user actions and automatic scheduling also need a deterministic interaction contract.

If the user manually consumes the credit first, the automatic pre-dispatch refresh should observe that state and stop.

Restart and crash recovery

This feature must define what happens when OpenCodex restarts between:

schedule creation
redemption dispatch
redemption response

A restart must not cause duplicate redemption.

On startup, recover only enough durable metadata to safely determine whether a scheduled automatic redemption still needs attention.

Then refresh authoritative upstream state before doing anything irreversible.

Do not simply replay every timer that existed before shutdown.

Example

Before crash:

credit generation G
auto-redemption due at 16:50

After restart at 16:51:

load durable scheduler intent
    ↓
refresh authoritative credit state
    ↓
G still exists and is eligible?
    ├─ no  -> discard recovered intent
    └─ yes -> resume same logical operation safely

If the prior process may already have dispatched the consume call, recovery must rely on idempotency and refreshed authoritative state rather than guessing.

Cancellation and configuration changes

Users must be able to disable automatic redemption before dispatch.

If the feature is disabled before the consume request is sent:

cancel pending automatic redemption

Likewise, removing an account or disabling its per-account policy must invalidate its pending scheduler entry.

Once the irreversible consume request has already been dispatched, OpenCodex cannot pretend it was cancelled.

Record the actual outcome truthfully.

Account isolation

Automatic redemption state is account-scoped.

A credit discovered for:

Account A

must never authorize a redemption for:

Account B

even if both accounts expose:

  • the same number of credits;
  • similar expiry timestamps;
  • similar quota state.

Use a safe internal account identity and credit-generation identity.

Do not infer equivalence from display labels.

Multiple credits / generations

If the upstream can expose several credits or successive credit generations, scheduling must identify exactly which logical credit is being protected from expiry.

Do not collapse:

credit count > 0

into sufficient authority to consume arbitrarily.

If the upstream contract does not provide enough identity to safely distinguish generations, define a conservative deduplication model before enabling automatic spending.

Clock behavior

Local wall-clock behavior must not create duplicate actions.

The scheduler should tolerate:

  • sleep/resume;
  • clock adjustments;
  • delayed event-loop execution;
  • restart after the planned dispatch time.

A timer waking late should perform the same authoritative revalidation rather than assuming the credit remains redeemable.

If the recorded expiry is already past:

do not blindly redeem

Refresh first and follow authoritative state.

Auditability

Every automatic decision should emit a bounded, sanitized audit event.

Useful events include:

auto_redeem_scheduled
auto_redeem_cancelled
auto_redeem_skipped
auto_redeem_dispatched
auto_redeem_completed
auto_redeem_failed

Useful bounded fields may include:

safe account reference
credit generation reference
scheduled time
observed expiry
lead time
final outcome
reason for skip/cancellation

Do not log:

  • OAuth access tokens;
  • refresh tokens;
  • authorization headers;
  • raw credential material;
  • full upstream responses;
  • unsafe account identifiers.

Management surfaces

Backend lifecycle semantics and tests should land before the Dashboard toggle.

Once the scheduler contract is proven, expose an opt-in control such as:

Auto-redeem reset credits before expiry
[ Off / On ]

Optionally support:

Redeem [10] minutes before expiry

if configurable lead time is part of the accepted backend policy.

The UI should make the irreversible nature explicit:

When enabled, OpenCodex may automatically consume a reset credit shortly before it expires.

Users should be able to inspect whether an automatic redemption is currently scheduled and cancel it before dispatch.

Global versus per-account policy

If both global and per-account controls are supported, precedence must be explicit.

For example:

per-account override
        ↓
global default

Do not let ambiguous configuration silently enable automatic spending for an account.

The safest initial implementation may use one explicit account-scoped or global opt-in contract before adding inheritance.

Relationship to #657

#657 owns rejection-triggered reset-credit recovery.

Its trigger is:

verified pre-stream quota exhaustion

This issue owns:

credit nearing expiry

The two features may share low-level reset-credit primitives and idempotency infrastructure, but their policy triggers and lifecycle are different.

Do not merge the two scheduler/state machines merely because both can eventually invoke the same consume endpoint.

Relationship to #823

#823 tracks optional activity after a natural quota reset to establish the next quota window.

That is separate from protecting an unused reset credit from expiring.

Automatic redemption here must not automatically activate an account afterward unless a separately accepted policy explicitly composes the two features.

Relationship to #1146

#1146 owns cooldown-expiry/recovery notifications.

A local rate-limit cooldown expiring is not authority to consume a reset credit.

Out of scope

This issue does not own:

Suggested implementation slices

Do not deliver scheduler, irreversible consume logic, Dashboard UI, and documentation as one large PR.

A safer sequence is:

  1. lifecycle/state model and deterministic tests;
  2. durable deduplication + idempotent single-flight execution;
  3. scheduler and restart/crash recovery;
  4. management API/configuration;
  5. Dashboard control and audit presentation;
  6. documentation and end-to-end acceptance coverage.

Acceptance criteria

Existing foundation

  • Reset-credit state can be inspected.
  • Expiry information can be surfaced where available.
  • Manual reset-credit redemption exists.

Remaining lifecycle

  • Automatic redemption is explicit opt-in and defaults off.
  • A safe pre-expiry lead-time policy exists.
  • Missing or stale expiry data never authorizes redemption.
  • Authoritative credit state is refreshed immediately before dispatch.
  • Scheduled work is bound to a specific account and credit generation.
  • Changed credit generation invalidates stale scheduled work.
  • One idempotency identity is used per logical automatic redemption.
  • Only one redemption can run per account/credit generation.
  • Manual and automatic redemption races are safe.
  • Restart/crash recovery cannot duplicate credit consumption.
  • Late timers and sleep/resume are handled safely.
  • Disabling the feature cancels undispatched work.
  • Removing/disabling an account invalidates pending work.
  • Irreversible dispatched operations report their real outcome.
  • Audit events are bounded and contain no credential material.
  • Backend lifecycle tests exist before GUI enablement.
  • Dashboard/API users can inspect and disable the feature once the backend contract is proven.
  • Accepted implementation is merged into dev.

Related

Activity

  1. github-actions commented on Jul 31, 2026

    @github-actions
    Contributor

    Automated translation bookkeeping — detected language: English.

  2. added
    account-poolOAuth, credentials, Codex pool, quota, failover, plans
    on Aug 1, 2026
  3. Ingwannu commented on Aug 1, 2026

    @Ingwannu
    Owner

    Accepted as a distinct design item from rejection-triggered recovery in #657, but the scheduler is not a small extension of the existing manual button.

    Any implementation must remain default-off and use a fresh authoritative credit balance and expiry immediately before dispatch. It also needs a safe lead time (redeeming at the recorded expiry may already be too late), one idempotency key and single-flight operation per account/credit generation, restart/crash deduplication, cancellation rules, and a redacted audit record. Stale or missing expiry data must never spend a credit.

    Please start with a backend policy/lifecycle proposal and tests before adding the GUI toggle. Keeping this open for that design.

  4. Wibias commented on Aug 7, 2026

    @Wibias
    Contributor

    Related quota automation

  5. changed the title [-][Feature] Auto-redeem reset credits before they expire[/-] [+][Feature]: opt-in crash-safe reset-credit auto-redemption before expiry[/+] on Aug 9, 2026
  6. github-actions commented on Aug 9, 2026

    @github-actions
    Contributor

    Maintainer decision respected

    A maintainer has reopened this issue. The automated closure has been deactivated.

  7. reopened this on Aug 9, 2026
  8. lidge-jun commented on Aug 19, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 59 / 80

    현재 dev에는 리셋 크레딧 잔액/만료 표시와 수동 소모가 있습니다. GET/POST /api/codex-auth/reset-credits와 ocx account reset-credits --consume --yes가 그 경로입니다. resetCreditAutoRedeem, leadTimeMinutes, 만료 전 스케줄러는 소스에서 검색되지 않습니다. 백그라운드가 수동 버튼을 누르는 구현은 없습니다.

    이슈가 원하는 것은 만료가 다가올 때만, 옵트인일 때만, 만료 직전에 한 장을 쓰는 것입니다. 트리거는 업스트림 쿼터 거절(#657)이 아니라 시간입니다. 디스패치 직전에 권위 있는 잔액을 다시 읽고, 계정·미사용·알려진 만료·같은 세대·여전히 켜짐이 모두 참일 때만 소모해야 합니다. 하나라도 모호하면 쓰지 않습니다.

    네이티브 신뢰성이 점수입니다. 크레딧은 희소하고 되돌릴 수 없습니다. 기록된 만료 시각에 맞추면 업스트림은 이미 만료로 볼 수 있어 리드 타임이 필요합니다. 옛 타이머가 새 세대 크레딧을 쓰면 안 됩니다. Windows/서비스 재시작은 타이머를 권위로 두면 중복 소모가 납니다. 프로세스 공유 단일 비행과 세대 키가 필요합니다.

    위생적으로 기본값은 꺼짐입니다. 만료 시각만 캐시하고 소모 전에 재검증하지 않으면 fail-open이 됩니다. #657의 거절-트리거 복구, #823의 자연 리셋 후 활성화와 스케줄러를 합치면 안 됩니다.

    해결방안은 기본 꺼짐 설정, 세대에 묶인 후보 스케줄, 디스패치 직전 재조회, 한 계정·한 세대에 하나의 멱등 키입니다. 기존 consume API를 타이머가 직접 부르지 말고 그 검증을 감싸야 합니다. 패치는 추측하지 않으며, 현재 트리에 자동 상환 스케줄러는 없습니다.

    이 댓글은 grok-bot이 작성했습니다

  9. lidge-jun commented on Sep 1, 2026

    @lidge-jun
    Owner

    Landed on dev via #3219 (squash 519bba745).

    "resetCreditAutoRedeem": { "enabled": true, "leadTimeMinutes": 10 } in config.json turns it on; default is off and a default install starts nothing. The proxy re-reads the main account's reset credits, schedules the soonest-expiring one at expires_at minus the lead (sleeps are capped at 15 minutes so a suspended laptop re-checks instead of trusting a stale plan), re-reads once more right before spending and skips when that exact credit is gone — which is what a manual redeem looks like — and journals the redeem_request_id to $OPENCODEX_HOME/reset-credit-auto-redeem.json before the consume call, so a crash or an uncertain response replays the same idempotent request instead of spending a second credit. Logs carry a hashed account key only. Dashboard toggle and per-account overrides are reasonable follow-ups.

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, plansenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions