Skip to content

fix(cli): os doctor 说出配置载入失败的原因,不再只说一句「载入不了」(#5403) - #5410

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-5403-doctor-config-error-cause
Aug 5, 2026
Merged

fix(cli): os doctor 说出配置载入失败的原因,不再只说一句「载入不了」(#5403)#5410
baozhoutao merged 1 commit into
mainfrom
claude/issue-5403-doctor-config-error-cause

Conversation

@baozhoutao

@baozhoutao baozhoutao commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #5403

前提核验(先于实现)

issue 引用的行号 98369a86b:1212 已因同日 churn 漂移,但事实成立origin/main(4b61cf3)上该 catch 仍为:

      } catch {
        printWarning('Could not load config for analysis (config checks skipped)');
        hasWarnings = true;
      }

packages/cli/src/commands/doctor.ts:1320。不带绑定,error 当场丢弃;这句话是裸 printWarning,不是 HealthCheckResult,所以 --verbose 根本没有 fix 可展开 —— issue 正文「没有任何旗标能让操作者看到更多」的判断经核实为真。

采用的方案:PM 指定的方案二(未退回最小改法)

printWarning 改为一条常规 HealthCheckResult,与 Environment files / Tenancy posture同一个渲染器、同一条 --verbose 展开规则。

方案二在本文件里有一处需要处理的时序事实,但不构成语义冲突:环境检查那个 results.forEach 在报告较早处就跑完了,而 config 载入发生在它之后 —— 把行 push 进 results 让老循环去打是打不出来的。解法不是重排报告(那会把结论挪离产生它的那一步,是超出判定面的输出契约变动),而是把渲染规则从循环体里提成具名函数 renderHealthCheckResult(),两处共用。这样「统一渲染 + 统一 verbose 展开 + 消灭裸 printWarning 旁路」三条都成立,报告顺序一字未动。行同时仍 push 进 results,让该轮的记录保持完整(今日惰性:末尾 fix 汇总按 error 过滤)。

效果

$ cat objectstack.config.ts
throw new Error('this config is genuinely broken');

$ os doctor
  → Loading configuration for analysis...
  ⚠ Config load          Could not load config for analysis (config checks skipped) — this config is genuinely broken

⚠️  Environment is functional but has some warnings.
   Run with --verbose to see fix suggestions.

$ os doctor --verbose
  ⚠ Config load          Could not load config for analysis (config checks skipped) — this config is genuinely broken
      → `os serve` loads this same file the same way — bundle-require, under the `.env*`
      cascade named above (#5397) — and prints this error in full, so a config that
      lands here is one the server cannot boot either.
      The config-aware checks were SKIPPED, not passed: spec version, circular
      dependencies, unused objects, orphan views, dashboard integrity.
      cause: this config is genuinely broken

(以上为真机 in-process 运行捕获的输出,非手写示意。)

四条刻意的取舍

  • 原话照引,不改写(fix(cli): os doctor 指名道姓报告非法 OS_TENANCY_POSTURE 并非零退出 (#5382) #5390 姿态)。配置载入的失败来自四个不同权威:用户自己的 throw、esbuild 的打包诊断、Node 的模块解析、loadConfig() 自己的 "no default export"。doctor 没有立场把它们总结得比它们自己更好。
  • cause 进 message,而不只进 fix Environment files 把 cause 只放 fix 是对的 —— 那一行本身已把结论说完整,cause 是脚注。这里 cause 就是结论:没有它,这一行只说了「出了点没被指名的问题」,默认运行仍然严格少于 os serve。所以平铺一行放收敛后的引文,--verbose 给未截断的原文。
  • 折叠而非取首行。 esbuild 的首行恰是它最没信息量的一句(Build failed with 1 error:,文件与原因在下一行)。空白折叠 + 超长截断,词序全是上游的,只有换行和过长的尾巴是我们的。
  • 档位不变。 仍是 warning,doctor 跑完其余检查,仍 exit 0。那句话本身原样保留为该行开头 —— 它被两份 changeset 引用、被本文件四处注释引用,更要紧的是兄弟测试用它的缺席表示「配置载入成功」,改写会让那些断言在「没有任何东西能匹配」的空理由下继续变绿。

反向验证(方向:红,事前预测)

预测:仅把调用点回退为裸 printWarning(builder 保留导出),则 8 条 builder 单测保持绿(被测对象未动),3 条正向 e2e 转红,2 条否定式 e2e 仍绿(它们断言「不出现」,结构上不可能察觉该回退 —— 这正是正向用例存在的理由)。实测逐条命中:

 ✓ ...quotes the thrown message, in the row AND in the verbose detail
 ✓ ...(其余 7 条 builder 单测全绿)
 × ...prints the cause on a plain run — the issue's own repro
   → expected '…' to contain 'this config is genuinely broken'
 × ...expands the full detail under --verbose, and only under --verbose
   → expected '…' to contain 'cause:'
 × ...surfaces a SYNTAX error's file and reason, not just "build failed"
   → expected '…' to contain 'objectstack.config.ts'
 ✓ ...does NOT fire for a config that only needed its .env — #5397 is not regressed
 ✓ ...says nothing at all when the config loads

承重件:#5397 的 async overlay 未受影响

loadConfig() 仍在 withDotenvOverlayAsync 内,一字未动 —— 同步 overlay 包不住 bundleRequire 的动态 import()。新增用例正面钉住:只因缺 .env 值才抛错的配置不得走到这条路径(否则该行会出现),且 overlay 事后不留残留。

测试

packages/cli/src/commands/doctor-config-load-cause.test.ts(新增 13 例,全部按车道纪律显式 60_000 超时):

命令与逐条结果见本 PR 的「验证」评论(全包 728 例绿、typecheck 绿、六项仓库门禁与 eslint 绿)。

越界情况

改动仅落在 packages/cli/src/commands/doctor.ts + 同目录测试 + 一份 changeset,与派发口径的文件面一致。已按消费半径搜过 Could not load config / config checks skipped / Loading configuration for analysis 全仓引用:除本文件与测试外只剩两份历史 changeset(#5382 / #5397 的),它们记述当时事实,不应改写。

顺带核验发现、未在本 PR 修的一条,已另行开单:#5412 —— 同文件 readInstalledPackageEntries() 的 catch 把「可选包没装」和「ledger 里的 manifest 损坏」当成同一件事,导致 ADR-0120 D5e 的 unique-scope 建议静默少报并打出 ✓ ... No unconfirmed installation-wide uniques(false PASS)。同族、低一层,但属于另一处 catch、另一条检查的判定面,且「读不到 ledger 时还能否声称 clean」是需要判定的决策点,不宜夹带。

The config-analysis `catch` took no binding — `catch {` — so the error
object was discarded at the point it was caught. A genuinely broken
config produced `Could not load config for analysis (config checks
skipped)` and nothing else, under any flag: the sentence came from a
bare `printWarning`, not from a `HealthCheckResult`, so `--verbose` had
no `fix` to expand. `os serve`, in the same directory, prints the error
in full — the diagnostic command returned strictly less than the command
it exists to diagnose.

#5382 / #5387 / #5397 fixed this sentence's ATTRIBUTION. This fixes what
it SAYS now that it is finally saying it about the right thing.

- `configLoadFailureCheck(err)` builds an ordinary `HealthCheckResult`,
  quoting the thrower verbatim (#5390's `cause:` posture) rather than
  paraphrasing: the failure can come from user code, esbuild, Node's
  resolver, or `loadConfig()` itself.
- `renderHealthCheckResult()` extracts the environment block's rendering
  so the finding travels the same path and the same `--verbose` rule as
  `Environment files` / `Tenancy posture`, deleting the bare-printWarning
  bypass that had no `fix` channel at all.
- The row carries a whitespace-folded, bounded quote (esbuild's first
  line is its least informative one); `--verbose` carries the untruncated
  original.
- Gauge unchanged: warning, rest of the report runs, exit 0. The
  recognizable sentence survives verbatim as the head of the row —
  sibling tests assert its absence to mean "the config loaded".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 5, 2026 9:05am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli.

21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/cli)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli)
  • content/docs/automation/hook-bodies.mdx (via packages/cli)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/validating-metadata.mdx (via packages/cli)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli)
  • content/docs/plugins/index.mdx (via @objectstack/cli)
  • content/docs/plugins/packages.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli)
  • content/docs/releases/v16.mdx (via @objectstack/cli)
  • content/docs/releases/v17.mdx (via @objectstack/cli)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Copy link
Copy Markdown
Contributor Author

验证(正文「命令与结果见下方」指向此处)

会话:session_016FNvXhtSdnEGEfLEsMmvxh;基线 origin/main = 4b61cf3;专用 worktree,重活全程走 flock /tmp/os-heavy-verify.lock + --max-old-space-size=4096

全包测试

$ pnpm --workspace-concurrency=2 --filter @objectstack/cli test -- --maxWorkers=2
 Test Files  77 passed (77)
      Tests  728 passed (728)
   Duration  124.14s

typecheck

$ pnpm --workspace-concurrency=2 --filter @objectstack/cli typecheck
> tsc --noEmit
(无输出 = 通过)

doctor 四个文件合并跑(含三份兄弟测试,确认未回归)

$ pnpm exec vitest run --maxWorkers=2 \
    src/commands/doctor-config-load-cause.test.ts \
    src/commands/doctor-config-env-overlay.test.ts \
    src/commands/doctor-env-provenance.test.ts \
    src/commands/doctor-tenancy-posture-report.test.ts
 Test Files  4 passed (4)
      Tests  58 passed (58)

其中 doctor-config-env-overlay.test.ts:349 那条 expect(run.out).toContain('Could not load config for analysis (config checks skipped)') 原样保持绿 —— 新 message 以该句原文起头,是本 PR 刻意保留它的直接原因。

仓库门禁 / lint

$ node scripts/check-nul-bytes.mjs
check-nul-bytes: OK (scanned 5423 tracked text file(s); ... no raw NUL bytes).

$ grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' <改动的三个文件>
clean(本 PR 的测试文件里出现的 SGR 正则按纪律写作 \x1b 转义,不是原始字节)

$ pnpm check:role-word          → OK (43 baselined file(s), no new occurrences).
$ pnpm check:adr-anchors        → OK (29 anchored file(s), every governing ADR still referenced).
$ pnpm check:error-code-casing  → no lowercase error codes in 2889 scanned file(s).
$ pnpm check:release-notes      → OK
$ pnpm check:doc-authoring      → 362 files clean
$ pnpm exec eslint --no-inline-config <改动的两个 ts 文件>   → 无输出

反向验证 的事前预测与逐条实测已写在 PR 正文;要点是 2 条否定式 e2e 在回退下仍绿,这是断言「不出现」的结构性上限,不是覆盖漏洞 —— 正向用例即为此存在。


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

os doctor 的 config 分析 catch 丢弃了 error 本身 —— 配置真坏掉时只说「载入不了」,不说为什么

2 participants