From 60d5b9b49dce0561e4990926f463fad95b958a3f Mon Sep 17 00:00:00 2001 From: can1357 Date: Sat, 1 Aug 2026 20:04:07 +0200 Subject: [PATCH] fix(coding-agent): classify host before auth storage --- packages/coding-agent/src/main.ts | 13 +++++---- .../test/main-host-classification.test.ts | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 packages/coding-agent/test/main-host-classification.test.ts diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 361538a1b..956f4810e 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -1150,10 +1150,6 @@ export async function runRootCommand( const notifs: (InteractiveModeNotify | null)[] = []; - // Create AuthStorage and ModelRegistry upfront - const authStorage = await logger.time("discoverAuthStorage", deps.discoverAuthStorage ?? discoverAuthStorage); - const modelRegistry = logger.time("modelRegistry:init", () => new ModelRegistry(authStorage)); - if (parsedArgs.version) { writeStartupNotice(parsedArgs, `${VERSION}\n`); process.exit(0); @@ -1205,9 +1201,9 @@ export async function runRootCommand( }); let cwd = getProjectDir(); - // Declare the interactive-host flag BEFORE Settings.init so the very first - // session-critical database opens (agent.db/stats.db during settings load) - // can pick the right busy timeout. See getDbBusyTimeoutMs(). + // Classify the host before opening auth or settings storage so every + // session-critical database connection picks the right busy timeout. + // See getDbBusyTimeoutMs(). const isProtocolMode = mode === "rpc" || mode === "rpc-ui" || mode === "acp"; // Protocol modes own stdin; treating it as prompt text would consume JSON-RPC frames before their transports start. const pipedInput = isProtocolMode ? undefined : await logger.time("readPipedInput", readPipedInput); @@ -1217,6 +1213,9 @@ export async function runRootCommand( // tree; declare it so headless subagent optimizations (e.g. skipping replan // title refresh) can tell a focusable process from a print/RPC/eval one. setInteractiveHost(isInteractive); + // Create AuthStorage and ModelRegistry upfront + const authStorage = await logger.time("discoverAuthStorage", deps.discoverAuthStorage ?? discoverAuthStorage); + const modelRegistry = logger.time("modelRegistry:init", () => new ModelRegistry(authStorage)); const settingsInstance = deps.settings ?? (await logger.time("settings:init", Settings.init, { cwd, configFiles: parsedArgs.config })); diff --git a/packages/coding-agent/test/main-host-classification.test.ts b/packages/coding-agent/test/main-host-classification.test.ts new file mode 100644 index 000000000..d982d853b --- /dev/null +++ b/packages/coding-agent/test/main-host-classification.test.ts @@ -0,0 +1,27 @@ +import { expect, it } from "bun:test"; +import { parseArgs } from "@oh-my-pi/pi-coding-agent/cli/args"; +import { runRootCommand } from "@oh-my-pi/pi-coding-agent/main"; +import { getDbBusyTimeoutMs, setInteractiveHost } from "@oh-my-pi/pi-utils"; + +it("classifies an interactive host before opening auth storage", async () => { + const previous = setInteractiveHost(false); + const stop = new Error("stop after auth classification"); + let observedTimeout: number | undefined; + const parsed = parseArgs([]); + parsed.noExtensions = true; + + try { + await expect( + runRootCommand(parsed, [], { + discoverAuthStorage: async () => { + observedTimeout = getDbBusyTimeoutMs(); + throw stop; + }, + }), + ).rejects.toBe(stop); + } finally { + setInteractiveHost(previous); + } + + expect(observedTimeout).toBe(5000); +});