Repository navigation
perf(cli): headless startup runs interactive-only probes (IDE process walk, macOS theme) #12599
Copy link
Copy link
Closed
Labels
category/performancePerformance and optimizationPerformance and optimizationpriority/P2Medium - Moderately impactful, noticeable problemMedium - Moderately impactful, noticeable problemscope/ideIDE integration generalIDE integration generalscope/latencyResponse time optimizationResponse time optimizationscope/macosmacOS specific issuesmacOS specific issuesscope/non-interactiveNon-interactive modeNon-interactive modescope/themesTheme system and customizationTheme system and customizationstatus/in-reviewThis issue is currently in review.This issue is currently in review.type/enhancementNon-bug improvement or optimizationNon-bug improvement or optimization
Description
Summary
Follow-up to #12405 (measurements comment). On the way to its first request, a headless run still does work that only an interactive terminal can use:
- IDE process-tree walk. Headless and ACP runs load the built-in slash commands. Building
/idecallsIdeClient.getInstance(), which runsps -o ppid=,command= -p <pid>through/bin/shonce for each ancestor process./ideis interactive-only (supportedModes: ['interactive']), so after paying for the walk the command is filtered out. Stack captured on 0.24.4:ideCommand → IdeClient.getInstance → getIdeProcessInfoForUnix → getProcessInfo, called fromBuiltinCommandLoader.loadCommands. How many forks this costs depends on the process ancestry: 2–5sh+pspairs per run here. - Synchronous macOS theme probe. When the theme is
autoor unset,main()resolves a baseline theme withexecSync('defaults read -g AppleInterfaceStyle', { timeout: 3000 }), which blocks the event loop. Output that goes to a pipe (--output-format json, automation) never renders theme colors. The interactive TUI refines the theme later with its own async OSC 11 probe, so it does not need this baseline either.
Proposal
- Skip building
/idewhen the config is non-interactive, the same way it is already skipped for execution environments and tool sandboxes. - Resolve the synchronous
autobaseline only when stdout is a terminal. The--resumepicker and TUI still get it, and piped runs keep the default theme.
Out of scope / still open
- The ~1.04 s zero-CPU wait inside
Config.initialize()reported on macOS is not reproduced on Linux (the wholeConfig.initialize()takes 89 ms there). It needs a macOS profile of thetool_registry_created → gemini_tools_updatedwindow before anything can be changed. - The resident
cli-entry.jslauncher (~49–58 MB RSS) is left as is. Its GB·s cost scales with run time, which the double-boot fix drops by ~48%.
中文说明
概述
#12405 的后续(实测评论)。headless 运行在发出第一个请求之前,还在做只有交互式终端才用得上的事:
- IDE 进程树遍历。 headless 和 ACP 运行都会加载内置斜杠命令。构造
/ide时会调用IdeClient.getInstance(),它对每个祖先进程都通过/bin/sh跑一次ps -o ppid=,command= -p <pid>。而/ide只支持交互模式(supportedModes: ['interactive']),遍历的代价付完之后,这个命令还是会被过滤掉。在 0.24.4 上抓到的调用栈是ideCommand → IdeClient.getInstance → getIdeProcessInfoForUnix → getProcessInfo,调用方是BuiltinCommandLoader.loadCommands。要 fork 多少次取决于进程的祖先链:这里每次运行 2–5 对sh+ps。 - 同步的 macOS 主题探测。 主题为
auto或未设置时,main()用execSync('defaults read -g AppleInterfaceStyle', { timeout: 3000 })解析一个基线主题,会阻塞事件循环。输出到管道时(--output-format json、自动化场景)根本不渲染主题颜色。交互式 TUI 之后还会用自己的异步 OSC 11 探测来细化主题,所以它也用不上这个基线。
方案
- config 为非交互时不构造
/ide,与现在对 execution environment 和工具 sandbox 的跳过方式一致。 - 只有 stdout 是终端时才同步解析
auto基线。--resume选择器和 TUI 照常拿到基线,输出到管道的运行保持默认主题。
不在范围内 / 仍待解决
- macOS 上报告的
Config.initialize()内 ~1.04 s 零 CPU 等待在 Linux 上复现不出来(Linux 上整个Config.initialize()只要 89 ms)。动手之前,要先在 macOS 上 profiletool_registry_created → gemini_tools_updated这段窗口。 - 常驻的
cli-entry.jslauncher(~49–58 MB RSS)这次不动。它的 GB·s 开销随运行时长变化,而修掉“启动两遍”之后运行时长已经降了 ~48%。
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
category/performancePerformance and optimizationPerformance and optimizationpriority/P2Medium - Moderately impactful, noticeable problemMedium - Moderately impactful, noticeable problemscope/ideIDE integration generalIDE integration generalscope/latencyResponse time optimizationResponse time optimizationscope/macosmacOS specific issuesmacOS specific issuesscope/non-interactiveNon-interactive modeNon-interactive modescope/themesTheme system and customizationTheme system and customizationstatus/in-reviewThis issue is currently in review.This issue is currently in review.type/enhancementNon-bug improvement or optimizationNon-bug improvement or optimization
Activity
doudouOUC commentedon Sep 24, 2026
Confirmed against main ca8146a. All four claims verified against the actual source.
1.
/idebuilding triggers thepsancestor walk even in headless mode.ideCommand(packages/cli/src/ui/commands/ideCommand.ts:136-137) is an async function whose first line isawait IdeClient.getInstance().IdeClient.getInstance(packages/core/src/ide/ide-client.ts:120-134) eagerly callsgetIdeProcessInfo()at line 124 on first resolution.getIdeProcessInfodispatches togetIdeProcessInfoForUnix(packages/core/src/ide/process-utils.ts:49-73), which loops up toMAX_TRAVERSAL_DEPTH=32ancestors callinggetProcessInfo(line 22-42), each spawningps -o ppid=,command= -p <pid>viaexecAsync(promisifiedexec→/bin/sh -c). When a shell ancestor is found, two additionalgetProcessInfocalls are made (grandparent + IDE command), so 3+ sh+ps pairs per run.2.
/ideissupportedModes: ['interactive']and filtered AFTER the walk. Every returned command object inideCommand.tscarriessupportedModes: ['interactive'] as const(lines 146, 164, 174, 195, 283, 308). The mode filter is applied later atnonInteractiveCliCommands.ts:501-502viagetCommandsForMode(executionMode)— afterloadCommandsalready paid for the process walk.3.
BuiltinCommandLoader.loadCommandsis the call site.packages/cli/src/services/BuiltinCommandLoader.ts:98-108:loadCommandscallsawait ideCommand()at line 108 when!getExecutionEnvironment() && !getShellExecutionSandbox()— but there is NO check for non-interactive mode.BuiltinCommandLoaderis included in the loader list for non-interactive paths (nonInteractiveCliCommands.ts:451-454, 780-783), andCommandService.create(packages/cli/src/services/CommandService.ts:77) callsloader.loadCommands(signal)for each loader unconditionally.4. Synchronous macOS theme probe runs regardless of stdout TTY.
main()(packages/cli/src/llm.tsx:418) at lines 613-627: whenconfiguredThemeisautoor unset (theelsebranch at line 620),themeManager.setActiveTheme(AUTO_THEME_NAME)is called at line 627 with no TTY guard. This resolves throughresolveAutoTheme()→detectTerminalTheme()→detectMacOSTheme()(packages/cli/src/ui/themes/detect-terminal-theme.ts:143-153), which callsexecSync('defaults read -g AppleInterfaceStyle', { timeout: 3000 })at line 149, blocking the event loop. The async OSC 11 refinement (llm.tsx:1195-1201) IS correctly guarded byconfig.isInteractive() && !wasRaw && process.stdin.isTTY(line 1166), but the sync baseline is not.Fix direction assessment. Both proposals are sound and follow existing patterns: (a) The
BuiltinCommandLoader.ts:104-107guard already skipsideCommand()for execution environments and sandboxes — adding a non-interactive mode check is the same pattern. (b)detectOsc11Theme()already has aprocess.stdin.isTTY || process.stdout.isTTYguard (detect-terminal-theme.ts:102-104), so gating the syncdetectMacOSTheme()path on stdout being a TTY is consistent. One minor note: the issue's stack omits the intermediarygetIdeProcessInfo(process-utils.ts:175-183) that dispatches togetIdeProcessInfoForUnix/getIdeProcessInfoForWindows, but this doesn't affect the root cause.