fix(codex): ignore empty rate-limit buckets

This commit is contained in:
Vincent Koc
2026-05-16 02:15:08 +08:00
parent 48b4e5b361
commit 4e10969ade
4 changed files with 86 additions and 2 deletions

View File

@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
- 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.
- TTS: preserve channel-derived voice-note delivery for `/tts audio` replies even when the provider output is not natively voice-compatible. (#82174) Thanks @xuruiray.
- 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.
- 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.
- 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`.
- 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.
- 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.

View File

@@ -148,4 +148,55 @@ describe("summarizeCodexRateLimits", () => {
blockingReason: "Codex usage limit is reached",
});
});
it("ignores metadata-only Codex buckets", () => {
expect(
summarizeCodexRateLimits({
rateLimitsByLimitId: {
codex: {
limitId: "codex",
limitName: "Codex",
primary: null,
secondary: null,
credits: null,
planType: "plus",
rateLimitReachedType: null,
},
},
}),
).toBeUndefined();
});
it("keeps displayable buckets when sibling buckets are empty", () => {
const nowMs = 1_700_000_000_000;
const nowSeconds = nowMs / 1000;
expect(
summarizeCodexRateLimits(
{
rateLimitsByLimitId: {
codex: {
limitId: "codex",
limitName: "Codex",
primary: { usedPercent: 26, windowDurationMins: 300, resetsAt: nowSeconds + 3600 },
secondary: null,
credits: null,
planType: "plus",
rateLimitReachedType: null,
},
"gpt-5.3-codex-spark": {
limitId: "gpt-5.3-codex-spark",
limitName: "GPT 5.3 Codex Spark",
primary: null,
secondary: null,
credits: null,
planType: "plus",
rateLimitReachedType: null,
},
},
},
nowMs,
),
).toBe("Codex: primary 74% left ⏱1h");
});
});

View File

@@ -372,7 +372,8 @@ function formatCodexRateLimitDetails(value: JsonValue | undefined): string {
function summarizeRateLimits(value: JsonValue | undefined): string {
const entries = extractArray(value);
if (entries.length > 0) {
return `${entries.length}`;
const count = entries.filter(isMeaningfulRateLimitSnapshot).length;
return count > 0 ? `${count}` : "none returned";
}
if (!isJsonObject(value)) {
return "none returned";
@@ -388,7 +389,18 @@ function summarizeRateLimits(value: JsonValue | undefined): string {
}
function isMeaningfulRateLimitSnapshot(value: JsonValue | undefined): boolean {
return isJsonObject(value) && Object.values(value).some((entry) => entry != null);
if (!isJsonObject(value)) {
return false;
}
const reachedType =
readString(value, "rateLimitReachedType") ?? readString(value, "rate_limit_reached_type");
if (reachedType) {
return true;
}
return ["primary", "secondary"].some((key) => {
const window = value[key];
return isJsonObject(window) && Object.values(window).some((entry) => entry != null);
});
}
function extractArray(value: JsonValue | undefined): JsonValue[] {

View File

@@ -647,6 +647,17 @@ describe("codex command", () => {
const limits = {
ok: true as const,
value: {
rateLimits: [
{
limitId: "codex",
limitName: "Codex",
primary: null,
secondary: null,
credits: null,
planType: "plus",
rateLimitReachedType: null,
},
],
rateLimitsByLimitId: {
premium: {
limitId: "premium",
@@ -657,6 +668,15 @@ describe("codex command", () => {
planType: "pro",
rateLimitReachedType: null,
},
codex: {
limitId: "codex",
limitName: "Codex",
primary: null,
secondary: null,
credits: null,
planType: "plus",
rateLimitReachedType: null,
},
},
},
};