问题描述
进入「配置编码工具 → 任一工具(Codex / Claude Code / CodeBuddy / WorkBuddy / OpenCode)」后,完成「模型选择」或其他子流程返回到该工具菜单时,上下方向键无响应,无法再选择菜单项,只能 Ctrl+C 强退。
复现步骤
- 运行:
npx qiniu-coding-helper enter codex(或任意其他工具)
- 在工具菜单选择「配置模型」并完成(或选择「查看当前配置」按回车返回)
- 返回到工具菜单后,按上下键无任何响应
注意:必须在「无法访问 https://registry.npmjs.org」的网络环境(如国内未配代理 / 公司防火墙)才能稳定复现。npm registry 通畅的网络下表面看不到,但仍存在隐式重渲染。
预期行为
完成子流程返回菜单后,能继续用方向键选择菜单项。
实际行为
菜单画面看上去"卡住"或在快速闪烁刷新,键盘输入被丢弃。底层是 @inquirer/select 持续抛 AbortPromptError → continue 立刻重渲染 → 立刻又被 abort,形成死循环。
根因分析(源码定位)
src/lib/wizard/menus/tool-menu.ts:24-49:
let latestVersion: string | null = null;
let fetchPromise: Promise<string | null> | null = null;
if (tool.npmPackageName) {
fetchPromise = fetchLatestVersion(tool.npmPackageName);
fetchPromise.then(v => { latestVersion = v; }).catch(() => {});
}
while (true) {
// ...
const controller = new AbortController();
if (latestVersion === null && fetchPromise) {
fetchPromise.then(() => {
if (!controller.signal.aborted) {
controller.abort();
}
});
}
// select<ToolMenuAction>({...}, { signal: controller.signal })
}
机制设计意图:菜单首次渲染时若 npm 最新版本还未拿到,等 fetch 完成自动 abort 当前 prompt,重渲染时显示最新版本号。
Bug:src/lib/version-service.ts:4 的 fetchLatestVersion 在网络失败/超时时返回 null(而非抛错)。一旦返回 null,latestVersion === null 永远成立,每次 while 循环都会给同一个已 resolved 的 fetchPromise 再挂一个 .then(() => controller.abort()):
- 已 resolved 的 Promise 的
.then 回调会立刻同步触发
controller.abort() 在 select prompt 还未开始等待用户输入时已被触发
- select 抛
AbortPromptError → catch 里 continue → 重新渲染 → 又挂 .then → 又立刻 abort
按键无响应是因为 select 每次都在 fast-path 抛错退出,连键盘事件循环都没进入。
复现条件
- 任何 npm registry 不可达的网络(国内裸网、公司代理拦截 GitHub/npm、TLS 中间人等)
- 已 fetch 一次失败后所有工具菜单(不只 Codex)都会触发
- 第一次进入因为 fetchPromise 还在 pending 不会立即触发,但子流程返回后必然触发
修复建议
引入 fetchCompleted 标志,fetch 完成(无论成败)后跳过后续 abort 挂载:
let latestVersion: string | null = null;
let fetchCompleted = false;
let fetchPromise: Promise<string | null> | null = null;
if (tool.npmPackageName) {
fetchPromise = fetchLatestVersion(tool.npmPackageName);
fetchPromise
.then(v => { latestVersion = v; fetchCompleted = true; })
.catch(() => { fetchCompleted = true; });
}
while (true) {
// ...
const controller = new AbortController();
if (!fetchCompleted && fetchPromise) { // ← 改这里
fetchPromise.then(() => {
if (!controller.signal.aborted) controller.abort();
});
}
// ...
}
这样不论 fetch 成功(返回版本号)还是失败(返回 null)后,第二次进入循环都不会再挂 abort 钩子。
环境信息
- 操作系统:Windows 11 (10.0.26200)
- Node.js 版本:v24.15.0
- pnpm 版本:10.33.2
- qiniu-coding-helper 版本:0.3.0
- 使用的工具:Codex / Claude Code / OpenCode 均能复现
- 工具版本:codex 已安装、claude-code 已安装、opencode-ai 1.15.6 已安装
- 线路:国内线路
api.qnaigc.com
其他信息
问题描述
进入「配置编码工具 → 任一工具(Codex / Claude Code / CodeBuddy / WorkBuddy / OpenCode)」后,完成「模型选择」或其他子流程返回到该工具菜单时,上下方向键无响应,无法再选择菜单项,只能 Ctrl+C 强退。
复现步骤
npx qiniu-coding-helper enter codex(或任意其他工具)注意:必须在「无法访问
https://registry.npmjs.org」的网络环境(如国内未配代理 / 公司防火墙)才能稳定复现。npm registry 通畅的网络下表面看不到,但仍存在隐式重渲染。预期行为
完成子流程返回菜单后,能继续用方向键选择菜单项。
实际行为
菜单画面看上去"卡住"或在快速闪烁刷新,键盘输入被丢弃。底层是
@inquirer/select持续抛AbortPromptError→continue立刻重渲染 → 立刻又被 abort,形成死循环。根因分析(源码定位)
src/lib/wizard/menus/tool-menu.ts:24-49:
机制设计意图:菜单首次渲染时若 npm 最新版本还未拿到,等 fetch 完成自动 abort 当前 prompt,重渲染时显示最新版本号。
Bug:src/lib/version-service.ts:4 的
fetchLatestVersion在网络失败/超时时返回null(而非抛错)。一旦返回 null,latestVersion === null永远成立,每次while循环都会给同一个已 resolved 的fetchPromise再挂一个.then(() => controller.abort()):.then回调会立刻同步触发controller.abort()在 select prompt 还未开始等待用户输入时已被触发AbortPromptError→catch里continue→ 重新渲染 → 又挂 .then → 又立刻 abort按键无响应是因为 select 每次都在 fast-path 抛错退出,连键盘事件循环都没进入。
复现条件
修复建议
引入
fetchCompleted标志,fetch 完成(无论成败)后跳过后续 abort 挂载:这样不论 fetch 成功(返回版本号)还是失败(返回 null)后,第二次进入循环都不会再挂 abort 钩子。
环境信息
api.qnaigc.com其他信息
tool-menu.ts的通用菜单逻辑问题