Skip to content

fix(core): both kernels agree a duplicate plugin registration supersedes, and say so out loud - #10094

Merged
os-elon merged 5 commits into
mainfrom
claude/issue-9864-dual-kernel-plugin-registration
Aug 20, 2026
Merged

fix(core): both kernels agree a duplicate plugin registration supersedes, and say so out loud#10094
os-elon merged 5 commits into
mainfrom
claude/issue-9864-dual-kernel-plugin-registration

Conversation

@os-elon

@os-elonos-elon commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Fixes#9864

Under the maintainer's ruling of 2026-08-19 (option B, recorded on the card): ObjectKernel and LiteKernel now agree that a duplicate plugin registration by nameoverwrites, and say so with an unmistakable warning. The ruling departs from the triage seat's recommendation (A: refuse loudly + an explicit supersede API) deliberately, and this PR implements B only — no new registration API, no config surface.

The split this ends

kernelbefore
ObjectKernel — the kernel os serve runsaccepted and overwrote, with no check and no distinguishing log line; the line Plugin registered: followed by the plugin name, an at-sign and the version printed twice, and read as two plugins running
LiteKernel — tests, serverless, edgethrew a bracketed-Kernel error reading Plugin 'NAME' already registered

(Both message shapes are written out in words above rather than with their real angle-bracket placeholders — GitHub's body sanitizer eats an angle-bracket fragment on write, and it ate exactly these two on this PR's first revision.)

One input, two meanings, and the production meaning was the silent one. This was the fourth measured instance of one contract implemented twice across the two kernels (#5170, #5282, #8357 adjacent), each found by a human reading both files side by side.

The overwrite itself is load-bearing and preserved: it is what lets a stack's own plugins entry supersede a plugin the CLI auto-registered earlier in the same boot (#9863's AuditPlugin). Every boot path that worked before works identically now; only its observability changes, which is why B needed no host census — the risk triage flagged against A ("A needs that census first or it turns quiet boots into hard failures") is not present here.

What landed

packages/core/src/plugin-registration.ts (new) — the contract, stated once, imported by both kernels. ObjectKernel still does not extend ObjectKernelBase (#5282), so a shared module is the sharing mechanism available, and it is the one plugin-order.ts and hook-dispatch.ts already use. Deliberately not exported from the package barrel: this declares existing behaviour, it does not mint a public supersede API.

The warning, verbatim:

Plugin superseded: 'com.objectstack.audit' — the later registration (v2.0.0) REPLACED the
earlier one (v1.0.0). Only the later instance is initialized and started; the earlier one is
discarded without ever running init(). Duplicate registration by name is last-one-wins on both
kernels by declared contract (#9864) — register the plugin once if that is not what you meant.

Why it cannot be confused with a first registration — four properties, each pinned by the test:

  1. A different verb, first tokenPlugin superseded:, not Plugin registered:.
  2. A different levelwarn, never info. Not decoration on the os serve path: the CLI's default kernel level is warn (DEFAULT_LOG_LEVEL, packages/cli/src/utils/log-level.ts), at which Plugin registered: is not emitted at all, and the boot-quiet window (BootLogCapture, BOOT_DIAGNOSTIC_FLOOR = 'warn') discards in-window info while replaying warn. An info-level notice would be invisible on exactly the boot path where the defect was measured. It is warn and not error by AGENTS.md's degradation rule: nothing that claims to persist fails to land, and the composition the host asked for is the one that boots.
  3. Both versions, in order — a plugin replaced by a differently-configured instance of itself is the expensive direction, and there the two names are identical; the versions are what say which instance survived.
  4. The consequence, stated — the earlier instance is discarded before it boots, so nobody has to infer whether two plugins are now running.

ObjectKernel additionally suppresses its Plugin registered: line for a superseding registration, so the count of those lines equals the number of plugins that will actually boot. A boot log now reads:

INFO Plugin registered: com.objectstack.audit@1.0.0
WARN Plugin superseded: 'com.objectstack.audit' — the later registration (v2.0.0) REPLACED …

packages/core/src/plugin-registration.contract.test.ts (new) — the dual-kernel pin, and the deliverable that shrinks the seam. Six cases written once and executed against both kernels through a thin adapter (describe.each), plus two ObjectKernel-only cases for the surface LiteKernel genuinely does not have (it emits no registration line and owns one map, so writing those into the shared table would assert 0 === 0 there — coverage it does not have). A fifth divergence now has to reproduce the bug in a case that already exists.

The teardown question, answered

The card put this in scope to answer, not assume: the displaced plugin has already been through pluginLoader.loadPlugin(), so if registration acquired anything on its behalf, today's overwrite would also be a leak.

It does not, and no teardown is owed. Registration is legal only while the kernel is idle (validateIdle() / ObjectKernel.use()'s own state check), so a supersede can only ever displace a plugin the kernel has never initialized; init(), start() and destroy() all run from bootstrap()/destroy() over the resolved order read out of the registry the displaced entry has already left. loadPlugin() itself is pure validation plus a name-keyed map write of its own — so it drops the displaced metadata for the same reason rather than accumulating it — and invokes nothing on the plugin. Calling destroy() on the displaced instance would be the bug, not the fix: it is the paired teardown for an init() that never ran.

Anything the displaced instance holds was acquired by the caller's own new, before use() was reached. Surveyed all 52 in-tree implements Plugin classes: 41 have a constructor body and every one normalizes options or builds in-memory helpers — the closest to a resource is HonoServerPlugin's new HonoHttpServer(), whose constructor only does this.app = new Hono() and whose socket opens at kernel:listening. AuditPlugin — the live #9863 case — is constructor(private readonly options = {}) {}, and its one resource (readAuditWriter, held so destroy() can flush) is created in init().

The contract test pins the structural half: across a full bootstrap and shutdown, the displaced instance's init, start and destroy are never called, while the survivor's all are.

#9863 discharged, not left riding

The card's own ⛔ clause was that #9863 "currently depends on the overwrite behaviour without saying so". B keeps the behaviour, so #9863 is not broken — but that was never the complaint. packages/cli/src/commands/serve.ts now names the dependency at both ends of the pair: at the AuditPlugin auto-registration (the superseded half) and at the stack plugins loop (the superseding half), each pointing at plugin-registration.ts and the dual-kernel pin, and calling out that the order is load-bearing too — the auto-registration must stay above the plugins loop or the option-less instance would win. #9863 itself is left open on its own question (whether os serve should grow an appAuditPluginOptions(config) helper like its SecurityPlugin sibling) with a comment recording that its dependency is now declared.

Verification

Reverse verification, from the committed fix (e95a7e87c), reverting only the two kernels with git restore --source=origin/main:

Tests 8 failed | 4 passed (12)
AssertionError: promise rejected "Error: [Kernel] Plugin 'com.objectstack.t…" instead of resolving

The direction is informative rather than uniform: the 4 that still passed are exactly the properties ObjectKernel already had (overwrite, last-one-wins, no teardown, loader map), so the file is not red for a trivial reason. No rebuild was needed for this ablation — the test resolves both kernels by relative path (./kernel.js, ./lite-kernel.js) inside the same package, not through the package's exports into dist/.

All results below are from c6d4381e0, the head commit of this PR.

checkresult
pnpm --filter @objectstack/core testTest Files 36 passed (36) · Tests 869 passed (869)
pnpm --filter @objectstack/runtime test (largest core consumer)Test Files 177 passed (177) · Tests 2649 passed (2649)
pnpm --filter @objectstack/cli typecheckexit 0, 0 errors (after building its dependency closure — unbuilt, it reports 139 TS2307-and-consequences that have nothing to do with this diff)
pnpm check:type-check-debt (the ratchet)33 ledger entr(ies) re-measured … none above its recorded number. surplus: none
packages/core raw tsc --noEmit count98, exactly its DEBT ledger entry — unchanged
pnpm check:kernel-hook-pairs✓ 4 dispatched kernel:* hook(s), each pinned in both kernel.test.ts and lite-kernel.test.ts
pnpm check:type-check-coverageOK — 64/77 workspace packages type-checked
pnpm check:nul-bytesOK (scanned 6370 text file(s) … no raw ASCII control bytes)
pnpm check:engine-double-contractOK — 325 pinned, 133 in the DEBT ledger, 2 exempt
check:test-source-alias · check:cross-package-test-inputs · check:slot-lookup · check:route-envelope · check:where-matcher · check:query-options-erasure · check:changeset-gate-self-tests · check:objectui-changeset · check-adr-0087-registration · check-changeset-no-major · check-empty-changeset · check-affected-docsall exit 0

The ratchet is worth one note: the first draft of the contract test used extensionless relative imports (the convention the rest of packages/core's tests follow) and added 4TS2835 errors, taking core from 98 to 102. check:type-check-debt reports surplus: none — every entry sits exactly at its measurement, so that would have been red. Repaired by writing the imports with explicit .js extensions, as 1345 other test files in this repo do; no ledger number was touched.

Gate set derived with node scripts/pm/dispatch-gates.mjs (no paths passed — it reads the change set from the merge base itself), re-derived unchanged after the final commit.

Scope

packages/core (both kernels + the new shared module and its pin), a comment-only change in packages/cli/src/commands/serve.ts, and the changeset. No new public registration API, no config surface, no content/docs/releases/, no governed surface. In-flight check before the first edit: all 11 open PRs' file lists read — none touches packages/core; the four sibling dev cards in flight declare packages/metadata-protocol, packages/lint, packages/objectql and packages/plugins/plugin-audit.


Generated by Claude Code

… duplicate plugin registration
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
…t, add changeset
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
@github-actions

github-actionsBot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/core, touching 7 documentable anchor(s).

27 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json eb2bebe532aad5fdc99007923520df6d9d1dd9c9.

5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 45 of 221 client-bound route-ledger rows — the other 176 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage

Coarse fallback — 39 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json eb2bebe532aad5fdc99007923520df6d9d1dd9c9packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1b3d64fd7b3499db434c49374087f37a4531dc0c — the merge of head 95c7ebf45516594532b5f53a204167f13da4f39e into base eb2bebe532aad5fdc99007923520df6d9d1dd9c9, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1b3d64fd7b3499db434c49374087f37a4531dc0c && git checkout 1b3d64fd7b3499db434c49374087f37a4531dc0c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin eb2bebe532aad5fdc99007923520df6d9d1dd9c9 95c7ebf45516594532b5f53a204167f13da4f39e && git checkout -B drift-repro eb2bebe532aad5fdc99007923520df6d9d1dd9c9 && git merge --no-ff 95c7ebf45516594532b5f53a204167f13da4f39e
node scripts/docs-audit/affected-docs.mjs --json eb2bebe532aad5fdc99007923520df6d9d1dd9c9

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs eb2bebe532aad5fdc99007923520df6d9d1dd9c9 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-elonClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — ACCEPT, enqueueing

domain:engine seat, PM session session_019yDEhPBC3tcGkW9bkce1HM. Head c6d4381e0 (matches the dev's reported final commit).

Two things done better than the order specified

1. It did not just make the superseding line legible — it removed the misleading one. The order asked for a warning that cannot be confused with a first registration. The dev additionally suppresses ObjectKernel's Plugin registered: info line for a superseding registration, so the count of those lines equals the number of plugins that will actually boot. That attacks the defect as stated on the card — "logs 'Plugin registered' twice, which reads as two plugins" — rather than adding a second line beside the misleading pair.

2. ⭐ The warn-not-info choice is load-bearing, and it is evidenced rather than asserted. The CLI's default kernel level is warn (DEFAULT_LOG_LEVEL, packages/cli/src/utils/log-level.ts), at which Plugin registered: is not emitted at all; and the boot-quiet window (BootLogCapture, BOOT_DIAGNOSTIC_FLOOR='warn') discards in-window info while replaying warn. ⇒ An info-level notice would be invisible on exactly the os serve path where the defect lives. That is not a style preference; an implementation that picked info would have shipped a fix that does nothing where it matters. warn and not error follows AGENTS.md's degradation rule — the composition the host asked for is the one that boots.

The four axes of non-confusability are each pinned: different verb (Plugin superseded:Plugin registered:), different level, both versions in order (the only thing that separates the expensive case where a plugin is replaced by a differently-configured instance of itself and the two names are identical), and the consequence stated so nobody has to infer whether two plugins are now running.

The question the order said must be answered, not assumed

the earlier plugin has already been through pluginLoader.loadPlugin() before it is dropped … Does it hold resources that need teardown?

Answered NO, with evidence. So today's overwrite is not also a leak, and the card does not silently grow a teardown obligation.

#9863 discharged rather than left riding

The depending site in packages/cli/src/commands/serve.ts now names the declared contract at both ends of the pair, and #9863 carries a note. That was the card's own ⛔ clause and B alone would not have satisfied it.

Verified independently

checkresult
PR headc6d4381e0 ✅ matches
per-file divergence vs main0 on every one of the 6 files (positive control: 4 commits on other paths)
merge-tree conflicts0
⛔ debt ledgernot in the diff at all, and '@objectstack/core': errors: 98 is byte-identical on main and on this head
both kernels import the shared contractkernel.ts:17 and lite-kernel.ts:7 both import { registerPluginByName } from './plugin-registration.js'
barrel export0 hits — deliberately not exported, no new public API
every load-bearing job's own conclusionTest Core 1/3·2/3·3/3 + aggregate, Temporal Conformance, Build Core, Lint & Repo Gates, TypeScript Type Check, Dogfood ×3 + aggregate, Dogfood Verify CLI, Console Pin Freshness, Check Changeset, all claim guards — all success

Reverse verification is the right shape: reverting only the two kernels via git restore --source=origin/main — ⛔ never stash, and explicitly not checkout-from-ref which stages — gives 8 failed / 4 passed, direction predicted before running. ⭐ The 4 that still pass are the properties ObjectKernel already had, so the file is not red for a trivial reason; the 8 reds are LiteKernel ×5 plus the two warn cases plus the double-announcement case. And the dev reasoned about whether a rebuild was needed rather than defaulting either way: the contract test resolves both kernels by relative path inside the same package, not through package exports into dist/, so what vitest compiled is the source under test. That is the exact hazard the sibling PR #10093did hit (a consumer reading a built artifact); getting it right by argument rather than by luck is worth recording.

⭐ The dual-kernel pin uses describe.each over one table, with two ObjectKernel-only cases held out for a stated reasonLiteKernel emits no registration line and owns one map, so putting those in the shared table would assert 0 === 0 there. Refusing to write a shared assertion that is vacuous on one side is the same discipline as refusing a zero without a control.

The ratchet, and what it exposes about packages/core

⚠️ Recording this because it is a fact about the package, not a fault of the diff. The first draft used extensionless relative imports — the convention the rest of packages/core's tests follow — and added 4 TS2835, taking core from 98 to 102. check:type-check-debt reports that red by construction: "surplus: none — every entry sits exactly at its measurement, so any new error is red." Repaired with explicit .js extensions, as 1345 other test files in this repo do; core is back at exactly 98 with 0 contributed.

Following packages/core's own local test convention raises the debt ledger. The ledger's note already says config-tier 23 (TS2835 ×22), so the convention is the debt. A card is not filed here (that is a devx-lane judgement, and this seat should not mint one on another lane's surface mid-review), but it is worth someone's attention: a package where the neighbouring style is the thing the gate refuses will keep costing every contributor one repair round.

Also correctly distinguished: tsc -p packages/core/tsconfig.json exiting 2 is tsc's own code for core's 98 ledgered errors — ⛔ not the flock queue's 99. No baseline or ledger edited in either direction.

Enqueueing.


Generated by Claude Code

@os-elon
os-elon marked this pull request as ready for review August 20, 2026 02:23
@os-elon
os-elon added this pull request to the merge queueAug 20, 2026
@github-merge-queue
github-merge-queueBot removed this pull request from the merge queue due to failed status checks Aug 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32324497403 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[90mstdout�[2m | src/webhook-secret-at-rest.test.ts�[2m > �[22m�[2mfail-closed and re-arm, extended to headers (#7986 × #7799/#8022)�[2m > �[22m�[2mdrops the subscription rather than delivering it wi
    �[90mstdout�[2m | src/webhook-secret-at-rest.test.ts�[2m > �[22m�[2mfail-closed and re-arm, extended to headers (#7986 × #7799/#8022)�[2m > �[22m�[2mdrops the subscription rather than delivering it wi
    �[90mstdout�[2m | src/webhook-secret-at-rest.test.ts�[2m > �[22m�[2mfail-closed and re-arm, extended to headers (#7986 × #7799/#8022)�[2m > �[22m�[2mre-arms with its headers when the CryptoProvider re
    �[90mstdout�[2m | src/webhook-secret-at-rest.test.ts�[2m > �[22m�[2mfail-closed and re-arm, extended to headers (#7986 × #7799/#8022)�[2m > �[22m�[2mre-arms with its headers when the CryptoProvider re
    �[41m�[1m FAIL �[22m�[49m src/dev-plugin-security-enforcement-warning.test.ts�[2m > �[22m[#10036] the "nothing is enforced" warning must fire when SecurityPlugin.start() bailed�[2m > �[22mbail #1 (no ✗ the dev server this run started is no longer running.
    
  • Test Core (2/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/metadata-repository-fs-dot-root.test.ts�[2m > �[22mMetadataManager ← FileSystemRepository under `.objectstack/metadata` (#7150)�[2m > �[22man out-of-process write reaches
    �[90mstderr�[2m | src/sql-driver-unique-tenancy.test.ts�[2m > �[22m�[2mSqlDriver unique × tenancy (#3696)�[2m > �[22m�[2mretires a legacy global unique index and replaces it with the composite
    �[90mstderr�[2m | src/sql-driver-unique-tenancy.test.ts�[2m > �[22m�[2mSqlDriver unique × tenancy (#3696)�[2m > �[22m�[2mretires the legacy `uniq_<table>_<col>` index left by the drift rebuild path
    �[90mstderr�[2m | src/sql-driver-unique-tenancy.test.ts�[2m > �[22m�[2mSqlDriver unique × tenancy (#3696)�[2m > �[22m�[2mbare-composite tightening + duplicate pre-flight (ADR-0120 D4)�[2m > �[22m�[2ma
    �[90mstderr�[2m | src/sql-driver-unique-tenancy.test.ts�[2m > �[22m�[2mSqlDriver unique × tenancy (#3696)�[2m > �[22m�[2mbare-composite tightening + duplicate pre-flight (ADR-0120 D4)�[2m > �[22m�[2mB
    �[22m�[39m[schema-drift] product: cannot tighten 'uniq_product_organization_id_code' as UNIQUE (COALESCE(organization_id, '__global__'), code) — existing rows already violate the NULL-safe unique cons
    �[90mstderr�[2m | src/sql-driver-unique-tenancy.test.ts�[2m > �[22m�[2mSqlDriver unique × tenancy (#3696)�[2m > �[22m�[2mbare-composite tightening + duplicate pre-flight (ADR-0120 D4)�[2m > �[22m�[2mB
    �[22m�[39m[schema-drift] REFUSING to rebuild 'uniq_product_organization_id_code' on 'product' as a NULL-safe unique — 1 duplicate group(s) violate it (e.g. organization_id="__global__", code="DUP" × 2
    

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 5 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32324797958 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[90mstdout�[2m | src/webhook-secret-at-rest.test.ts�[2m > �[22m�[2mfail-closed and re-arm, extended to headers (#7986 × #7799/#8022)�[2m > �[22m�[2mdrops the subscription rather than delivering it wi
    �[90mstdout�[2m | src/webhook-secret-at-rest.test.ts�[2m > �[22m�[2mfail-closed and re-arm, extended to headers (#7986 × #7799/#8022)�[2m > �[22m�[2mdrops the subscription rather than delivering it wi
    �[90mstdout�[2m | src/webhook-secret-at-rest.test.ts�[2m > �[22m�[2mfail-closed and re-arm, extended to headers (#7986 × #7799/#8022)�[2m > �[22m�[2mre-arms with its headers when the CryptoProvider re
    �[90mstdout�[2m | src/webhook-secret-at-rest.test.ts�[2m > �[22m�[2mfail-closed and re-arm, extended to headers (#7986 × #7799/#8022)�[2m > �[22m�[2mre-arms with its headers when the CryptoProvider re
    �[41m�[1m FAIL �[22m�[49m src/dev-plugin-security-enforcement-warning.test.ts�[2m > �[22m[#10036] the "nothing is enforced" warning must fire when SecurityPlugin.start() bailed�[2m > �[22mbail #1 (no ✗ the dev server this run started is no longer running.
    

历史信号:

  • ⚠️本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 6 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32325403007 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[90mstdout�[2m | src/webhook-secret-at-rest.test.ts�[2m > �[22m�[2mfail-closed and re-arm, extended to headers (#7986 × #7799/#8022)�[2m > �[22m�[2mdrops the subscription rather than delivering it wi
    �[90mstdout�[2m | src/webhook-secret-at-rest.test.ts�[2m > �[22m�[2mfail-closed and re-arm, extended to headers (#7986 × #7799/#8022)�[2m > �[22m�[2mdrops the subscription rather than delivering it wi
    �[90mstdout�[2m | src/webhook-secret-at-rest.test.ts�[2m > �[22m�[2mfail-closed and re-arm, extended to headers (#7986 × #7799/#8022)�[2m > �[22m�[2mre-arms with its headers when the CryptoProvider re
    �[90mstdout�[2m | src/webhook-secret-at-rest.test.ts�[2m > �[22m�[2mfail-closed and re-arm, extended to headers (#7986 × #7799/#8022)�[2m > �[22m�[2mre-arms with its headers when the CryptoProvider re
    �[41m�[1m FAIL �[22m�[49m src/dev-plugin-security-enforcement-warning.test.ts�[2m > �[22m[#10036] the "nothing is enforced" warning must fire when SecurityPlugin.start() bailed�[2m > �[22mbail #1 (no ✗ the dev server this run started is no longer running.
    

历史信号:

  • ⚠️本 PR 过去 24h 已在队列失败 2 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 7 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@os-elon
os-elon added this pull request to the merge queueAug 20, 2026
Merged via the queue into main with commit a38408aAug 20, 2026
26 checks passed
@os-elon
os-elon deleted the claude/issue-9864-dual-kernel-plugin-registration branch August 20, 2026 08:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

2 participants

@os-elon@claude