Skip to content

feat(codex): coordinate reset-credit recovery attempts - #1410

Draft
luvs01 wants to merge 1 commit into
lidge-jun:devfrom
luvs01:agent/reset-credit-recovery-coordinator
Draft

feat(codex): coordinate reset-credit recovery attempts#1410
luvs01 wants to merge 1 commit into
lidge-jun:devfrom
luvs01:agent/reset-credit-recovery-coordinator

Conversation

@luvs01

@luvs01 luvs01 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add a process-shared, coordinator-only foundation for reset-credit recovery attempts
  • bind recovery to verified pre-stream 402/429 exhaustion, live output-commit guards, and an exact account/credential/exhaustion generation
  • reuse one UUIDv4 per logical turn, single-flight concurrent attempts, and retain monotonic terminal fences for deterministic outcomes
  • fail closed on cancellation, timeout, malformed adapter results, noncanonical account IDs, and bounded process-state capacity

This PR intentionally does not wire automatic redemption or request replay into Responses, change the manual consume route, add account selection, or expose configuration/UI. It is a Draft foundation for independently proving the irreversible-operation invariants requested in #657.

The coordinator is process-local. A future runtime adapter must persist and reuse the same operation identity across uncertain transport outcomes, echo that identity from consume results, bind it to the account generation, advance the main-account identity epoch, and keep the output-exposure guard monotonic.

Refs #657.

Verification

  • Bun 1.4.0-canary.1: bun test --isolate tests/codex-reset-credit-recovery.test.ts — 34 passed, 0 failed
  • Bun 1.3.14: bun test --isolate tests/codex-reset-credit-recovery.test.ts — 34 passed, 0 failed
  • Bun 1.4.0-canary.1: bun x --package typescript@7.0.2 tsc --noEmit — passed
  • Bun 1.3.14: bun x --package typescript@7.0.2 tsc --noEmit — passed
  • Bun 1.4.0-canary.1: bun run privacy:scan — passed
  • git diff --cached --check — passed
  • two independent focused reviews found no actionable P0–P2 findings
  • an earlier Bun 1.4 full-suite attempt completed with 10,151 passed, 329 failed, and 16 errors; the then-current 30 coordinator tests passed, while broader Windows cleanup/privilege, missing GUI setup, and isolated Bun 1.4 compatibility failures kept the suite non-green. The final 34-test coordinator file was rerun focused on both Bun versions above.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed. The internal adapter and process-local limitations are documented inline; there is no user-facing runtime behavior yet.
  • Security-sensitive changes were reviewed for secrets, auth, unsafe defaults, idempotency, and cancellation.

Review readiness checklist

This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:

  • All CI tests are green on my local testing.
  • I pushed my PR to the latest dev commit.
  • I resolved all correct Codex and CodeRabbit findings.
  • My PR is ready for review.

Summary by CodeRabbit

  • New Features

    • Added guarded recovery for eligible credit-reset failures.
    • Recovery now validates account state and credit availability before retrying.
    • Added safeguards for duplicate attempts, stale account generations, cancellation, timeouts, and concurrent recovery requests.
    • Added bounded retries and clear outcomes for successful, cancelled, failed, expired, and unavailable recovery operations.
  • Bug Fixes

    • Prevents recovery actions from being dispatched when account state is invalid or outdated.
    • Improves handling of ambiguous or malformed recovery responses.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds CodexResetCreditRecoveryCoordinator and public recovery types. The coordinator validates generations and authorization, shares process-wide flights, bounds retries and deadlines, handles cancellation and output exposure, records terminal outcomes, and limits state. Tests cover the recovery lifecycle and edge cases.

Changes

Reset-credit recovery

Layer / File(s) Summary
Recovery contracts and validation
src/codex/reset-credit-recovery.ts, tests/codex-reset-credit-recovery.test.ts
Adds public recovery contracts, input normalization, consume-result mapping, coordinator construction, logical-turn creation, and tests for authorization, generations, operation IDs, and malformed revalidation.
Single-flight and waiter lifecycle
src/codex/reset-credit-recovery.ts, tests/codex-reset-credit-recovery.test.ts
Adds per-turn idempotency, process-wide flight sharing, capacity limits, cancellation handling, output-exposure checks, waiter detachment, and active-flight cleanup.
Deadline-bound consume dispatch
src/codex/reset-credit-recovery.ts, tests/codex-reset-credit-recovery.test.ts
Adds pre-dispatch revalidation, operation deadlines, bounded consume retries with one operation ID, and explicit dispatch outcomes.
Terminal outcomes and generation fencing
src/codex/reset-credit-recovery.ts, tests/codex-reset-credit-recovery.test.ts
Adds terminal-generation caching, stale-generation rejection, supersession fences, ambiguous-result quarantine, and process-state reset tests.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • lidge-jun/opencodex#866: Provides reset-eligible quota classification signals consumed by this recovery coordinator.
  • lidge-jun/opencodex#955: Implements related cooldown recovery probing with generation validation and single-flight coordination.

Suggested reviewers: ingwannu, lidge-jun, wibias

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: coordinating reset-credit recovery attempts.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the enhancement New feature or request label Aug 10, 2026
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • review readiness checklist open (3/4 boxes ticked).

What to do

  • Tick all four boxes in the PR description once you're done (currently 3/4).

Review readiness checklist

  • ✅ All CI tests are green on my local testing.
  • ✅ I pushed my PR to the latest dev commit.
  • ⬜ I resolved all correct Codex and CodeRabbit findings.
  • ✅ My PR is ready for review.

3/4 boxes ticked.

This PR stays in draft until every box above is ticked.

@luvs01
luvs01 force-pushed the agent/reset-credit-recovery-coordinator branch from d6da400 to 9ce9b26 Compare August 10, 2026 08:19
@luvs01

luvs01 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/codex/reset-credit-recovery.ts`:
- Around line 439-447: Gate or make internal the resetProcessStateForTests
helper and its process registries so production code cannot clear
terminalByAccount; update src/codex/reset-credit-recovery.ts lines 439-447
accordingly. In tests/codex-reset-credit-recovery.test.ts lines 880-916, split
the post-reset confirmed scenario into a separate test and perform the reset in
beforeEach instead of mid-test.
- Around line 403-421: Update the static active-flight coordination around
CodexResetCreditRecoveryCoordinator so flights are not shared across
coordinators with different dependency sets or limits; include a
per-dependency-set identity in the flight key, or validate identity and limits
when joining and return a dedicated not-dispatched reason on mismatch. In
tests/codex-reset-credit-recovery.test.ts lines 315-359, give each coordinator
distinct consume implementations and assert which one executes to verify the
selected contract.
- Around line 320-322: Update hasFlightCapacity to emit an operational signal
whenever capacity is rejected, such as a log line or counter containing only the
current saturation count. Ensure the signal is triggered on the false path for
the MAX_TRACKED_RECOVERY_ACCOUNTS limit and never includes accountId. Preserve
the existing fail-closed not-dispatched/recovery-state-capacity behavior.
- Around line 145-148: Import CodexResetEligibleExhaustionCode from
quota-rejection.ts, type RESET_ELIGIBLE_CODES as
ReadonlySet<CodexResetEligibleExhaustionCode>, and cast value.semanticCode to
that type in authorizedResetRejection when calling has. Preserve the existing
eligible code values while making changes to the shared union produce
compile-time drift errors.
- Around line 546-572: Add a deadline timer field to RecoveryFlight, assign the
setTimeout handle from runFlightWithDeadline to that field, and clear it during
resetProcessStateForTests alongside the aborts and registry cleanup. Preserve
the existing finally cleanup and ensure the reset handles flights with pending
timers.

In `@tests/codex-reset-credit-recovery.test.ts`:
- Around line 405-410: Export MAX_TRACKED_RECOVERY_ACCOUNTS and
MAX_TRACKED_RECOVERY_FLIGHTS from the reset-credit recovery module, then update
both affected tests to derive loop bounds, expected consume-call counts,
terminal-generation counts, flight counts, and exhaustionGeneration from the
appropriate exported constants instead of literal 128 values. Preserve the
overflow assertions by using the constants consistently for capacity and the
subsequent over-capacity case.
- Around line 315-359: Update the concurrent-flight test around makeCoordinator
and consume so firstCoordinator and secondCoordinator use distinguishable
consume adapters that record which coordinator executed. Keep shared revalidate
behavior, then assert the recorded executedBy value explicitly alongside the
existing single-flight assertions, documenting the implementation’s intended
adapter ownership contract.
- Around line 749-752: Replace the single Promise.resolve microtask drain in the
late transport rejection test with the coordinator’s idle synchronization, such
as waitForIdleForTests, so execution has reached the retry decision before
asserting consumeCalls is 1. Ensure the idle signal actually tracks the captured
flight or otherwise resolves only after the consume loop exits, since the flight
may be removed from allFlights by its finally handler.
- Around line 774-784: Update the exact consume-outcome test.each block so each
case uses a unique suffixed accountId, following the pattern used by the other
multi-case tests, and pass that accountId when creating the logical turn or
coordinator input as appropriate. Confirm the generated identifiers satisfy
CODEX_ACCOUNT_ID_RE, while preserving the existing expected outcome assertions.
- Around line 176-201: Add focused negative recovery cases near the existing
`generic` and `wrongStatus` cases: one must preserve an otherwise authorized
rejection while setting `resetCreditEligible: false`, and another must preserve
valid kind/status and eligibility while using an unrecognized `semanticCode`.
Assert both results equal `generic` alongside the existing recovery assertions,
isolating each gate in `authorizedResetRejection`.
- Around line 133-146: Update the test to import and use MAIN_CODEX_ACCOUNT_ID
instead of hardcoding "__main__" in the accepted account-id case, ensuring it
follows the production contract. Extend the rejected account-id cases with
"__PROTO__" to verify case-insensitive handling of reserved identifiers while
retaining the existing canonical-format rejection coverage.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4e8adcb2-4dd6-48d5-a1ef-4cd319e0a8e5

📥 Commits

Reviewing files that changed from the base of the PR and between 0de4fd2 and 9ce9b26.

📒 Files selected for processing (2)
  • src/codex/reset-credit-recovery.ts
  • tests/codex-reset-credit-recovery.test.ts

Comment on lines +145 to +148
const RESET_ELIGIBLE_CODES: ReadonlySet<string> = new Set([
"usage_limit_exceeded",
"insufficient_quota",
]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Confirm the CodexResetEligibleExhaustionCode union members and export status.
set -euo pipefail

fd -t f 'quota-rejection.ts' src | while IFS= read -r file; do
  echo "== $file =="
  rg -n -C 4 'CodexResetEligibleExhaustionCode' "$file"
done

# Find every other place that hardcodes the same semantic codes.
rg -n 'usage_limit_exceeded|insufficient_quota' --type=ts

Repository: lidge-jun/opencodex

Length of output: 2419


🏁 Script executed:

#!/bin/bash
set -u

echo "== candidate files =="
git ls-files 'src/codex/reset-credit-recovery.ts' 'src/codex/quota-rejection.ts'

echo
echo "== reset-credit-recovery relevant declarations/usages =="
sed -n '1,270p' src/codex/reset-credit-recovery.ts

echo
echo "== quota-rejection exact section =="
sed -n '1,25p' src/codex/quota-rejection.ts

Repository: lidge-jun/opencodex

Length of output: 10324


Import and enforce CodexResetEligibleExhaustionCode for RESET_ELIGIBLE_CODES.

src/codex/reset-credit-recovery.ts:145-148 defines an invariant set against "usage_limit_exceeded" | "insufficient_quota" by copying values instead of using the shared src/codex/quota-rejection.ts:3-9 union. If CodexResetEligibleExhaustionCode changes, authorizedResetRejection at src/codex/reset-credit-recovery.ts:221 can silently exclude the new code and return not-dispatched / ineligible-rejection. Import the type from quota-rejection.ts, make RESET_ELIGIBLE_CODES typed as ReadonlySet<CodexResetEligibleExhaustionCode>, and cast value.semanticCode at the has call so drift is a compile error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/reset-credit-recovery.ts` around lines 145 - 148, Import
CodexResetEligibleExhaustionCode from quota-rejection.ts, type
RESET_ELIGIBLE_CODES as ReadonlySet<CodexResetEligibleExhaustionCode>, and cast
value.semanticCode to that type in authorizedResetRejection when calling has.
Preserve the existing eligible code values while making changes to the shared
union produce compile-time drift errors.

Comment on lines +320 to +322
private static readonly activeFlights = new Map<string, RecoveryFlight>();
private static readonly allFlights = new Set<RecoveryFlight>();
private static readonly terminalByAccount = new Map<string, TerminalGeneration>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔵 Trivial

terminalByAccount never evicts, so the 128-account cap is permanent for the process lifetime.

terminalByAccount is only cleared by resetProcessStateForTests. hasFlightCapacity at Lines 453-456 counts terminal accounts plus in-flight accounts against MAX_TRACKED_RECOVERY_ACCOUNTS. In a long-lived server with more than 128 distinct pool accounts, the registry fills permanently. Every subsequent new account then receives not-dispatched / recovery-state-capacity forever, with no log line and no metric.

The behavior fails closed, which is correct for an irreversible operation. The operational gap is that it is silent and unrecoverable without a process restart.

Before this coordinator is wired to the runtime adapter, add one of the following:

  • Time-based or LRU eviction for terminalByAccount entries, keyed on the recorded generation.
  • A counter or log line when hasFlightCapacity returns false, so operators can detect saturation.

Do not log the accountId value itself in that signal; emit a count only.

Also applies to: 449-457

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/reset-credit-recovery.ts` around lines 320 - 322, Update
hasFlightCapacity to emit an operational signal whenever capacity is rejected,
such as a log line or counter containing only the current saturation count.
Ensure the signal is triggered on the false path for the
MAX_TRACKED_RECOVERY_ACCOUNTS limit and never includes accountId. Preserve the
existing fail-closed not-dispatched/recovery-state-capacity behavior.

Comment on lines +403 to +421
let flight = CodexResetCreditRecoveryCoordinator.activeFlights.get(key);
if (flight
&& !flight.dispatchStarted
&& (flight.preDispatchAbort.signal.aborted || flight.activeWaiters === 0)) {
turnState.attempt = Promise.resolve(CANCELLED_BEFORE_DISPATCH);
return turnState.attempt;
}
if (!flight) {
if (!CodexResetCreditRecoveryCoordinator.hasFlightCapacity(
generationSnapshot.accountId,
)) {
turnState.attempt = Promise.resolve(notDispatched("recovery-state-capacity"));
return turnState.attempt;
}
flight = this.createFlight(key, generationSnapshot, turn.operationId);
CodexResetCreditRecoveryCoordinator.activeFlights.set(key, flight);
}

turnState.attempt = this.joinFlight(flight, signals, authorization.isOutputExposed);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

The static flight key omits coordinator identity, so a joining instance silently runs another instance's adapters and limits. activeFlights is static and the key at Line 383 contains only accountId, credentialGeneration, and exhaustionGeneration. A flight created by one instance closes over that instance's dependencies, operationTimeoutMs, and maxConsumeAttempts. A second instance that joins discards its own configuration for that flight, including its consume adapter for an irreversible credit operation.

  • src/codex/reset-credit-recovery.ts#L403-L421: either include a per-dependency-set identity in the flight key so instances with different adapters never share a flight, or validate the joining instance's dependency identity and limits against the flight and return a dedicated not-dispatched reason on mismatch.
  • tests/codex-reset-credit-recovery.test.ts#L315-L359: both coordinators receive the same revalidate and consume references at Lines 333-340, so the assertions cannot distinguish which instance executed. Give each coordinator a distinguishable consume and assert which one ran, so the chosen contract is pinned.
📍 Affects 2 files
  • src/codex/reset-credit-recovery.ts#L403-L421 (this comment)
  • tests/codex-reset-credit-recovery.test.ts#L315-L359
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/reset-credit-recovery.ts` around lines 403 - 421, Update the static
active-flight coordination around CodexResetCreditRecoveryCoordinator so flights
are not shared across coordinators with different dependency sets or limits;
include a per-dependency-set identity in the flight key, or validate identity
and limits when joining and return a dedicated not-dispatched reason on
mismatch. In tests/codex-reset-credit-recovery.test.ts lines 315-359, give each
coordinator distinct consume implementations and assert which one executes to
verify the selected contract.

Comment on lines +439 to +447
static resetProcessStateForTests(): void {
for (const flight of this.allFlights) {
flight.preDispatchAbort.abort();
flight.operationAbort.abort();
}
this.activeFlights.clear();
this.allFlights.clear();
this.terminalByAccount.clear();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

A public, ungated resetProcessStateForTests erases the only fence against a duplicate irreversible consume. terminalByAccount is the sole record that a given (accountId, credentialGeneration, exhaustionGeneration) tuple already dispatched a consume. resetProcessStateForTests is public static on an exported class, clears that map, and carries no runtime gate. The test body demonstrates the exact sequence.

  • src/codex/reset-credit-recovery.ts#L439-L447: gate the method so production code cannot reach it, or move the process registries and this helper into an internal module that the production entry point does not re-export.
  • tests/codex-reset-credit-recovery.test.ts#L880-L916: after Line 904 clears the fence, the assertion at Line 915 confirms a second consume runs for the same BASE_GENERATION that already recorded an ambiguous terminal outcome. Split the post-reset "confirmed" scenario into its own test so the reset happens in beforeEach, and the suite stops relying on a mid-test call to the fence eraser.
📍 Affects 2 files
  • src/codex/reset-credit-recovery.ts#L439-L447 (this comment)
  • tests/codex-reset-credit-recovery.test.ts#L880-L916
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/reset-credit-recovery.ts` around lines 439 - 447, Gate or make
internal the resetProcessStateForTests helper and its process registries so
production code cannot clear terminalByAccount; update
src/codex/reset-credit-recovery.ts lines 439-447 accordingly. In
tests/codex-reset-credit-recovery.test.ts lines 880-916, split the post-reset
confirmed scenario into a separate test and perform the reset in beforeEach
instead of mid-test.

Comment on lines +546 to +572
private async runFlightWithDeadline(
flight: RecoveryFlight,
): Promise<Awaited<RecoveryFlight["promise"]>> {
let timer: ReturnType<typeof setTimeout> | undefined;
const deadline = new Promise<Awaited<RecoveryFlight["promise"]>>(resolve => {
timer = setTimeout(() => {
flight.expired = true;
if (!flight.dispatchStarted) {
flight.preDispatchAbort.abort();
resolve(notDispatched("operation-expired-before-dispatch"));
return;
}
flight.operationAbort.abort();
resolve(freezeResult({
kind: "ambiguous",
reason: "consume-timeout",
operationId: flight.operationId,
}));
}, this.operationTimeoutMs);
});

try {
return await Promise.race([this.runFlight(flight), deadline]);
} finally {
if (timer !== undefined) clearTimeout(timer);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Look for existing timer-lifetime handling patterns in src.
set -euo pipefail

rg -n -C 3 'setTimeout\(|clearTimeout\(|\.unref\(\)' --type=ts -g 'src/**' | head -60

Repository: lidge-jun/opencodex

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the relevant target file and surrounding code without executing repository code.
if [ -f src/codex/reset-credit-recovery.ts ]; then
  echo "== file exists =="
  wc -l src/codex/reset-credit-recovery.ts
  echo "---- lines 410-585 ----"
  sed -n '410,585p' src/codex/reset-credit-recovery.ts | nl -ba -v410
  echo "---- lines 640-715 ----"
  sed -n '640,715p' src/codex/reset-credit-recovery.ts | nl -ba -v640
else
  echo "missing src/codex/reset-credit-recovery.ts"
  fd -a reset-credit-recovery.ts .
fi

echo "---- RecoveryFlight declarations/usages ----"
rg -n "interface RecoveryFlight|type RecoveryFlight|RecoveryFlight|operationTimeoutMs|MAX_OPERATION_TIMEOUT_MS|resetProcessStateForTests|runFlightWithDeadline|runFlight\\(" --type=ts src/codex/reset-credit-recovery.ts

Repository: lidge-jun/opencodex

Length of output: 278


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path("src/codex/reset-credit-recovery.ts")
if not p.exists():
    print("missing")
    raise SystemExit
text = p.read_text()
checks = {
    "runFlightWithDeadline_has_timer_local": "let timer: ReturnType<typeof setTimeout> | undefined" in text,
    "runFlightWithDeadline_clears_in_finally": "if (timer !== undefined) clearTimeout(timer)" in text,
    "resetProcessState_aborts_flights": "flight.preDispatchAbort.abort()" in text and "flight.operationAbort.abort()" in text,
    "resetProcessState_clears_timers": "clearTimeout" in text and "flight.preDispatchAbort.abort()" in text and text.find("clearTimeout") <= text.find("flight.preDispatchAbort.abort()"),
    "has_unref_pattern": ".unref()" in text or "unref()" in text,
}
for k, v in checks.items():
    print(f"{k}={v}")

# Extract a deterministic structural model based only on text occurrences and line numbers.
for name in ["RecoveryFlight", "resetProcessStateForTests", "runFlightWithDeadline"]:
    idx = text.find(f" {name} ")
    print(f"\n--- first occurrence of {name} at char {idx} ---")
    # Print nearby lines around first occurrence.
    lines = text.splitlines()
    line = next((i+1 for i,l in enumerate(text.splitlines()) if name in l), None)
    if line:
        lo, hi = max(1,line-15), min(len(lines),line+60)
        for i in range(lo,hi+1):
            if "RecoveryFlight" in lines[i-1] or "resetProcessStateForTests" in lines[i-1] or "runFlightWithDeadline" in lines[i-1] or "clearTimeout" in lines[i-1] or "operationTimeoutMs" in lines[i-1]:
                print(f"{i:4}:{lines[i-1]}")
PY

Repository: lidge-jun/opencodex

Length of output: 1161


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Probe Bun timer event-loop semantics and abort-signal promise behavior if Bun is available.
if command -v bun >/dev/null 2>&1; then
  echo "== bun version =="
  bun --version
  echo "== setTimeout keeps loop alive behavior =="
  cat > /tmp/bun_timers_probe.ts <<'JS'
let settled = false;
setTimeout(() => {
  console.log("timer fired");
  settled = true;
}, 10);
setTimeout(() => {
  console.log("second timer", settled);
}, 2;
JS
  (timeout 1s bun /tmp/bun_timers_probe.ts 2>&1 || true)
  echo "== Bun AbortSignal abort behavior =="
  cat > /tmp/bun_abort_probe.ts <<'JS'
const controller = new AbortController();
const signal = controller.signal;
async function ignored() { await new Promise(() => {}); }
const p = ignored();
setTimeout(() => { controller.abort(); }, 10);
setTimeout(() => {
  console.log("aborted", signal.aborted);
}, 30);
setTimeout(() => console.log("settled"), 50);
// No await or catch here; emulate flight promise settling without observing abort.
JS
  (timeout 1s bun /tmp/bun_abort_probe.ts 2>&1 || true)
else
  echo "bun not available"
fi

Repository: lidge-jun/opencodex

Length of output: 175


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path("src/codex/reset-credit-recovery.ts")
if not p.exists():
    print("missing")
    raise SystemExit
text = p.read_text()
checks = {
    "runFlightWithDeadline_has_timer_local": "let timer: ReturnType<typeof setTimeout> | undefined" in text,
    "runFlightWithDeadline_clears_in_finally": "if (timer !== undefined) clearTimeout(timer)" in text,
    "resetProcessState_aborts_flights": "flight.preDispatchAbort.abort()" in text and "flight.operationAbort.abort()" in text,
    "resetProcessState_clears_timers": "clearTimeout" in text and "flight.preDispatchAbort.abort()" in text and text.find("clearTimeout") <= text.find("flight.preDispatchAbort.abort()"),
    "has_unref_pattern": ".unref()" in text or "unref()" in text,
}
for k, v in checks.items():
    print(f"{k}={v}")

for name in ["RecoveryFlight", "resetProcessStateForTests", "runFlightWithDeadline"]:
    print(f"\n--- matches for {name} ---")
    lines = text.splitlines()
    for i, line in enumerate(lines, 1):
        if name in line:
            print(f"{i}:{line}")
PY

Repository: lidge-jun/opencodex

Length of output: 1318


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,130p' src/codex/reset-credit-recovery.ts | cat
sed -n '420,585p' src/codex/reset-credit-recovery.ts | cat
sed -n '574,625p' src/codex/reset-credit-recovery.ts | cat
sed -n '640,710p' src/codex/reset-credit-recovery.ts | cat

Repository: lidge-jun/opencodex

Length of output: 14275


Clear flight timers during test state reset.

In src/codex/reset-credit-recovery.ts, runFlightWithDeadline stores the deadline handle in a local variable and clears it only in the finally block after Promise.race settles. resetProcessStateForTests aborts preDispatchAbort/operationAbort and clears registries but does not clear any pending timers. If revalidate or consume ignore the abort signal, the race can remain pending and keep the Bun event loop alive for up to MAX_OPERATION_TIMEOUT_MS (60s) until the timeout fires.

Add a timer field to RecoveryFlight, assign it in runFlightWithDeadline, and clear it in resetProcessStateForTests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/reset-credit-recovery.ts` around lines 546 - 572, Add a deadline
timer field to RecoveryFlight, assign the setTimeout handle from
runFlightWithDeadline to that field, and clear it during
resetProcessStateForTests alongside the aborts and registry cleanup. Preserve
the existing finally cleanup and ensure the reset handles flights with pending
timers.

Source: Path instructions

Comment on lines +176 to +201
const generic = await recover(
coordinator,
coordinator.createLogicalTurn(),
BASE_GENERATION,
{},
{
enabled: true,
isOutputExposed: () => false,
rejection: {
kind: "generic-rate-limit",
status: 429,
alternateRetryEligible: true,
resetCreditEligible: false,
},
},
);
const wrongStatus = await recover(
coordinator,
coordinator.createLogicalTurn(),
BASE_GENERATION,
{},
{
...AUTHORIZATION,
rejection: { ...AUTHORIZATION.rejection, status: 500 },
},
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add negative cases that isolate the resetCreditEligible and semanticCode gates.

authorizedResetRejection in src/codex/reset-credit-recovery.ts applies five independent conditions at Lines 216-221. The current suite does not isolate two of them.

  • Line 219 checks resetCreditEligible === true. The generic case at Lines 176-191 sets resetCreditEligible: false, but it also changes kind to "generic-rate-limit" and drops semanticCode. Three conditions fail at once. If Line 219 were deleted, this test would still pass.
  • Line 221 checks membership in RESET_ELIGIBLE_CODES. No case supplies a valid kind and status with an unrecognized semanticCode. If Line 221 were deleted, the whole suite would still pass.

Both gates authorize an irreversible credit consume. Each needs a case that fails exactly one condition.

As per path instructions for tests/**: "A behavior change in src/ should come with a focused regression test near the existing tests for that subsystem."

♻️ Proposed additional cases
     const wrongStatus = await recover(
       coordinator,
       coordinator.createLogicalTurn(),
       BASE_GENERATION,
       {},
       {
         ...AUTHORIZATION,
         rejection: { ...AUTHORIZATION.rejection, status: 500 },
       },
     );
+    const notResetCreditEligible = await recover(
+      coordinator,
+      coordinator.createLogicalTurn(),
+      BASE_GENERATION,
+      {},
+      {
+        ...AUTHORIZATION,
+        rejection: { ...AUTHORIZATION.rejection, resetCreditEligible: false },
+      },
+    );
+    const unknownSemanticCode = await recover(
+      coordinator,
+      coordinator.createLogicalTurn(),
+      BASE_GENERATION,
+      {},
+      {
+        ...AUTHORIZATION,
+        rejection: {
+          ...AUTHORIZATION.rejection,
+          semanticCode: "some_other_code" as never,
+        },
+      },
+    );

Then assert both equal generic alongside Lines 222-223.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/codex-reset-credit-recovery.test.ts` around lines 176 - 201, Add
focused negative recovery cases near the existing `generic` and `wrongStatus`
cases: one must preserve an otherwise authorized rejection while setting
`resetCreditEligible: false`, and another must preserve valid kind/status and
eligibility while using an unrecognized `semanticCode`. Assert both results
equal `generic` alongside the existing recovery assertions, isolating each gate
in `authorizedResetRejection`.

Source: Path instructions

Comment on lines +315 to +359
test("single-flights concurrent turns process-wide for the same generation", async () => {
const gate = deferred<CodexResetCreditRevalidationResult>();
const started = deferred<void>();
let revalidationCalls = 0;
let consumeCalls = 0;
const seenOperationIds: string[] = [];
const revalidate = async () => {
revalidationCalls += 1;
started.resolve();
return await gate.promise;
};
const consume: CodexResetCreditRecoveryDependencies["consume"] = async ({
operationId,
}) => {
consumeCalls += 1;
seenOperationIds.push(operationId);
return { code: "reset", operationId };
};
const firstCoordinator = makeCoordinator({
revalidate,
consume,
});
const secondCoordinator = makeCoordinator({
revalidate,
consume,
});

const firstTurn = firstCoordinator.createLogicalTurn();
const secondTurn = secondCoordinator.createLogicalTurn();
const first = recover(firstCoordinator, firstTurn);
const second = recover(secondCoordinator, secondTurn);
await started.promise;
gate.resolve(eligible(BASE_GENERATION));

expect(await Promise.all([first, second])).toEqual([
{ kind: "refresh-required", code: "reset" },
{ kind: "refresh-required", code: "reset" },
]);
expect(revalidationCalls).toBe(1);
expect(consumeCalls).toBe(1);
expect(secondTurn.operationId).not.toBe(firstTurn.operationId);
expect(seenOperationIds).toEqual([firstTurn.operationId]);
expect(firstCoordinator.activeFlightCountForTests()).toBe(0);
expect(firstCoordinator.terminalGenerationCountForTests()).toBe(1);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

This test cannot detect which instance's dependencies the shared flight uses.

The test constructs firstCoordinator and secondCoordinator at Lines 333-340, but passes the identical revalidate and consume function references to both. Both also use the default operationTimeoutMs and maxConsumeAttempts.

The assertions at Lines 353-356 therefore hold no matter which instance owns the flight. The test proves that a flight is shared. It does not prove which coordinator's adapters ran. That distinction is the substance of the finding on src/codex/reset-credit-recovery.ts Lines 403-421.

Give each coordinator a distinguishable consume and assert which one executed.

As per path instructions for tests/**: "Flag PRs that change shared routing, adapters, config, or server behavior without touching tests."

♻️ Proposed discriminating assertion
+    const executedBy: string[] = [];
     const firstCoordinator = makeCoordinator({
       revalidate,
-      consume,
+      consume: async input => {
+        executedBy.push("first");
+        return await consume(input);
+      },
     });
     const secondCoordinator = makeCoordinator({
       revalidate,
-      consume,
+      consume: async input => {
+        executedBy.push("second");
+        return await consume(input);
+      },
     });

Then assert executedBy explicitly. Whichever value the implementation produces, record it as the documented contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/codex-reset-credit-recovery.test.ts` around lines 315 - 359, Update the
concurrent-flight test around makeCoordinator and consume so firstCoordinator
and secondCoordinator use distinguishable consume adapters that record which
coordinator executed. Keep shared revalidate behavior, then assert the recorded
executedBy value explicitly alongside the existing single-flight assertions,
documenting the implementation’s intended adapter ownership contract.

Source: Path instructions

Comment on lines +405 to +410
for (let index = 0; index < 128; index += 1) {
expect(await recover(coordinator, coordinator.createLogicalTurn(), {
...BASE_GENERATION,
accountId: `capacity-${index}`,
})).toEqual({ kind: "refresh-required", code: "reset" });
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive the capacity bounds from exported constants instead of the literal 128.

Line 405 loops to 128. Line 419 asserts consumeCalls === 128. Line 420 asserts terminalGenerationCountForTests() === 128. Line 439 builds 128 flights and Line 446 uses exhaustionGeneration: 128.

These literals mirror MAX_TRACKED_RECOVERY_ACCOUNTS and MAX_TRACKED_RECOVERY_FLIGHTS in src/codex/reset-credit-recovery.ts Lines 125-126. Neither constant is exported, so the compiler cannot link them. If a maintainer lowers either limit, these tests fail with an off-by-N mismatch that does not name the constant that moved. If a maintainer raises either limit, the overflow assertions at Lines 412-418 and 444-450 stop testing overflow and instead assert a successful dispatch, so the tests fail for the opposite reason.

Export the two constants and compute the loop bounds from them.

♻️ Proposed fix

In src/codex/reset-credit-recovery.ts:

-const MAX_TRACKED_RECOVERY_ACCOUNTS = 128;
-const MAX_TRACKED_RECOVERY_FLIGHTS = 128;
+export const MAX_TRACKED_RECOVERY_ACCOUNTS = 128;
+export const MAX_TRACKED_RECOVERY_FLIGHTS = 128;

In this test file:

-    for (let index = 0; index < 128; index += 1) {
+    for (let index = 0; index < MAX_TRACKED_RECOVERY_ACCOUNTS; index += 1) {
-    const pending = Array.from({ length: 128 }, (_, exhaustionGeneration) => recover(
+    const pending = Array.from(
+      { length: MAX_TRACKED_RECOVERY_FLIGHTS },
+      (_, exhaustionGeneration) => recover(

Update the remaining 128 assertions in both tests to reference the same constants.

Also applies to: 439-450

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/codex-reset-credit-recovery.test.ts` around lines 405 - 410, Export
MAX_TRACKED_RECOVERY_ACCOUNTS and MAX_TRACKED_RECOVERY_FLIGHTS from the
reset-credit recovery module, then update both affected tests to derive loop
bounds, expected consume-call counts, terminal-generation counts, flight counts,
and exhaustionGeneration from the appropriate exported constants instead of
literal 128 values. Preserve the overflow assertions by using the constants
consistently for capacity and the subsequent over-capacity case.

Comment on lines +749 to +752
firstAttempt.reject(new Error("late transport rejection"));
await firstAttemptSettled.promise;
await Promise.resolve();
expect(consumeCalls).toBe(1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

The microtask drain is too weak to prove that no late retry was launched.

Line 750 awaits firstAttemptSettled.promise. The test's own finally at Lines 736-738 resolves that deferred before the rejection propagates to the coordinator. Line 751 then yields exactly one microtask.

In src/codex/reset-credit-recovery.ts, the rejection must travel from the await at Line 616 into the catch at Line 629, reach the flight.expired check at Line 630, and return at Line 631. That is more than one microtask hop.

The assertion at Line 752 is consumeCalls === 1, meaning "no second call yet". If the drain is too short, the assertion passes without ever reaching the retry decision point. The test cannot fail from an insufficient drain, so it silently under-verifies the guard it is named for.

Await the coordinator's own idle signal instead of counting microtasks.

♻️ Proposed fix
     firstAttempt.reject(new Error("late transport rejection"));
     await firstAttemptSettled.promise;
-    await Promise.resolve();
+    await coordinator.waitForIdleForTests();
     expect(consumeCalls).toBe(1);

waitForIdleForTests uses Promise.allSettled over allFlights. The flight is already removed from allFlights by the finally at Line 538, so also consider capturing the flight promise before the deadline settles, or add an explicit drain helper that resolves after the consume loop exits.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/codex-reset-credit-recovery.test.ts` around lines 749 - 752, Replace
the single Promise.resolve microtask drain in the late transport rejection test
with the coordinator’s idle synchronization, such as waitForIdleForTests, so
execution has reached the retry decision before asserting consumeCalls is 1.
Ensure the idle signal actually tracks the captured flight or otherwise resolves
only after the consume loop exits, since the flight may be removed from
allFlights by its finally handler.

Comment on lines +774 to +784
test.each([
["reset", { kind: "refresh-required", code: "reset" }],
["already_redeemed", { kind: "refresh-required", code: "already_redeemed" }],
["nothing_to_reset", { kind: "stopped", code: "nothing_to_reset" }],
["no_credit", { kind: "stopped", code: "no_credit" }],
] as const)("maps the exact %s consume outcome", async (code, expected) => {
const coordinator = makeCoordinator({
consume: async ({ operationId }) => ({ code, operationId }),
});
expect(await recover(coordinator, coordinator.createLogicalTurn())).toEqual(expected);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a per-case accountId so this test.each block does not depend on per-entry beforeEach.

All four test.each entries dispatch against the default BASE_GENERATION, which is a single (accountId, credentialGeneration, exhaustionGeneration) tuple. The first entry writes a terminal fence for "account-a" through src/codex/reset-credit-recovery.ts Lines 522-532. Entries two through four depend on the beforeEach at Lines 79-81 running before every generated entry to clear that fence.

If Bun ever scoped beforeEach per test.each block rather than per generated entry, entries two through four would replay the cached refresh-required / reset outcome and fail. The failure would be loud, not silent, so this is a robustness point rather than a defect.

Every other multi-case test in this file already avoids the dependency by suffixing the account id: Lines 802, 830, and 859. Apply the same pattern here for consistency.

♻️ Proposed fix
-  ] as const)("maps the exact %s consume outcome", async (code, expected) => {
+  ] as const)("maps the exact %s consume outcome", async (code, expected) => {
     const coordinator = makeCoordinator({
       consume: async ({ operationId }) => ({ code, operationId }),
     });
-    expect(await recover(coordinator, coordinator.createLogicalTurn())).toEqual(expected);
+    expect(await recover(coordinator, coordinator.createLogicalTurn(), {
+      ...BASE_GENERATION,
+      accountId: `outcome-${code.replace(/_/g, "-")}`,
+    })).toEqual(expected);
   });

Confirm that the generated account ids satisfy CODEX_ACCOUNT_ID_RE before adopting this exact naming.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test.each([
["reset", { kind: "refresh-required", code: "reset" }],
["already_redeemed", { kind: "refresh-required", code: "already_redeemed" }],
["nothing_to_reset", { kind: "stopped", code: "nothing_to_reset" }],
["no_credit", { kind: "stopped", code: "no_credit" }],
] as const)("maps the exact %s consume outcome", async (code, expected) => {
const coordinator = makeCoordinator({
consume: async ({ operationId }) => ({ code, operationId }),
});
expect(await recover(coordinator, coordinator.createLogicalTurn())).toEqual(expected);
});
test.each([
["reset", { kind: "refresh-required", code: "reset" }],
["already_redeemed", { kind: "refresh-required", code: "already_redeemed" }],
["nothing_to_reset", { kind: "stopped", code: "nothing_to_reset" }],
["no_credit", { kind: "stopped", code: "no_credit" }],
] as const)("maps the exact %s consume outcome", async (code, expected) => {
const coordinator = makeCoordinator({
consume: async ({ operationId }) => ({ code, operationId }),
});
expect(await recover(coordinator, coordinator.createLogicalTurn(), {
...BASE_GENERATION,
accountId: `outcome-${code.replace(/_/g, "-")}`,
})).toEqual(expected);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/codex-reset-credit-recovery.test.ts` around lines 774 - 784, Update the
exact consume-outcome test.each block so each case uses a unique suffixed
accountId, following the pattern used by the other multi-case tests, and pass
that accountId when creating the logical turn or coordinator input as
appropriate. Confirm the generated identifiers satisfy CODEX_ACCOUNT_ID_RE,
while preserving the existing expected outcome assertions.

@luvs01
luvs01 marked this pull request as ready for review August 10, 2026 08:42
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@github-actions[bot] Acknowledged. The pull request is marked ready for review. The prior CodeRabbit review request has completed.

You are interacting with an AI system.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

@github-actions[bot] Acknowledged. The pull request is ready for review. I will review the current changes.

⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

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: 9ce9b2685d

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (kind === "no-credit") return notDispatched("no-credit-after-revalidate");
if (kind !== "eligible") return notDispatched("invalid-revalidation");

if (![...flight.outputGuards.keys()].some(guard => !outputIsExposed(guard))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Confirm the authorizing waiter survives its guard

When two turns share a flight, a guard can synchronously abort its own waiter during this .some() check, as the re-entrant-guard test establishes. The snapshot still contains that guard, so its returned false short-circuits the search even though onAbort removed it; if another active waiter's output is already exposed, activeWaiters remains nonzero and the coordinator dispatches the irreversible consume with no eligible waiter. After invoking a guard, confirm that it is still registered before treating it as authorization, and cover this concurrent case.

AGENTS.md reference: AGENTS.md:L106-L113

Useful? React with 👍 / 👎.

const finish = (result: Awaited<RecoveryFlight["promise"]>) => {
if (settled) return;
settled = true;
removeAbortListeners();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep abort observation live through the completion guard

At flight completion, abort listeners are removed before outputGuard runs. If that guard re-entrantly aborts the request on its final invocation and returns false, the abort cannot set detachedAfterDispatch, so the raw refresh-required outcome is returned and can authorize replay of a canceled turn. Evaluate the guard while abort observation remains active or explicitly recheck the signals afterward; resolveTerminalOutcome needs the same post-guard check for cached outcomes.

AGENTS.md reference: src/AGENTS.md:L17-L17

Useful? React with 👍 / 👎.

@github-actions
github-actions Bot marked this pull request as draft August 10, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant