Skip to content

Commit 4e10969

Browse files
committed
fix(codex): ignore empty rate-limit buckets
1 parent 48b4e5b commit 4e10969

4 files changed

Lines changed: 86 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
2222
- LINE: stop cron recovery from inferring lowercased LINE recipients from canonical session keys, so long-running task replies do not silently retry undeliverable push targets. Fixes #81628. (#81704) Thanks @edenfunf.
2323
- TTS: preserve channel-derived voice-note delivery for `/tts audio` replies even when the provider output is not natively voice-compatible. (#82174) Thanks @xuruiray.
2424
- Codex app-server: preserve inbound sender metadata and source-channel provenance on mirrored user prompts, including failure snapshots, so channel history keeps the original sender identity. (#82184) Thanks @zknicker.
25+
- Codex account/status: treat metadata-only rate-limit buckets as returned but empty so `/codex status` and `/codex account` report `none returned` instead of counting phantom limits.
2526
- Codex/Lossless: keep Codex explicit compaction on native app-server threads while allowing Lossless through the context-engine slot; `openclaw doctor --fix` now migrates legacy `compaction.provider: "lossless-claw"` config to `plugins.slots.contextEngine`.
2627
- Cron/doctor: report scheduled jobs with explicit `payload.model` overrides, including provider namespace counts and default-model mismatches, so stale cron model pins are visible during auth or billing investigations. Fixes #82151. Thanks @mgonto.
2728
- Codex app-server: keep the short turn-completion idle watchdog armed after the last non-assistant current-turn item completes, so a quiet Codex app-server releases the OpenClaw session lane before the outer attempt timeout. Fixes #82171. (#82172) Thanks @funmerlin.

extensions/codex/src/app-server/rate-limits.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,55 @@ describe("summarizeCodexRateLimits", () => {
148148
blockingReason: "Codex usage limit is reached",
149149
});
150150
});
151+
152+
it("ignores metadata-only Codex buckets", () => {
153+
expect(
154+
summarizeCodexRateLimits({
155+
rateLimitsByLimitId: {
156+
codex: {
157+
limitId: "codex",
158+
limitName: "Codex",
159+
primary: null,
160+
secondary: null,
161+
credits: null,
162+
planType: "plus",
163+
rateLimitReachedType: null,
164+
},
165+
},
166+
}),
167+
).toBeUndefined();
168+
});
169+
170+
it("keeps displayable buckets when sibling buckets are empty", () => {
171+
const nowMs = 1_700_000_000_000;
172+
const nowSeconds = nowMs / 1000;
173+
174+
expect(
175+
summarizeCodexRateLimits(
176+
{
177+
rateLimitsByLimitId: {
178+
codex: {
179+
limitId: "codex",
180+
limitName: "Codex",
181+
primary: { usedPercent: 26, windowDurationMins: 300, resetsAt: nowSeconds + 3600 },
182+
secondary: null,
183+
credits: null,
184+
planType: "plus",
185+
rateLimitReachedType: null,
186+
},
187+
"gpt-5.3-codex-spark": {
188+
limitId: "gpt-5.3-codex-spark",
189+
limitName: "GPT 5.3 Codex Spark",
190+
primary: null,
191+
secondary: null,
192+
credits: null,
193+
planType: "plus",
194+
rateLimitReachedType: null,
195+
},
196+
},
197+
},
198+
nowMs,
199+
),
200+
).toBe("Codex: primary 74% left ⏱1h");
201+
});
151202
});

extensions/codex/src/command-formatters.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,8 @@ function formatCodexRateLimitDetails(value: JsonValue | undefined): string {
372372
function summarizeRateLimits(value: JsonValue | undefined): string {
373373
const entries = extractArray(value);
374374
if (entries.length > 0) {
375-
return `${entries.length}`;
375+
const count = entries.filter(isMeaningfulRateLimitSnapshot).length;
376+
return count > 0 ? `${count}` : "none returned";
376377
}
377378
if (!isJsonObject(value)) {
378379
return "none returned";
@@ -388,7 +389,18 @@ function summarizeRateLimits(value: JsonValue | undefined): string {
388389
}
389390

390391
function isMeaningfulRateLimitSnapshot(value: JsonValue | undefined): boolean {
391-
return isJsonObject(value) && Object.values(value).some((entry) => entry != null);
392+
if (!isJsonObject(value)) {
393+
return false;
394+
}
395+
const reachedType =
396+
readString(value, "rateLimitReachedType") ?? readString(value, "rate_limit_reached_type");
397+
if (reachedType) {
398+
return true;
399+
}
400+
return ["primary", "secondary"].some((key) => {
401+
const window = value[key];
402+
return isJsonObject(window) && Object.values(window).some((entry) => entry != null);
403+
});
392404
}
393405

394406
function extractArray(value: JsonValue | undefined): JsonValue[] {

extensions/codex/src/commands.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,17 @@ describe("codex command", () => {
647647
const limits = {
648648
ok: true as const,
649649
value: {
650+
rateLimits: [
651+
{
652+
limitId: "codex",
653+
limitName: "Codex",
654+
primary: null,
655+
secondary: null,
656+
credits: null,
657+
planType: "plus",
658+
rateLimitReachedType: null,
659+
},
660+
],
650661
rateLimitsByLimitId: {
651662
premium: {
652663
limitId: "premium",
@@ -657,6 +668,15 @@ describe("codex command", () => {
657668
planType: "pro",
658669
rateLimitReachedType: null,
659670
},
671+
codex: {
672+
limitId: "codex",
673+
limitName: "Codex",
674+
primary: null,
675+
secondary: null,
676+
credits: null,
677+
planType: "plus",
678+
rateLimitReachedType: null,
679+
},
660680
},
661681
},
662682
};

0 commit comments

Comments
 (0)