Skip to content

fix(service-datasource): datasource fail-fast 认得「工作区未构建」这个成因 (#5794) - #6302

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-5794-unbuilt-workspace-hint
Aug 7, 2026
Merged

fix(service-datasource): datasource fail-fast 认得「工作区未构建」这个成因 (#5794)#6302
hotlong merged 1 commit into
mainfrom
claude/issue-5794-unbuilt-workspace-hint

Conversation

@hotlong

@hotlong hotlong commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #5794

前提复核(先做的一步)

issue 正文锚在 :687-701;当前 origin/main(dbe92a7e1)上该文件已长到 796 行,
这段代码在 :754-758 —— 按 issue 自己的提示以内容定位,措辞一字未变,前提成立

仓库里还有一份独立佐证:scripts/check-dev-prereqs.mjs(#5795 / #5863)的头注释逐字写着

- STILL LIVE. The only actionable line suggests two fixes that are both WRONG for this cause
... A separate services-lane issue off #5726 teaches that message to recognise the unbuilt
cause; this gate stops dev before it is reached.

那个 "separate services-lane issue" 就是本单。

抛出值上到底有什么(实测,不是猜)

判据要「结构化信号优先」,前提是先知道抛出值上真实可读的是什么。在本 worktree
(装了 node_modules、没 build)实测三种形状:

触发方式 err.code err.message 首行
ESM import('@objectstack/driver-sql') ERR_MODULE_NOT_FOUND Cannot find module '…/driver-sql/dist/index.mjs' imported from …
CJS require('@objectstack/driver-sql') MODULE_NOT_FOUND Cannot find module '…/driver-sql/dist/index.js'
包目录整个不在 ERR_MODULE_NOT_FOUND Cannot find package '@objectstack/driver-sql' imported from …

(第三行就是 issue 正文引的那种形状。)三种都带结构化 code

同时发现:这个信号原本在到达消息之前就被丢掉了attemptConnect 的末尾 catch 只
errMsg(err) 传给 handleFailure,而 handleFailure 的签名收的是 reason: string
—— 字符串化的那一刻 err.code 就没了。所以本 PR 除了换文案,还要把抛出值本身
传下去。

改了什么

新增 packages/services/service-datasource/src/connect-failure-remedy.ts:把 fail-fast
报错的收尾句按成因分成两支,两段文案 + 为什么这么写的理由都放在这一个文件里。

实际输出(用真实的 bootCritical default datasource 跑出来的)

改之前 / 其它成因(逐字未动):

datasource 'default': connect failed — connect ECONNREFUSED 127.0.0.1:5432. (declared
boot-critical by the host — … ⇒ fail-fast per ADR-0062 D5). Fix the datasource
configuration, or set OS_ALLOW_DRIVER_CONNECT_FAILURE=1 to boot anyway and serve errors
until it is reachable.

改之后 / 未构建成因:

datasource 'default': connect failed — Cannot find module
'…/@objectstack/driver-sql/dist/index.mjs' imported from …. (declared boot-critical by
the host — … ⇒ fail-fast per ADR-0062 D5). The driver package could not be LOADED at all
— it is not installed, or its build output is missing. That is a build precondition, not
a datasource fault: the configuration is fine, and no boot-time override can make a
driver that does not exist answer a query. Run `pnpm install && pnpm build`, then start
again.

收尾句之前的部分(datasource 名、底层原因、绑定对象清单、ADR-0062 D5 的裁定)一字未改。

零行为变化

fail-fast 的判定与时机、抛出的错误类型(仍是 plain Error)、保留的连接状态与上报给
engine 的 markDatasourceUnavailable、以及设了 OS_ALLOW_DRIVER_CONNECT_FAILURE 时的
降级启动路径与横幅 —— 全部不变,并且都单独钉住了。

反向验证(方向是预判再跑的)

预判:把分类关掉(让 connectFailureRemedy 恒返回通用文案),新的未构建 pin 应该全红,
而所有「其它成因逐字不变」与「零行为变化」的 pin 应该全绿
—— 因为通用文案没被这次改动碰过。

实测与预判一致:

FAIL … > by err.code alone: ERR_MODULE_NOT_FOUND with an unrecognisable message
FAIL … > by err.code alone: CJS require() reports MODULE_NOT_FOUND
FAIL … > by message alone: ESM's `Cannot find package`, no code
FAIL … > by message alone: the factory-wrapped optional-driver form, no code
FAIL … > the real unbuilt-worktree shape names the build fix and nothing else
 Tests  5 failed | 60 passed (65)

红的正好是「两个判据」那一组的 5 条,其余 60 条全绿。两点值得记下来,免得下一位读成别的:

  1. keeps everything ABOVE the remedy 这条没有变红,而且它就该不变红 —— 它只断言
    共享前缀,而分类开关碰不到前缀。
  2. isUnbuiltWorkspaceFailure 的那组单测也没有变红 —— 我关掉的是分派,不是
    分类器本身。也就是说分类器的 pin 和分派的 pin 各自独立承重,不是一组的复读。

测试

新增 20 条 pin,分四组:两个判据(code / message 各有覆盖)、其它成因逐字不变、
零行为变化、以及分类器本身的单测(含两条负例:真连接失败、以及 ERR_DLOPEN_FAILED
的原生 addon ABI 不匹配 —— 那是要 rebuild,不是工作区没构建,必须不被认成未构建)。

pnpm --filter @objectstack/service-datasource test     Test Files 12 passed (12)   Tests 264 passed (264)
pnpm --filter @objectstack/service-datasource typecheck  tsc --noEmit — clean
pnpm --filter @objectstack/service-datasource build      CJS/ESM/DTS build success
packages/runtime(4 个 datasource 测试文件)                Test Files 4 passed (4)     Tests 67 passed (67)
check:nul-bytes / check:empty-changeset / check:engine-double-contract
check:error-code-casing / check:type-check-coverage       全部 OK

消费半径已扫

改的是 handleFailure 的消息,凡是跑到这条路径的包都读它,所以按调用者而不是按改动的
包来扫。全仓 grep 那句通用文案,packages/services/service-datasource 之外没有任何测试
钉它;唯一的另一处出现是 packages/objectql/src/driver-connect-errors.ts
DriverConnectError —— 另一个包、另一个类、另一条路径,本 PR 未触及(见下)。
消费方 packages/runtime 的 4 个 datasource 测试文件跑通,日志里能直接看到凭据失败与
不受支持驱动这两个成因的消息逐字未变

刻意划出去的边界

  • 降级横幅(设了 OS_ALLOW_DRIVER_CONNECT_FAILURE 那一支)未改。 那一支不给建议,
    它描述的是使用者已经主动选进去的状态;而且 fail-fast 这句不再推销那个开关之后,
    未构建这个成因基本就到不了那一支了 —— 承重的修法在 fail-fast 这边。
  • packages/objectqlDriverConnectError 未改。 它带着同样的两句建议,但属于
    另一个包的另一条路径(driver 已注册、其 connect() 拒绝),而未构建的工作区里 driver
    根本构造不出来、到不了那里。它是否真能被模块解析失败命中,我没有实测,所以不在
    本 PR 里顺手改,按 Prime Directive chore: version packages #10 另立观察类 finding。

Generated by Claude Code

connect 失败的 fail-fast 报错原本只有一句收尾建议,不分成因:
"Fix the datasource configuration, or set OS_ALLOW_DRIVER_CONNECT_FAILURE=1
to boot anyway"。对「数据库真连不上」它是对的;对「驱动包没构建」两半都有害
—— 配置本来就是好的,而设开关只会让半个工作区宣称启动成功、然后对每个请求
报错。唯一有效的修法 pnpm build 一个字都没提。

现在按成因选收尾句:底层错误是模块解析失败时(ESM import() 的
err.code === 'ERR_MODULE_NOT_FOUND'、CJS require() 的 'MODULE_NOT_FOUND';
code 被 re-throw 丢掉时退回 Cannot find module / Cannot find package 文本),
消息只给一个正确修法 `pnpm install && pnpm build`,并且不提那个逃生开关。

判据复用 @objectstack/types 的 isModuleNotFoundError(framework#3265 起的
唯一所有者),不另起一份;结构化的 err.code 原本在 handleFailure 只收
reason: string 时被丢弃,所以抛出值本身现在也一并传入。

纯诊断分类,零行为变化:fail-fast 的判定与时机、抛出的错误类型、保留的连接
状态、以及 OS_ALLOW_DRIVER_CONNECT_FAILURE 的降级启动路径全部不变;其它成因
的消息逐字未动。

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

vercel Bot commented Aug 7, 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 7, 2026 1:29pm

Request Review

@github-actions github-actions Bot added the size/m label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 1 changed package(s). ✅

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 7, 2026
@hotlong
hotlong marked this pull request as ready for review August 7, 2026 13:33
@hotlong
hotlong enabled auto-merge August 7, 2026 13:33
@hotlong
hotlong added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit d92ed03 Aug 7, 2026
24 checks passed
@hotlong
hotlong deleted the claude/issue-5794-unbuilt-workspace-hint branch August 7, 2026 13:54
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/m tests tooling

Projects

None yet

2 participants