From 28d64512651f0c1f6b52661e827077315b1f1a6b Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Fri, 24 Apr 2026 17:22:15 +0000 Subject: [PATCH] feat: add rate_limit_event formatting to printClaudeStreamEvent (FAR-32) rate_limit_event was previously falling through to the debug-only branch and silently dropped in non-debug mode. Now it surfaces a concise, human-readable line for CLI consumers: rate_limit: type=five_hour status=allowed resets=2026-04-22T06:00:00.000Z Two tests cover the exact FAR-32 repro payload and graceful handling of missing rate_limit_info fields. Co-Authored-By: Paperclip --- src/cli/format-event.test.ts | 33 +++++++++++++++++++++++++++++++++ src/cli/format-event.ts | 16 ++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/cli/format-event.test.ts b/src/cli/format-event.test.ts index f052773..902cdeb 100644 --- a/src/cli/format-event.test.ts +++ b/src/cli/format-event.test.ts @@ -138,6 +138,39 @@ describe("printClaudeStreamEvent", () => { expect(output()).toBe("some output text"); }); + it("prints rate_limit_event with type, status, and reset time", () => { + printClaudeStreamEvent(JSON.stringify({ + type: "rate_limit_event", + rate_limit_info: { + status: "allowed", + resetsAt: 1777056000, + rateLimitType: "five_hour", + overageStatus: "allowed", + isUsingOverage: false, + }, + uuid: "3ab8f9eb-b9d6-4bf6-9c39-4608427717fc", + session_id: "ad5f3e11-3c0c-4144-b53d-d4b959e57cee", + }), false); + expect(output()).toContain("rate_limit:"); + expect(output()).toContain("five_hour"); + expect(output()).toContain("allowed"); + expect(output()).toContain("resets="); + // Raw JSON must not be surfaced verbatim + expect(output()).not.toContain("3ab8f9eb-b9d6-4bf6-9c39-4608427717fc"); + }); + + it("prints rate_limit_event with unknown fields gracefully", () => { + printClaudeStreamEvent(JSON.stringify({ + type: "rate_limit_event", + rate_limit_info: {}, + }), false); + expect(output()).toContain("rate_limit:"); + expect(output()).toContain("type=unknown"); + expect(output()).toContain("status=unknown"); + // No resetsAt present — reset clause omitted + expect(output()).not.toContain("resets="); + }); + it("does not print unknown types in non-debug mode", () => { printClaudeStreamEvent(JSON.stringify({ type: "unknown", data: "stuff" }), false); expect(output()).toBe(""); diff --git a/src/cli/format-event.ts b/src/cli/format-event.ts index 13263be..9cbe191 100644 --- a/src/cli/format-event.ts +++ b/src/cli/format-event.ts @@ -133,6 +133,22 @@ export function printClaudeStreamEvent(raw: string, debug: boolean): void { return; } + if (type === "rate_limit_event") { + const info = + typeof parsed.rate_limit_info === "object" && parsed.rate_limit_info !== null + ? (parsed.rate_limit_info as Record) + : {}; + const limitType = typeof info.rateLimitType === "string" ? info.rateLimitType : "unknown"; + const status = typeof info.status === "string" ? info.status : "unknown"; + const resetsAt = typeof info.resetsAt === "number" + ? new Date(info.resetsAt * 1000).toISOString() + : ""; + const parts = [`rate_limit: type=${limitType} status=${status}`]; + if (resetsAt) parts.push(`resets=${resetsAt}`); + console.log(pc.yellow(parts.join(" "))); + return; + } + if (debug) { console.log(pc.gray(line)); }