Repository navigation
[Feature]: opt-in crash-safe reset-credit auto-redemption before expiry #822
Description
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:
- rejection-triggered failed-turn recovery from [Feature]: opt-in reset-credit recovery after verified pre-stream quota exhaustion #657;
- automatic replay of a failed model request;
- natural-reset waiting while a request remains attached;
- post-reset account activation from [Research] Provider-specific post-reset activation to anchor quota windows #823;
- generic account selection/failover from [Feature]: Generic OAuth account-pool failover with session affinity and quota-aware selection #695;
- cooldown notification from [Feature]: provider/key cooldown recovery notifications #1146;
- spending a credit based on stale cached expiry alone.
Suggested implementation slices
Do not deliver scheduler, irreversible consume logic, Dashboard UI, and documentation as one large PR.
A safer sequence is:
- lifecycle/state model and deterministic tests;
- durable deduplication + idempotent single-flight execution;
- scheduler and restart/crash recovery;
- management API/configuration;
- Dashboard control and audit presentation;
- 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
- [Feature]: opt-in reset-credit recovery after verified pre-stream quota exhaustion #657 — rejection-triggered reset-credit recovery.
- [Research] Provider-specific post-reset activation to anchor quota windows #823 — optional post-reset account activation.
- [Feature]: Generic OAuth account-pool failover with session affinity and quota-aware selection #695 — generic OAuth account-pool runtime.
- [Feature]: provider/key cooldown recovery notifications #1146 — cooldown recovery notifications.
Activity
github-actions commentedon Jul 31, 2026
Automated translation bookkeeping — detected language: English.
Ingwannu commentedon Aug 1, 2026
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.
Wibias commentedon Aug 7, 2026
Related quota automation
[-][Feature] Auto-redeem reset credits before they expire[/-][+][Feature]: opt-in crash-safe reset-credit auto-redemption before expiry[/+]github-actions commentedon Aug 9, 2026
Maintainer decision respected
A maintainer has reopened this issue. The automated closure has been deactivated.
lidge-jun commentedon Aug 19, 2026
리뷰 · 우선순위 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이 작성했습니다
feat(codex): opt-in reset-credit auto-redemption before expiry (#822) (…
lidge-jun commentedon Sep 1, 2026
Landed on
devvia #3219 (squash519bba745)."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 atexpires_atminus 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 theredeem_request_idto$OPENCODEX_HOME/reset-credit-auto-redeem.jsonbefore 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.feat(codex): opt-in reset-credit auto-redemption before expiry (lidge…
feat(codex): opt-in reset-credit auto-redemption before expiry (lidge…