发现自 #4813 的实现过程(PR 见下)。未认领。 与 #4813 是不同的 bug —— 修好 #4813 之后它照旧复现。
现象
一条成功的 os migrate 子命令(JSON 正常输出、✅ Graceful shutdown complete 正常打印、stderr 全空)退出码是一个每次都不一样的非零值:
cd examples/app-crm
for i in 1 2 3; do
rm -f /tmp/t.db*
OS_DATABASE_URL="file:/tmp/t.db" node ../../packages/cli/bin/run.js migrate recorded-by --json >/dev/null 2>&1
echo "run $i exit=$?"
done
# run 1 exit=208
# run 2 exit=171
# run 3 exit=176
同一条命令在其它时刻还观察到 62、163、169。#4813 的正文里记录的 214 / 238 也是同一个现象。
对照组 —— 同一个 bin/run.js,不走 schema stack 的子命令干净退出 0:
node ../../packages/cli/bin/run.js --version → exit=0
node ../../packages/cli/bin/run.js --help → exit=0
所以问题不在 CLI 框架本身,而在 bootSchemaStack 系子命令的退出路径上。
与 #4813 的关系(重要,别合并成一条)
#4813 是「内核的 init/start 超时守卫定时器不清除,进程干完活还空转 120 秒」。它不是这条的原因:
|
挂起时长 |
退出码 |
| #4813 修复前 |
122.4s |
163 |
| #4813 修复后 |
3.1s |
62 / 169 / 208 / 171 / 176 |
两次都是同一个构建链、同一条命令,唯一差别是 packages/core/src/kernel.ts 的守卫改动。时长被修好了,退出码没有。
这一点值得单独强调,因为 #4813 的正文把不稳定退出码归因于「进程挂太久被外部杀掉」。现在证明不是 —— 没有任何东西杀它,它自己在 3 秒后退出,退出码依旧随机非零。那个归因是错的,修好挂起并不会顺带修好退出码。
为什么值得修
一条成功的命令返回非零码,对任何按退出码判断成败的调用方都是直接的假失败:CI 步骤、set -e 脚本、Makefile、容器 entrypoint。而且它是随机的,所以「特判某个码」这种兜底也做不了。
--json 的存在说明这条命令本来就是设计给程序消费的;程序消费的第一件事就是退出码。
线索(未定位到根因)
- 值域看着像 0–255 的随机数,而不是某个有意义的错误码 —— 更像原生层异常退出(退出时的 native addon 崩溃 / abort),而不是某处
process.exit(n) 传了业务数字。
- stderr 全空,没有堆栈、没有
unhandledRejection、没有 Warning:,Node 侧看起来一切正常 —— 这也支持「JS 之后的阶段出的事」。
packages/core/src/utils/env.ts 有 safeExit(code = 0),但 CLI 的 migrate 路径里没有直接调用点,值得从那里往下追。
- 数据源是 libsql/sqlite(
OS_DATABASE_URL="file:…"),teardown 时关连接池的原生句柄是一个值得先看的嫌疑点。
复现
cd examples/app-crm
rm -f /tmp/t.db*
OS_DATABASE_URL="file:/tmp/t.db" node ../../packages/cli/bin/run.js migrate recorded-by --json
echo "exit=$?"
Refs #4813、#4747。
发现自 #4813 的实现过程(PR 见下)。未认领。 与 #4813 是不同的 bug —— 修好 #4813 之后它照旧复现。
现象
一条成功的
os migrate子命令(JSON 正常输出、✅ Graceful shutdown complete正常打印、stderr 全空)退出码是一个每次都不一样的非零值:同一条命令在其它时刻还观察到
62、163、169。#4813 的正文里记录的214/238也是同一个现象。对照组 —— 同一个
bin/run.js,不走 schema stack 的子命令干净退出 0:所以问题不在 CLI 框架本身,而在
bootSchemaStack系子命令的退出路径上。与 #4813 的关系(重要,别合并成一条)
#4813 是「内核的 init/start 超时守卫定时器不清除,进程干完活还空转 120 秒」。它不是这条的原因:
两次都是同一个构建链、同一条命令,唯一差别是
packages/core/src/kernel.ts的守卫改动。时长被修好了,退出码没有。这一点值得单独强调,因为 #4813 的正文把不稳定退出码归因于「进程挂太久被外部杀掉」。现在证明不是 —— 没有任何东西杀它,它自己在 3 秒后退出,退出码依旧随机非零。那个归因是错的,修好挂起并不会顺带修好退出码。
为什么值得修
一条成功的命令返回非零码,对任何按退出码判断成败的调用方都是直接的假失败:CI 步骤、
set -e脚本、Makefile、容器 entrypoint。而且它是随机的,所以「特判某个码」这种兜底也做不了。--json的存在说明这条命令本来就是设计给程序消费的;程序消费的第一件事就是退出码。线索(未定位到根因)
process.exit(n)传了业务数字。unhandledRejection、没有Warning:,Node 侧看起来一切正常 —— 这也支持「JS 之后的阶段出的事」。packages/core/src/utils/env.ts有safeExit(code = 0),但 CLI 的 migrate 路径里没有直接调用点,值得从那里往下追。OS_DATABASE_URL="file:…"),teardown 时关连接池的原生句柄是一个值得先看的嫌疑点。复现
Refs #4813、#4747。