From c8230be4a76bdc8a2b2b4f59dc3766da3d358ba7 Mon Sep 17 00:00:00 2001 From: can1357 Date: Fri, 20 Feb 2026 11:00:38 +0100 Subject: [PATCH] fix(coding-agent): fail fast when RPC startup exits early --- packages/ai/CHANGELOG.md | 12 +++++------- .../coding-agent/src/modes/rpc/rpc-client.ts | 14 +++++--------- .../coding-agent/test/rpc-client.start.test.ts | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 packages/coding-agent/test/rpc-client.start.test.ts diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 0167d8ab1..d5b42d658 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,17 +1,15 @@ # Changelog ## [Unreleased] - -### Changed - -- Implemented time-budget-based retry strategy for Google Gemini rate limits (429 errors), allowing retries within a 5-minute window instead of a fixed attempt cap -- Set default `maxRetries` to 5 for Anthropic, Azure OpenAI, and OpenAI client configurations to improve resilience against transient failures - ### Fixed - Improved error messages for OAuth token refresh failures by including detailed error information from the provider - Separated rate limit and usage limit error handling to provide distinct user-friendly messages for ChatGPT rate limits vs subscription usage limits -- Changed 429 retry strategy for OpenAI Codex to use a 5-minute time budget when the server provides a retry delay, instead of a fixed 5-attempt cap + +### Changed + +- Increased SDK retry attempts to 5 for OpenAI, Azure OpenAI, and Anthropic clients (was SDK default of 2) +- Changed 429 retry strategy for OpenAI Codex and Google Gemini CLI to use a 5-minute time budget when the server provides a retry delay, instead of a fixed attempt cap ## [12.14.0] - 2026-02-19 ### Added diff --git a/packages/coding-agent/src/modes/rpc/rpc-client.ts b/packages/coding-agent/src/modes/rpc/rpc-client.ts index 6a47be4a0..7596cb2c2 100644 --- a/packages/coding-agent/src/modes/rpc/rpc-client.ts +++ b/packages/coding-agent/src/modes/rpc/rpc-client.ts @@ -134,15 +134,11 @@ export class RpcClient { // Wait a moment for process to initialize await Bun.sleep(100); - try { - const exitCode = await Promise.race([this.#process.exited, Bun.sleep(500).then(() => null)]); - if (exitCode !== null) { - throw new Error( - `Agent process exited immediately with code ${exitCode}. Stderr: ${this.#process.peekStderr()}`, - ); - } - } catch { - // Process still running, which is what we want + const exitCode = await Promise.race([this.#process.exited, Bun.sleep(500).then(() => null)]); + if (exitCode !== null) { + throw new Error( + `Agent process exited immediately with code ${exitCode}. Stderr: ${this.#process.peekStderr()}`, + ); } } diff --git a/packages/coding-agent/test/rpc-client.start.test.ts b/packages/coding-agent/test/rpc-client.start.test.ts new file mode 100644 index 000000000..7009b93ef --- /dev/null +++ b/packages/coding-agent/test/rpc-client.start.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, test } from "bun:test"; +import * as path from "node:path"; +import { RpcClient } from "@oh-my-pi/pi-coding-agent/modes/rpc/rpc-client"; + +describe("RpcClient.start", () => { + test("rejects when RPC process exits immediately", async () => { + using client = new RpcClient({ + cliPath: path.join(import.meta.dir, "..", "src", "cli.ts"), + cwd: path.join(import.meta.dir, ".."), + provider: "__missing_provider__", + model: "claude-sonnet-4-5", + env: { PI_NO_TITLE: "1" }, + }); + + await expect(client.start()).rejects.toThrow('Unknown provider "__missing_provider__"'); + }); +});