Skip to content

perf(cli): stop one-shot runs from booting the CLI twice - #12602

Merged
yiliang114 merged 5 commits into
mainfrom
perf/one-shot-skip-self-relaunch
Sep 24, 2026
Merged

yiliang114 merged 5 commits into
mainfrom
perf/one-shot-skip-self-relaunch

Conversation

@yiliang114

@yiliang114 yiliang114 commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

When an explicit one-shot prompt would relaunch itself without adding any Node or script arguments, and neither .env files nor settings.env injected any value, the CLI now keeps running in the current process instead of calling execve. If they did inject something, the process is still replaced: modules that read the environment when they load (e.g. the DashScope provider's DASHSCOPE_PROXY_BASE_URL) and Node itself (NODE_EXTRA_CA_CERTS) only see those values in a fresh image. The production launcher also passes NODE_COMPILE_CACHE to the CLI it spawns, so spawned runs reuse compiled code the way the in-process serve / mcp fast path already does. An inherited NODE_COMPILE_CACHE and NODE_DISABLE_COMPILE_CACHE are both respected. The one-shot design doc (EN + zh-CN) documents the in-place continuation.

Why it's needed

advanced.autoConfigureMemory defaults to off, and --bare never loads it, so the memory relaunch normally has no arguments to add. Replacing the process then only boots an identical image a second time: every module loads again, settings load again, startup probes run again. Separately, the spawn path never enabled the compile cache, so every one-shot run recompiled the bundle. Between them, these are the largest share of the startup, CPU and memory cost tracked in #12405.

Reviewer Test Plan

How to verify

  1. Run qwen --bare --safe-mode --output-format json -p hi against any OpenAI-compatible endpoint under strace -f -e trace=execve (Linux) or NODE_DEBUG=child_process. Expected: only two node images (launcher + CLI), not three.
  2. Put NODE_EXTRA_CA_CERTS=<path> in ~/.qwen/.env and repeat. Expected: three node images, because the process is still replaced so the new CA setting reaches Node.
  3. After one warm-up run, confirm that a cache directory ($TMPDIR/node-compile-cache by default) now exists.
  4. Unit tests: packages/cli/src/utils/relaunch.test.ts (continues in place, still replaces the process when .env / settings.env injected values, falls back to spawn when execve fails) and scripts/tests/cli-entry.test.js (the compile cache is handed to the child; inherited and disabled caches are respected).

Evidence (Before & After)

Measured on Linux x64, 4 vCPU, Node 22.23.2, published @qwen-code/[email protected], local OpenAI-compatible mock, --bare --safe-mode --no-telemetry --output-format json -p hi, mean of 3–4 warm runs. To produce the "after" rows, the same two changes were applied to that installed bundle:

wall CPU (core·s) memory (GB·s) node images
before 3.93 s 5.08 0.84 3
after 2.04 s 2.81 0.44 2

Time to the first model request dropped from ~3.85 s to ~1.95 s. The NODE_EXTRA_CA_CERTS-in-.env case still produces 3 images.

Tested on

OS Status
🍏 macOS ⚠️
🪟 Windows N/A
🐧 Linux ✅

Windows has no execve and keeps the supervised relaunch unchanged; the compile-cache part of the change is platform-neutral.

Environment (optional)

Published 0.24.4 bundle with the equivalent patch applied, local mock server. Unit tests were written but not run locally; CI runs them.

Risk & Scope

  • Main risk or tradeoff: users whose .env or settings.env sets variables (commonly API keys in ~/.qwen/.env) still pay the second boot; the gain applies when configuration comes from the process environment, as in automation and the benchmark. The in-place path depends on the fact that the relaunched child would have seen exactly this process's state. The only extra thing it used to receive is the relaunch env-provenance marker. This process already holds that state in memory, so the marker is not copied into its own environment, where it would reach tool subprocesses. NODE_COMPILE_CACHE now reaches tool subprocesses too, the same way it already does from the serve fast path.
  • Not validated / out of scope: macOS end-to-end timing; the macOS-only ~1 s idle wait in Config.initialize() (perf(cli): headless startup runs interactive-only probes (IDE process walk, macOS theme) #12599); the resident launcher process.
  • Breaking changes / migration notes: none.

Design: English · 简体中文

Linked Issues

Closes #12598. Part of #12405.

中文说明

本 PR 的改动

显式 one-shot prompt 要自我 relaunch 时,如果不会多出任何 Node 参数或脚本参数,并且 .env 文件和 settings.env 都没有注入任何值,CLI 现在直接在当前进程里继续运行,不再调用 execve。只要注入过值,就仍然替换进程:在加载时读取环境变量的模块(比如 DashScope provider 的 DASHSCOPE_PROXY_BASE_URL)和 Node 本身(NODE_EXTRA_CA_CERTS),只有在新镜像里才能看到这些值。生产 launcher 现在也把 NODE_COMPILE_CACHE 传给它 spawn 的 CLI,让 spawn 出来的运行也复用编译结果,与进程内 serve / mcp fast path 一致。继承下来的 NODE_COMPILE_CACHE 和 NODE_DISABLE_COMPILE_CACHE 都会被尊重。one-shot 设计文档(中英文)补充了原地继续运行的说明。

为什么需要这个改动

advanced.autoConfigureMemory 默认关闭,--bare 也不会加载它,所以内存 relaunch 通常没有参数可加。这时替换进程只是把同样的镜像再启动一遍:所有模块重新加载,settings 重新读取,启动探测再跑一遍。另外,spawn 路径从来没开编译缓存,每次 one-shot 都要重新编译整个 bundle。这两点合起来,是 #12405 所跟踪的启动时间、CPU 和内存开销的最大头。

Reviewer 测试计划

如何验证

  1. 对任意 OpenAI 兼容端点运行 qwen --bare --safe-mode --output-format json -p hi,同时挂 strace -f -e trace=execve(Linux)或 NODE_DEBUG=child_process。预期:只有两个 node 镜像(launcher + CLI),不再是三个。
  2. 在 ~/.qwen/.env 里写入 NODE_EXTRA_CA_CERTS=<path> 后重复。预期:三个 node 镜像,因为仍会替换进程,让新的 CA 设置传到 Node。
  3. 热身运行一次后,确认缓存目录(默认 $TMPDIR/node-compile-cache)已经生成。
  4. 单测:packages/cli/src/utils/relaunch.test.ts(原地继续运行;.env / settings.env 注入过值时仍替换进程;execve 失败时回退到 spawn)和 scripts/tests/cli-entry.test.js(编译缓存传给子进程;继承的缓存和禁用的缓存都被尊重)。

证据(改动前后)

测量环境:Linux x64、4 vCPU、Node 22.23.2、已发布的 @qwen-code/[email protected]、本地 OpenAI 兼容 mock,参数 --bare --safe-mode --no-telemetry --output-format json -p hi,取 3–4 次热启动的平均值。“改动后”一行的数据来自把同样两处改动打进那份已安装的 bundle:

墙钟 CPU (core·s) 内存 (GB·s) node 镜像数
改动前 3.93 s 5.08 0.84 3
改动后 2.04 s 2.81 0.44 2

到第一个模型请求的时间从 ~3.85 s 降到 ~1.95 s。.env 里带 NODE_EXTRA_CA_CERTS 的情况仍是 3 个镜像。

已测试平台

OS 状态
🍏 macOS ⚠️
🪟 Windows N/A
🐧 Linux ✅

Windows 没有 execve,受监督的 relaunch 保持不变;编译缓存这部分改动与平台无关。

环境(可选)

已发布的 0.24.4 bundle 打上等价补丁,本地 mock server。单测已编写,但没有在本地跑,交给 CI。

风险与范围

  • 主要风险或取舍:.env 或 settings.env 里设了变量的用户(常见的是 ~/.qwen/.env 里放 API key)仍然要启动两遍;收益主要落在配置来自进程环境变量的场景,比如自动化和 benchmark。原地继续运行成立的前提是,relaunch 出来的子进程看到的状态本来就和当前进程完全一样。它额外收到的只有 relaunch 的环境来源标记。当前进程内存里已经有这份状态,所以不把这个标记写回自己的环境变量,否则它会流到工具子进程里。NODE_COMPILE_CACHE 现在也会流到工具子进程,与 serve fast path 现有的行为一致。
  • 未验证 / 不在范围内:macOS 端到端计时;macOS 独有的 Config.initialize() ~1 s 空等(perf(cli): headless startup runs interactive-only probes (IDE process walk, macOS theme) #12599);常驻的 launcher 进程。
  • 破坏性变更 / 迁移说明:无。

设计文档:English · 简体中文

关联 Issue

Closes #12598。属于 #12405。

A one-shot prompt re-executes itself to apply Node memory arguments, but
with advanced.autoConfigureMemory off (the default, and always under
--bare) there are none: execve boots an identical image a second time.
Continue in place when the replacement would add no Node or script
arguments and no Node boot variable (e.g. NODE_EXTRA_CA_CERTS from a
home .env) changed after startup.

The launcher's spawn path also never enabled the compile cache, so every
spawned run recompiled the bundle; pass NODE_COMPILE_CACHE to the child
the way the serve fast path already does.
@github-actions github-actions Bot added the review/self-reported The linked issue was opened by the PR author (self-reported) label Sep 24, 2026
yiliang114 and others added 2 commits September 24, 2026 13:03
Every import of scripts/cli-entry.js stamps QWEN_CODE_MANAGED_NPM_PIN and
QWEN_CODE_MANAGED_NPM_ROOT into the real process.env, and the pin's bootstrap
guard cannot tell two imports of the same file apart: Vitest drops the
cache-buster query from import.meta.url, so currentEntryPath is identical
across tests. The compile-cache tests therefore handed their pin to the
tilde-QWEN_HOME test below them, which then read the inherited updateRoot
instead of the tmpdir fallback it pins. Previously harmless only by accident —
the standalone-shim test above them deleted both variables.

Clear both in beforeEach so every test drives a top-level invocation. The
assertion is unchanged, and breaking the homedir fallback in getHomeDir still
fails it (checked by mutation).

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmuf135w40z
Brings in the base-side .github/workflows/ci.yml change (a5a5eb5) so the
"Check lint gate freshness" step sees a current GATE_FILE copy.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmuf135w40z
@yiliang114

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@yiliang114
yiliang114 requested review from qqqys and wenshao September 24, 2026 07:40
Continuing in place skipped the second boot that let module-level env
reads (e.g. DASHSCOPE_PROXY_BASE_URL in the DashScope provider) see
values from .env or settings.env. Guard on any injected value instead of
only NODE_*/UV_ keys; that also covers NODE_EXTRA_CA_CERTS.
@yiliang114
yiliang114 dismissed a stale review via b67ba8c September 24, 2026 08:32
utils/ may not value-import from config/ (lint boundary), so llm.tsx
computes hasLoadedEnvironmentValues() and hands it to
relaunchAppInChildProcess.

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at 5019fa2 (author is myself — this COMMENT is the record; a non-author maintainer vote is needed to clear REVIEW_REQUIRED).

No blocking findings. All 7 open threads are the bot's Suggestion-level notes from today, and I verified each against the head rather than adopting them:

  • R1-1 / R1-8 (test-worthiness): real — the environmentChangedSinceBoot wiring has no test witness, and the disabled-cache test never exercises the status === ENABLED term. Worth one small test each in a follow-up.
  • R1-2 (hasLoadedEnvironmentValues under-reports): real but narrow — only when CLOUD_SHELL=true AND a .env carries the exact keys the cloud-shell helper writes first (e.g. GOOGLE_CLOUD_PROJECT). The relaunch skip then misses a file-injected value. Edge enough to fix in a follow-up rather than gate this PR.
  • R1-3 / R1-4 (guard duplicated; design-doc invariant now stale): real — three copies of the compile-cache publish guard exist now, and acp-compile-cache-propagation.md's "only mutation point" line is falsified by the second launcher site. Cheap follow-up: hoist the guard + fix the doc sentence.
  • R1-5 (identical-image skip inside the execve gate): noted — win32/os400 don't reach execve at all, so moving the skip platform-wide needs a separate look at what relaunch means there; not a defect in this PR's scope.
  • R1-6 (launcher writes resolved NODE_COMPILE_CACHE into the child env): real semantic note — a file-sourced NODE_COMPILE_CACHE can no longer win in the child because the key arrives pre-set. The written value is the correct default, and overriding it via .env is a rare corner; acceptable for this PR, worth a doc line.

CI is green except review-pr (queued, bot infrastructure). Threads stay open for author disposition rather than resolved-by-me.

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking findings.
Approval blockers: none.

Tier: Standard — perf optimization, in-place continuation skips the second CLI boot when a relaunch would produce an identical image.

Checked:

  • Tracking correctness: dotEnvSourcedKeys and settingsEnvSourcedKeys are populated inside loadEnvironment() only when a key is actually written to process.env (behind isEffectivelyUnset). hasLoadedEnvironmentValues() correctly returns true whenever any value was injected by .env or settings.env. preResolveHomeEnvOverrides intentionally does not contribute to these sets — HOME bootstrap vars are set before any module loads, so the continuing process already holds the right state and a fresh image would read the same values.
  • In-place condition (relaunch.ts:99–106): fires only when additionalNodeArgs.length === 0 && additionalScriptArgs.length === 0 && !options?.environmentChangedSinceBoot — the three conditions required for a truly identical image. QWEN_CODE_NO_RELAUNCH is set to true on in-place continuation, matching what the replaced image would have received. childEnv entries are deliberately not applied (correct: provenance info is already in-memory; privateAcpChildEnv is only relevant when replaceProcess is false, i.e. not on this path).
  • Fallback-to-spawn test (relaunch.test.ts:270–279): correctly updated to use ['--max-old-space-size=4096'] so the execve path is exercised rather than the new in-place short-circuit.
  • Compile-cache forwarding (cli-entry.js): module.enableCompileCache?.() with three guards — not already inherited (!env['NODE_COMPILE_CACHE']), status is ENABLED, directory is present. Tests cover the newly-enabled case, an inherited NODE_COMPILE_CACHE, and NODE_DISABLE_COMPILE_CACHE (mock returns { status: 3 }, no directory set).
  • Design doc (EN + zh-CN): in-place continuation paragraph added at matching positions in both files.

Suggestions noted (non-blocking, consistent with prior reviewer's Suggestion classification):

  • R1-2: hasLoadedEnvironmentValues() under-reports on Cloud Shell: setUpCloudShellEnvironmentInEnv (environment.ts:682) writes GOOGLE_CLOUD_PROJECT before the tracking loop, so a .env supplying only that key returns false. No import-time read of that key was identified, so no live wrong outcome — but the JSDoc's completeness claim is violated.
  • R1-5: In-place skip sits inside the execve/win32 gate; the new design-doc paragraph says "every one-shot run" but Windows / IBM i don't benefit. Doc overclaims.
  • R1-6: Launcher writes final NODE_COMPILE_CACHE into the spawned child's boot env, pre-empting the CLI precedence layer. An operator's settings.env compile-cache path is silently overridden on spawned runs, though honoured on the serve fast path. Author acknowledged as a documented gap.

Cross-check: Author and qqqys both classified all seven prior suggestions as non-blocking. I agree with that classification.

Not covered: macOS and Windows end-to-end timing (no host); rung 3 not applicable (no terminal-dependent behavior changed).

Reviewed with AI assistance.

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical-only review at 5019fa27, base d0cd622a.

Verdict: APPROVE — no Critical. Seven Suggestions are unresolved and I do not treat any of them as gating; two of them point at claims I checked independently because they sit on the wire that makes the skip safe, and both hold.

The skip is gated on the three things that would make a fresh image necessary

if (
  additionalNodeArgs.length === 0 &&
  additionalScriptArgs.length === 0 &&
  !options?.environmentChangedSinceBoot
) {
  process.env['QWEN_CODE_NO_RELAUNCH'] = 'true';
  return;
}

A replacement image is only needed when the new process would differ from this one, and the guard tests exactly the three ways it could: new Node arguments (the caller passes memoryArgs, so a heap-calibrated run still execs), new script arguments, and an environment that changed after this process's modules and Node itself read it. QWEN_CODE_NO_RELAUNCH is not a new signal — llm.tsx already reads it at lines 188 and 522, the latter alongside SANDBOX — so publishing it here puts this path on the same footing as the ones that already skip relaunching.

The predicate under-reports, and that is safe — verified rather than assumed

One unresolved thread says hasLoadedEnvironmentValues() contradicts its own JSDoc, because a .env-supplied value can reach process.env without entering either tracking set. That is factually correct, so I checked whether any of those untracked values is one that requires a fresh image — which is the only way under-reporting could cost something.

The tracked tiers are the two that matter for this question: the .env tier at lines 694-703 and the settings.env tier at 732-740 both apply the no-override rule and both add the key to their set, so anything they inject is reported. The untracked paths are two, and neither needs a relaunch:

  • readHomeEnvInto, reached from preResolveHomeEnvOverrides, injects the home bootstrap keys — and its entire purpose, per its own docblock, is to resolve QWEN_HOME and QWEN_RUNTIME_DIR before any settings or storage path is read, precisely so the current process does not snapshot legacy paths. The values are consumed in-process, by design, ahead of the readers. A fresh image would add nothing.
  • setUpCloudShellEnvironment writes GOOGLE_CLOUD_PROJECT in place. It is read at runtime by application code, not by Node at startup, so this process already has it.

Neither is a variable Node itself reads during bootstrap — NODE_EXTRA_CA_CERTS is the shape that would matter, and that arrives through the tracked tiers. So the JSDoc over-claims its own scope, which is worth tightening, and the behaviour is correct.

The related thread about the escape hatch having no test is also fair: environmentChangedSinceBoot: hasLoadedEnvironmentValues() at the single production call site, and the predicate it calls, are asserted nowhere, so deleting the line would keep every suite green while silently reintroducing the relaunch for env-changed runs. I confirmed the line is present at this head and that the predicate answers correctly for the tiers that matter, so the uncovered path contains no defect today — which under a Critical-only standard makes it a coverage gap rather than a blocker. It is the one test I would add first.

Skipping the execve does not strand the afterSpawn hook

The caller passes afterSpawn: clearCorruptionEnvVars, which deletes ENV_CORRUPTED_PATH and ENV_WAS_RECOVERED. On the skip path that hook never runs, so I checked whether the markers ending up retained is wrong. It is not: in the relaunch path the child inherits them and the parent clears them after spawning, so the process doing the real work holds them in both cases. Continuing in place makes this process that process. The outcomes match.

The compile-cache addition is correct, with two recorded costs

The launcher's non-fast-path branch now enables the compile cache and writes the resolved directory into the child's boot environment, guarded on !env['NODE_COMPILE_CACHE'], status === ENABLED and a non-empty directory — so it cannot publish a half-initialised value, and it respects NODE_DISABLE_COMPILE_CACHE because enableCompileCache() returns a non-ENABLED status in that case. Two unresolved threads about it are accurate and neither is blocking: the guard is a near-duplicate of the one already in this file's in-process fast path, differing in three of ten lines; and the design doc's recorded invariant that the production fast path is the only mutation point is now false, so acp-compile-cache-propagation.md:66 needs updating. The precedence thread is real too — because loadEnvironment is no-override for both tiers, a NODE_COMPILE_CACHE supplied by a .env file can no longer win over the launcher's resolved directory. That is a user-visible precedence change on a regenerable performance knob, not a correctness or data-loss defect, so it does not gate this merge, but it is the one I would not leave undocumented.

The last thread notes that the identical-image skip sits inside the execve / non-win32 / non-os400 gate while the added design-doc text speaks of every one-shot run, so Windows still double-boots. Worth aligning the doc to the gate; the gate itself is the right place for the decision, since execve is what makes continuing in place equivalent.

CI

15 checks pass and 27 are skipped at this head, with route still pending and nothing failing. Test (ubuntu-latest, Node 22.x), Lint & Static and the cli-entry suite that covers the launcher all completed green. No review carries a changes-requested state.

@yiliang114
yiliang114 added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit f6b4b4b Sep 24, 2026
177 checks passed
tanzhenxin added a commit to tanzhenxin/qwen-code that referenced this pull request Sep 24, 2026
Resolve the launcher conflict with QwenLM#12602 by enabling the compile cache
once, before the platform split. The Windows child gets
NODE_COMPILE_CACHE in its environment as on main; on POSIX the
in-process CLI exports it, so supervised relaunches and tool
subprocesses still receive it.

The QwenLM#12603 theme-baseline test stopped main() at the supervised
relaunch; a plain one-shot run now supervises itself in-process, so
the test stops there too.
tanzhenxin added a commit to tanzhenxin/qwen-code that referenced this pull request Sep 24, 2026
QwenLM#12602 re-boots a one-shot run whenever .env or settings.env injected
values, because modules that read the environment at load time (and
Node itself, e.g. NODE_EXTRA_CA_CERTS) only see them in a fresh image.
The in-process path ran ahead of that check and skipped it for both
interactive and -p. Take the in-process path only when no env-file
value was injected; otherwise relaunch as main does.
SachinD6 pushed a commit to SachinD6/qwen-code that referenced this pull request Sep 25, 2026
…ntials (QwenLM#12680)

* perf(cli): keep running in place when env files only hold model credentials

The env-file relaunch rule from QwenLM#12602 fires for any value that `.env` files
or `settings.env` inject, including the API key that `/auth` stores and the
keys the docs recommend for `~/.qwen/.env`. Those users always paid the
second boot. Providers read credentials and endpoints per request, never
while a module loads, so `hasLoadedEnvironmentValues()` now ignores the
documented per-auth-type variables and the `envKey` of every configured
model provider. Any other injected value still forces a fresh image, and a
provider entry can never exempt a loader or boot-time key.

The DashScope proxy base URL was the one such variable read at module load;
it is now read at call time.

Reported by @wenshao in the QwenLM#12622 review (F2).

Claude-Session: https://claude.ai/code/session_01163b7X4Ntw1S2pr9VrtRrs

* feat(scripts): let the startup benchmark load credentials from a file

`--credentials settings|dotenv` moves the stub's key and URL from the shell
into the settings file's `env` block or `~/.qwen/.env`, so the env-file
relaunch rule can be measured. The default stays `shell`.

Claude-Session: https://claude.ai/code/session_01163b7X4Ntw1S2pr9VrtRrs
pull Bot pushed a commit to bit-cook/qwen-code that referenced this pull request Sep 25, 2026
…wenLM#12622)

* perf(cli): halve fresh-startup time to typeable and cut RSS by 60%

Measured with a clean-environment benchmark following the approach in
https://claude.dev/blog/how-we-made-claude-ai-faster/ (interleaved A/B,
p75 wall-clock backed by deterministic proxies). Against main, time to a
typeable prompt goes from 2.55 s to 1.18 s, `qwen -p` time to first
request from 2.15 s to 0.94 s, and RSS at the prompt from 574 MB to
231 MB, with no screen change after the prompt becomes usable.

- Skip the supervisor relaunch when it adds no Node flags; restarts
  re-exec in place and updates run in-process (POSIX, plain TUI and -p).
- The bin launcher runs the CLI in-process on POSIX.
- Enable Node's compile cache on the default route.
- Move startup side effects off the critical path: editor probing, the
  IDE process walk outside VS Code, and the npm-based update check.
- Resolve the git branch before the first frame so the footer does not
  reflow the screen after the prompt is usable.
- Bundle with minifyWhitespace; load the review command and non-common
  highlight grammars only when needed.

Experiments, tradeoffs and open decisions are in
docs/design/2026-09-24-fresh-startup-performance.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01N1XQv6wm9YJ5rSM53eMSRT

* fix(cli): keep global.gc in relaunched sessions

On POSIX the bin launcher now runs the CLI in-process and exposes gc at
runtime, so --expose-gc is no longer in process.execArgv. The supervised
relaunch built its child's flags from execArgv alone, so the session
process lost global.gc in --acp, stream-json, --json-fd, file input,
advanced.autoConfigureMemory, and on Node without process.execve.

Both relaunch paths now take their flags from one helper that adds
--expose-gc when gc is exposed but not on the command line.

* fix(cli): relaunch when env files injected values

QwenLM#12602 re-boots a one-shot run whenever .env or settings.env injected
values, because modules that read the environment at load time (and
Node itself, e.g. NODE_EXTRA_CA_CERTS) only see them in a fresh image.
The in-process path ran ahead of that check and skipped it for both
interactive and -p. Take the in-process path only when no env-file
value was injected; otherwise relaunch as main does.

* fix(cli): keep the spawned CLI under Bun

The Bun standalone flavor (the OpenTUI preview archives) runs the same
bin entry. Bun has no v8.setFlagsFromString, so the in-process path
crashed every non-fast-path command at startup on macOS and Linux.
Under Bun the entry spawns the CLI with --expose-gc, as on Windows.

Also correct the entry's header comment, the relaunch-flags helper's
doc comment and a displaced test comment, and narrow the design doc's
gc claim to the modes that were checked.

* fix(cli): install a deferred update after a clean exit

Without a supervising parent, the startup check fell back to "Run
/update to install the update." for standalone and update-command
installs. Record the request in-process instead: once the session ends
cleanly, install the update and exit with its code, so the launcher's
exit hook reopens the new version as the parent did on main. A second
quit during the install waits instead of cutting it short.

* docs(design): record the fresh-startup decisions

Resolve the open decisions, and describe the env-file relaunch rule,
the Bun launcher path, the restored update-on-exit and the independent
reproductions, in both language versions.

* docs(design): record decision 7 as a manual harness without a CI gate

The follow-up (QwenLM#12674) checks in the benchmark harness for manual runs.
The interactive startup counts stop at the typeable prompt, and
timer-driven work before it shifts with runner speed, so a CI gate
would be flaky.

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/self-reported The linked issue was opened by the PR author (self-reported)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(cli): one-shot runs boot the CLI twice and skip the compile cache

3 participants