Skip to content

os doctor 载入 objectstack.config.ts 时不带 .env* overlay —— 配置文件自己读 process.env 时,doctor 报「Could not load config」而 os serve 同目录正常启动 #5397

Closed

Description

@baozhoutao

发现于 #5387(doctor 对齐 serve 的 .env* 读取顺序)实施过程中的顺带核验;本单只记录,不在该 PR 里改 —— 原因写在下面。

事实

#5387 之后,doctor 会按 os serve 的顺序读 .env*,但读到的值只在需要它的那一次读取周围临时生效(withDotenvOverlay),刻意不合并进整轮运行的 process.env。今天套上 overlay 的只有 env 派生检查(posture)那一处;loadConfig() 没有套。

两条命令的顺序因此仍然不同:

  • serve:dotenvFlow.config()(packages/cli/src/commands/serve.ts:520)→ 之后才 bundleRequire 载入用户的 objectstack.config.ts。配置文件顶层读到的 process.env 含 .env* 的值。
  • doctor:readDotenvFiles()(不写 process.env)→ loadConfig()(packages/cli/src/commands/doctor.ts 的 config 分析块)。配置文件顶层读到的 process.env 不含 .env* 的值。

后果

配置文件在顶层读环境变量是常见写法(数据源 URL、开关)。当变量只写在 .env 里时:

  1. 值不同 —— 依赖该值的结构(条件声明的 object / datasource)在 doctor 与 serve 眼里不是同一份,doctor 的 config 检查判定的是一份服务器不会运行的配置;

  2. 更响的一种 —— 配置在缺值时抛错(throw new Error('OS_DATABASE_URL is required') 之类),doctor 会落进 config 分析那个很宽的 try,打印

    ⚠ Could not load config for analysis (config checks skipped)
    

    记一个 warning、exit 0,而 os serve 在同一个目录正常启动。这句话正是 os doctor 对非法 OS_TENANCY_POSTURE 退出码 0 并报告「环境功能正常」—— 抛错被 config 分析的宽 catch 吞成一句「Could not load config」 #5382 判定为「归因错误」的那一句:配置本身没问题,问题是 doctor 没把 .env 交给它。

为什么没有顺手改

给 loadConfig() 套上 overlay 会改变既有 config 检查(circular deps / unused objects / orphan views / dashboard integrity / spec 版本)的输入,可能新增或消除 warning —— 属于诊断输出的另一处契约变化,超出 #5387 派发时被限定的判定面(该单的口径明确要求:若改变既有检查的判定超出预期就停下报告)。

需要决定的是同一个问题的另一半:doctor 的 config 分析应当在哪一份环境下进行 —— serve 的那份(overlay 覆盖 loadConfig(),与运行时一致,但既有检查的判定面会变),还是当前这份(只覆盖显式声明的 env 输入,判定面不变,但 config 那条路径与 serve 仍不一致)。

现状

零件已经就位,不需要新机制:readDotenvFiles() / withDotenvOverlay() 就在同一个文件里(#5387 引入,packages/cli/src/commands/doctor.ts),把 loadConfig() 的调用包进去即可。packages/cli 下没有任何测试钉 doctor 的 config 载入与 .env 的关系。

Depends-on: #5387(其 PR 引入的两个零件是本单的前置)。与 #4801、cloud#1020 同属「诊断面与运行时不一致」家族。


Generated by Claude Code

Activity

  1. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    ContributorAuthor

    分诊(cli 车道 PM,session_01VkPSGsX9o17MsGv3Lbxu2w,2026-08-05):入队 pm:queue,并附方向裁定(留否决窗口)。本单是 #5387 同一不变量(诊断面与 serve 同见)的最后一块:loadConfig() 无 overlay,配置文件顶层读 process.env.X(X 仅在 .env)时 doctor 与 serve 仍见不同值,缺值抛错还会落回 #5382 修掉的那句错误归因。方向:给 loadConfig() 套用 #5398 已就位的 withDotenvOverlay,config 检查判定因此变化的部分如实呈现(这正是修复内容,不是副作用 —— 与 #5398 对 D5e 建议的处理同一姿态);来源行照 #5398 口径归因。派发时范本引 PR #5398。Depends-on: #5387 已由 PR #5398 落地解除。维护者可否决。


    Generated by Claude Code

  2. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    ContributorAuthor

    🔒 认领:PM 循环第 5 轮(cli 车道)
    会话:session_01VkPSGsX9o17MsGv3Lbxu2w
    分支:claude/issue-5397-doctor-config-env-overlay
    Worktree:objectstack-issue-5397
    域:domain:cli
    文件面:packages/cli/src/commands/doctor.ts + 测试 + changeset。⛔ 不动 serve/dev/start/env.ts。与同批 #5399(runtime/metadata/rest 注释 + runtime 测试)不相交。⚠️ 依赖 PR #5398(withDotenvOverlay 等零件)—— 其在合并队列中,派发词已要求 dev 从 origin/main 取到后再动手;若未合先等。

    执行口径见上一条分诊评论:loadConfig() 套 overlay,config 检查判定的变化如实呈现(是修复内容非副作用),来源行照 #5398 口径。范本 PR #5398。


    Generated by Claude Code

  3. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    ContributorAuthor

    ✅ 验收通过 —— PR #5402,转 ready 挂合并队列(CI 23 项已全绿)

    对实况复核(cli 车道 PM,session_01VkPSGsX9o17MsGv3Lbxu2w):

    • 裁定兑现:loadConfig() 套上 run() 顶部同一份 dotenvReading(一轮一次解析,两处共用,不产生可分歧的第二次读取);判定变化按裁定作为修复内容如实呈报(被跳过的 config 检查组开始运行、真坏配置照旧报警不被消音 —— 各有钉)。
    • withDotenvOverlayAsync 的必要性论证与验证俱全:config 顶层在 bundleRequire 动态 import() 内,同步版 finally 会在 promise pending 时摘除 overlay —— 静默失效;反向验证 B 实锤(换同步版的失败输出与完全不套逐字相同,这正是该变体承重的证明)。两 wrapper 共享同一套 apply/revert,策略只写一遍。
    • 来源纪律升级后仍守住:overlay 现在携带 config 想读的任意变量(不再限于声明子集),报告只报文件不报变量名(测试钉住);DOCTOR_ENV_INPUTS 漂移守卫绿(注释刻意不写具体变量名)。
    • 测试 10 新例(reject 时仍摘除是主线路径而非边角)、反向验证两方向预测逐条命中、整包 76 文件 / 715 用例绿、七道门 + eslint + 控制字符自查干净。
    • 衍生 os doctor 的 config 分析 catch 丢弃了 error 本身 —— 配置真坏掉时只说「载入不了」,不说为什么 #5403(config 分析 catch 丢弃 error、警告无因且 --verbose 展不开)已由 PM 分诊入队,留给下一任派发。

    转 ready 挂 auto-merge,跟到 MERGED。本单落地后「诊断面与 serve 同见」不变量三部曲(#5382 → #5387 → #5397)收官。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions