Skip to content

fix(core)!: kernel:ready 抛错在两个内核上一律失败 boot(#5170) - #5258

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5170-kernel-ready-unified-failure
Aug 4, 2026
Merged

fix(core)!: kernel:ready 抛错在两个内核上一律失败 boot(#5170)#5258
os-zhuang merged 1 commit into
mainfrom
claude/issue-5170-kernel-ready-unified-failure

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5170

按单上记录的决策落 方案 A(统一为传播):kernel:ready handler 抛错在 ObjectKernelLiteKernel 上一律失败 boot。B/C 未实现。

证据闸(改行为之前先做的摸底)

派发词要求先摸清 blast radius,结论是 小到零,可以推进:

  1. LiteKernel 上可达的 kernel:ready handler:全仓 56 处非测试注册点(git grep -n "hook('kernel:ready'" origin/main),其中能出现在 LiteKernel 组合里的是 ObjectQLPluginHonoServerPluginAppPluginAutomationServicePlugin、dispatcher/rest 系列。逐个读过:它们要么是 warn-and-skip,要么自带 try/catch(例如 automation 的 syncFlowsFromProtocol 在没有 objectql 时只 warn —— 实测日志里就有 [Automation] flow pull from ObjectQL registry failed: [Kernel] Service 'objectql' not found,并不抛)。没有一个在健康 boot 上抛错
  2. LiteKernel 启动的测试面:git grep -ln "LiteKernel" 命中的全部 booting 套件都跑过了(见下),零失败——即没有任何既有测试是"靠吞异常才跑得起来"的。
  3. 因此不触发"停下回报 needs_decision"的条件。

改法

内核基类新增 triggerHookOrThrow(传播版分发器),LiteKernel 只在 kernel:ready 这一处用它:

  • 为什么不是直接改调 context.trigger:triggerHook 那句 logger.debug('Triggering hook: ...') 是 LiteKernel boot 日志的一部分,换成 context.trigger 会静默丢掉它;新增一个并列的具名分发器还能把"哪些钩子该 fail-loud、哪些该 fail-soft"这件事写在类型/命名上,而不是靠调用点的注释。
  • 为什么不是给 triggerHook 加参数:两种语义是两种契约,不是同一函数的开关;两个分发器各自带上"何时用我"的 docblock,下一个人加钩子时要做的是选一个,而不是记住一个布尔。
  • 失败后 state = 'stopped',对齐 ObjectKernel.bootstrap() 的 catch —— 失败的 boot 不该继续读作 running。原始错误原样抛出,不包装。
  • 其余钩子(kernel:bootstrapped / kernel:listening / kernel:shutdown)在 LiteKernel 上语义不变,按派发词"本单只裁 kernel:ready"。

测试

两边对称的回归用例(补在既有 lifecycle ordering 用例旁),外加一条"其余钩子仍 fail-soft"的 pin,让将来任何扩大化都必须是显式改动:

  • kernel.test.ts — 抛错的 ready handler ⇒ bootstrap() reject 原始错误、后续 ready handler 不跑、bootstrapped/listening 不触发、getState() === 'stopped'
  • lite-kernel.test.ts — 同上一条(这条在改动前是失败的:AssertionError: promise resolved "undefined" instead of rejecting,即原先 boot 照常成功);外加 keeps fail-soft dispatch for hooks other than kernel:ready

顺带订正的措辞(仅此两处)

验证(全部在容器共享锁下跑)

packages/core          Test Files 27 passed | Tests 422 passed
packages/core          typecheck  Tasks: 2 successful
packages/plugin-email  Test Files 15 passed | Tests 231 passed  + typecheck
packages/runtime       Test Files  7 passed | Tests  51 passed   (app-plugin / dispatcher / route-parity / server-timing)
packages/client        Test Files  4 passed | Tests  16 passed
qa/http-conformance    Test Files  2 passed | Tests  46 passed
connector-rest/mcp/slack  3+2+3 files, 16+8+23 tests passed
service-automation     Test Files 55 passed | Tests 665 passed

changeset

.changeset/kernel-ready-unified-failure.md,按行为变更来写:点名受影响的人(vitest / serverless / edge 上 ready handler 会抛的宿主),写明"以前也不是完全静默 —— 是一条 Hook handler failed: kernel:ready 的 ERROR 日志",并给出自查方法(在既有日志里搜这句)与出路(工作若真的可选,就在 handler 内部自己 catch)。

范围外发现


🤖 Generated with Claude Code

https://claude.ai/code/session_01Pbu27iNUfQCHeuS551Rqo7


Generated by Claude Code

…er fails the boot on LiteKernel too (#5170)

The two kernels ran one hook name through two dispatchers with opposite
failure semantics: ObjectKernel's `context.trigger` is a bare awaited loop
that never catches (a throwing `kernel:ready` handler fails the boot), while
LiteKernel's `triggerHook` wraps every handler in try/catch and continues
("Continue with other handlers even if one fails"), so the same plugin code
got one error log and a "✅ Bootstrap complete".

`kernel:ready` is the only correct moment for a plugin to assert that a
precondition it DECLARED was actually delivered — the service registry is
still filling during init() — so every "declare it and we refuse to start if
we cannot honour it" gate lives there. On LiteKernel (vitest/serverless/edge)
those gates were silently downgraded to a log line while the process came up
and served traffic without the guarantee it had announced.

`triggerHookOrThrow` is the propagating dispatcher, used by LiteKernel for
`kernel:ready` only; the failed boot leaves the kernel 'stopped' rather than
'running', mirroring ObjectKernel.bootstrap()'s catch. The other lifecycle
hooks keep LiteKernel's isolating dispatch, pinned by a test so widening it
has to be a deliberate change (filed as #5257).

Blast radius surveyed before the change and verified after: core (422),
client, runtime, http-conformance, connector-{rest,mcp,slack},
service-automation (665) all pass unchanged — nothing relied on the swallow.

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

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

Request Review

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

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/core, @objectstack/plugin-email.

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

  • content/docs/ai/actions-as-tools.mdx (via @objectstack/core)
  • content/docs/ai/knowledge-rag.mdx (via @objectstack/core)
  • content/docs/ai/natural-language-queries.mdx (via @objectstack/core)
  • content/docs/automation/flows.mdx (via @objectstack/plugin-email)
  • content/docs/automation/webhooks.mdx (via @objectstack/core)
  • content/docs/concepts/north-star.mdx (via packages/core)
  • content/docs/deployment/environment-variables.mdx (via @objectstack/plugin-email)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/core)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/core)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/core)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/core)
  • content/docs/kernel/services.mdx (via @objectstack/core)
  • content/docs/permissions/authentication.mdx (via @objectstack/core)
  • content/docs/permissions/authorization.mdx (via packages/core)
  • content/docs/plugins/anatomy.mdx (via @objectstack/core)
  • content/docs/plugins/development.mdx (via @objectstack/core)
  • content/docs/plugins/index.mdx (via @objectstack/core)
  • content/docs/plugins/packages.mdx (via @objectstack/core, @objectstack/plugin-email)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/core)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/core, @objectstack/plugin-email)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/core)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/core)
  • content/docs/releases/implementation-status.mdx (via @objectstack/core)
  • content/docs/releases/v12.mdx (via @objectstack/core)
  • content/docs/releases/v15.mdx (via @objectstack/core)
  • content/docs/releases/v17.mdx (via @objectstack/core)

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.

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

Development

Successfully merging this pull request may close these issues.

core: kernel:ready 钩子抛错在 ObjectKernel 上失败 boot、在 LiteKernel 上被吞成一条 error 日志 —— 同一钩子两套失败语义

2 participants