Skip to content

fix(lint): warn on a flow start input declared a record of the object-less placeholder, or one id the flow reads as a list - #30753

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-30633-flow-input-objectname-lint
Nov 6, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-30633-flow-input-objectname-lint

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #30633

Clause-②: yes (widening) — @objectstack/lint exports two new rule-id constants, FLOW_INPUT_OBJECT_LESS and FLOW_INPUT_RECORD_READ_AS_LIST; nothing that passed before is refused.
Metadata-protocol: none

维护者速读

  • 改了什么:os validate 的流程触发就绪检查新增两条警告——开始输入把 objectName 写成无对象占位符 'global';或声明为单条记录 id、却被同一流程的节点按列表(filter.id.$in)读取。
  • 为什么改:这两种写法 schema 都接受,但运行时要么根本不加载、值未经校验就进入运行,要么每次启动都被 400 拒绝,作者只有运行时才发现。
  • 风险与回滚:只新增 warning 和两个公开导出的规则 id 常量(公开面扩大,Clause-② widening),不拒绝任何构建或发布;既有 flow-input-unknown-object 判定不变。回滚即还原本 PR。
  • 先看:packages/lint/src/validate-flow-trigger-readiness.ts 的 1j 段(第二条警告的读取范围是否与运行时准入一致),其次同名测试文件新增的 describe;index.ts 只加两个导出名。
  • 两分钟验证:pnpm --filter @objectstack/lint exec vitest run src/validate-flow-trigger-readiness.test.ts,预期 141 个测试全部通过(整包 8183 个)。
  • 需要你做的:无,席位落地。

What changes

Two new advisory ids from validateFlowTriggerReadiness, beside flow-input-unknown-object:

  • flow-input-object-less: a start input declares objectName: 'global' (or '*', which only a pre-parse stack can carry). The start doors' load skips an object-less key: loadActionSubjectRecord in packages/runtime/src/action-execution.ts loads only when !isObjectLessActionKey(objectName), and the declared read that PR fix(runtime): a flow start input declared a record id is loaded in the caller's own scope from the declared object at every door that starts the flow by name #30628 added (flowDeclaredRecordReads → refuseUnreadableFlowStartRecords) goes through that load. So the value reaches the run unjudged. Located at flows[N].variables[M].objectName.
  • flow-input-record-read-as-list: a start input declares one record id, and a get_record / update_record / delete_record node in the same flow reads it as a list (filter.id.$in handing the input on as it is). Admission judges the input once per reading: oneRecordIdOrRefuse for the declaration and recordIdListOrRefuse for the $in read. No value passes both, so every start that supplies the input is refused 400. Located at the node's config.filter.id.$in, nested regions included (walkFlowNodes).

The second rule reads the flow the way flowDeclaredRecordReads does: only isInput: true variables, only a CRUD node with a literal objectName, only a CEL envelope whose whole source is the input (bare or vars.), and not where a loop or map rebinds the name. Both ids are warning in every context, a stated package closure included.

Also in this PR (claim amended to admit them):

  • packages/lint/src/index.ts re-exports the two new constants, because rule-id-barrel-exports.test.ts requires every rule id to be reachable from a published barrel.
  • Wording only: the flow-input-unknown-object message, the FLOW_INPUT_UNKNOWN_OBJECT docblock and the 1i comment now say the start doors enforce the declaration (PR fix(runtime): a flow start input declared a record id is loaded in the caller's own scope from the declared object at every door that starts the flow by name #30628), not that they will. The message changes from "…every start that supplies the input will be refused once the start doors enforce the declaration." to "…every start that supplies the input is refused." No test pinned the old sentence. The id, severity, path, hint and closure verdict are unchanged.
  • Wording only, in packages/lint/src/rule-explanations.ts (the flow-input-unknown-object paragraph, admitted by a second claim amendment): "The declaration is planned: no door reads it yet" now reads "Every door that starts the flow by name enforces the declaration", and the paragraph's "are to load" / "would refuse" are now present tense.

Tests

New top-level describe in validate-flow-trigger-readiness.test.ts, plus one row per id in the severity table:

  • object-less: id, severity, where and path on 'global', with the existing flow-input-unknown-object finding still beside it; 'global' defined as a stack object still warns; '*'; a silent control on a defined object.
  • read-as-list: the exact finding at nodes[1].config.filter.id.$in; a vars. hand-off inside a try_catch region, located at the nested node; seven silent controls (filter.id, $eq, undeclared input, non-input, computed source, loop rebinding, create_record).

The existing flow-input-unknown-object cases are unchanged.

Acceptance notes

  • Additive by design: for 'global' on a stack that defines no object of that name, flow-input-unknown-object still fires beside the new id. The card asks that the existing cases stay as they are, and under a stated closure that id is a publish refusal, so replacing it would widen the gate.
  • Not covered: a list read reached through a subflow or map hand-off into a callee flow (admission follows those up to 8 levels deep; this rule judges the same flow only). A map over the input whose callee reads each item as one id also reads the input as a list, and is not judged here either.
  • The new ids have no os explain entry. Each finding carries its full verdict and fix.
  • The object-less key set is spelled locally, because lint depends on @objectstack/spec only. It has to be kept in step with isObjectLessActionKey by hand, as stack.zod.ts's GLOBAL_ACTION_SCOPE already is.

Verification

All readings are at head 922bc3dd6.

Lint package: tests, typecheck, eslint
  • pnpm --filter @objectstack/lint test: exit 0. Test Files 195 passed (195), Tests 8183 passed (8183).
  • pnpm --filter @objectstack/lint typecheck (tsc --noEmit && pnpm check:test-typecheck): exit 0.
  • pnpm lint (eslint, repo-wide): exit 0.
Derived gate families: 57 commands
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 57 commands from 5 paths, merge base 06cccd3.
  • All 57 exited 0, check:type-check-debt, check:dual-build-cjs-loads and check:lean-entry-closure included.
  • --ran reconciliation: "57 derived, 57 run, 0 NOT-MEASURED, 0 UNRUN", exit 0.
Ablations (scripts/ablation-replace.mjs, each restored to the HEAD blob)

Taken at 989f345. The guarded lines are byte-identical at 922bc3d: the later commits touched only the barrel, wording, the changeset and the position of one existing docblock.

  1. Object-less guard set to false &&: 4 failed, 137 passed (the severity row and the three object-less cases).
  2. Read-as-list guard replaced by continue: 3 failed, 138 passed (the severity row, the exact-finding case, the nested-region case). The first attempt used the replacement text continue;, which already occurs 4 times in the file, so the tool could not prove the mutation landed and refused it; nothing ran. It was re-run with a unique marker.
  3. Loop-rebinding exclusion deleted: 1 failed, 140 passed (the silent-controls case).

Generated by Claude Code

…-less placeholder, or one id the flow reads as a list

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cytLmJxfAmgPVjUbbW
…start doors enforce the declaration

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cytLmJxfAmgPVjUbbW
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 11 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/lint/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

23 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 b3098661cd3ad78634dd77a31ae68530ab9a3a83.

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

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/lint/src/index.ts) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 63 of 221 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 103 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 4 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 b3098661cd3ad78634dd77a31ae68530ab9a3a83 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from a08fec2c639db674dade37655b32c6f1738e1a22 — the merge of head 922bc3dd66cce58dc28d81dd512c4f651f6ff610 into base b3098661cd3ad78634dd77a31ae68530ab9a3a83, 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 a08fec2c639db674dade37655b32c6f1738e1a22 && git checkout a08fec2c639db674dade37655b32c6f1738e1a22
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b3098661cd3ad78634dd77a31ae68530ab9a3a83 922bc3dd66cce58dc28d81dd512c4f651f6ff610 && git checkout -B drift-repro b3098661cd3ad78634dd77a31ae68530ab9a3a83 && git merge --no-ff 922bc3dd66cce58dc28d81dd512c4f651f6ff610

node scripts/docs-audit/affected-docs.mjs --json b3098661cd3ad78634dd77a31ae68530ab9a3a83

⚠️ 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 b3098661cd3ad78634dd77a31ae68530ab9a3a83 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…oors enforce the declaration

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cytLmJxfAmgPVjUbbW
…tNodeOf's docblock back above it

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cytLmJxfAmgPVjUbbW
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 922bc3dd66cce58dc28d81dd512c4f651f6ff610
Local-runs: none
Scope: packages/lint/src/index.ts, packages/lint/src/validate-flow-trigger-readiness.ts, packages/lint/src/rule-explanations.ts, packages/lint/src/validate-flow-trigger-readiness.test.ts, .changeset/30633-flow-input-declaration-lint.md (the whole non-prose diff; the start-admission code it cites on main, packages/runtime/src/flow-start-admission.ts and isObjectLessActionKey, was read, not reviewed)

The face is the declaration alone: Clause-②: yes (widening) on the PR body and changeset, with no published schema and no governed path in the diff. Governing text: card #30633 (filed from contract record 6666908076 ④ items 2–3), claim 6672120182 with amendments 6672887535, 6673479741 and 6674671434, dev report 6673465151. Read-only: the diff at this head and the head's check-runs.

① Derived judgments

  1. Public surface. The @objectstack/lint barrel gains two names, FLOW_INPUT_OBJECT_LESS and FLOW_INPUT_RECORD_READ_AS_LIST, string rule-id constants beside the existing FLOW_INPUT_UNKNOWN_OBJECT. Nothing is removed or retyped. Widening, as declared.
  2. Nothing narrows. Both new findings are warning severity, in the advisory family of 1i; no path makes either an error, and no stack that validated before is refused. flow-input-unknown-object keeps its verdicts (the 1j block is additive and leaves declaredInputs local). Right.
  3. flow-input-object-less fires on objectName in the object-less key set global and *, mirrored by hand from isObjectLessActionKey, the same lockstep convention stack.zod.ts uses for GLOBAL_ACTION_SCOPE. It is judged on every variable that carries objectName; since the schema refuses objectName on a non-input at parse, that is the start inputs in practice. Right.
  4. flow-input-record-read-as-list reads the way start admission reads: only an isInput variable, only get_record / update_record / delete_record with a literal objectName, only a filter.id.$in that hands the input on unchanged (a CEL envelope whose source is the bare name or vars. name), and not where a loop or map rebinds the name. The walk is walkFlowNodes, so nested branches are covered. A list read through a subflow or map hand-off is not judged, and the comment says so. Right, and conservative: it can miss, it cannot misfire on a computed value.
  5. Explanation text. The flow-input-unknown-object paragraph and the 1i docblock and message move to the present tense now that PR fix(runtime): a flow start input declared a record id is loaded in the caller's own scope from the declared object at every door that starts the flow by name #30628 enforces the declaration, and the "planned" sentence is gone. No tracker number reaches a runtime string (check:doc-authoring). Right.

② Semver level

patch for @objectstack/lint, with Metadata-protocol: none. The level follows the metadata-protocol line alone (ruling A on #25105, scripts/pm/clause2-line.mjs); two lint rule ids are not part of the protocol an author writes against. The declaration lines are identical in body and changeset and read as yes / widening and none. Check Changeset is green on this head. Right.

③ Boundary flags

open_questions: []. Deviations: the Clause-② line was no on the claim and the first heads, corrected to yes (widening) at this head and on the claim by amendment 6674671434. No other deviation. Author note: PR user.login is objectstack-fleet[bot].

④ Design judgment

  1. Pins. The test file only adds cases (no existing line is edited). Object-less: global warns beside the unknown-object verdict, still warns where the stack defines an object named global, * warns, and a defined object is silent. Read-as-list: a $in hand-off warns at the read naming input, node and both objects; a vars. hand-off inside a nested region warns at the nested node. Silent cases: filter.id and filter.id.$eq reads, an undeclared input, a non-input, a computed $in source, a loop that rebinds the name, and a non-addressing create_record node. Each names a distinct branch of the 1j block. A map item rebinding is not pinned on its own (it shares the loop branch).
  2. Tried to break it. Two declared inputs with the same name: the first wins, which matches the schema's own uniqueness. A $in holding a literal array: handedOnVariable returns undefined, so no finding. An objectName of '': nonEmptyString drops it, and the schema refuses it anyway. A loop whose iterator shadows a declared input: the input is dropped from the read check for the whole flow. That can miss a real read outside the loop, but it never misfires, which is the right direction for an advisory. No must-fix.
  3. Gates. Every check-run on this head has concluded: 56 success, 11 skipped, 0 failure.

Should-fix (non-blocking): content/docs/automation/flows.mdx ("The name is checked", near :2212) names only flow-input-unknown-object. The two new warnings could be taught there; the sentence is not false, so this is a follow-up, not a fix here.

Implemented-by: claude/issue-30633-flow-input-objectname-lint
Reviewed-by: session_016WV4cytLmJxfAmgPVjUbbW

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

席位核对 · ACCEPT · 2026-11-06T02:15Z · #30633

Head-sha: 922bc3d

  • Shape: 5 files, all in packages/lint/ plus one changeset: the trigger-readiness rule gains block 1j with two advisory warnings (flow-input-object-less, flow-input-record-read-as-list), the barrel exports the two rule ids, the flow-input-unknown-object explanation and 1i text move to the present tense after PR fix(runtime): a flow start input declared a record id is loaded in the caller's own scope from the declared object at every door that starts the flow by name #30628, and the test file adds cases only. Inside claim 6672120182 and amendments 6672887535 / 6673479741 / 6674671434. Not governed, no published schema.
  • Review of record: TIER_DEFAULT PASS 6675042063 at this head (the face is the Clause-②: yes (widening) declaration alone).
  • Gates: every check-run on this head has concluded: 56 success, 11 skipped, 0 failure. Locally, the dev reports lint 195 files / 8183 tests, typecheck clean, 57/57 derived commands with 0 NOT-MEASURED, pnpm lint 0 at this head.
  • Accepted deviations:
    • The Clause-② line was no on the claim and the first heads. Two new barrel exports enlarge the published surface, so it is yes (widening) at this head and on the claim (amendment 6674671434). The level stays patch under Metadata-protocol: none.
    • WIP-era report comment 6673465151 still describes head 25f1613e5. This ACCEPT and the PR body (Verification head 922bc3dd6) supersede it.
  • Out of scope, follow-up: content/docs/automation/flows.mdx ("The name is checked") names only flow-input-unknown-object. Teaching the two new warnings there is a docs follow-up, not a false sentence.
  • Contract: Clause-②: yes (widening), Metadata-protocol: none, confirmed from the diff.
  • Landing: ready, then auto-merge, through the relay. Fixes #30633.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 6, 2026 02:17
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 6, 2026 02:17
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

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

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

分类:failure —— 按下面的日志分诊。

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

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

    @objectstack/cli:test:  FAIL   unit  test/validate-build-gate-parity.test.ts > os validate is the read-only superset of os build (#3782, #4409) > the command sources use no call shape this scan cannot
      ↳ 失败原因: @objectstack/cli:test: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • test/validate-build-gate-parity.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

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

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

分类:failure —— 按下面的日志分诊。

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

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

    @objectstack/cli:test:  FAIL   unit  test/validate-build-gate-parity.test.ts > os validate is the read-only superset of os build (#3782, #4409) > the command sources use no call shape this scan cannot
      ↳ 失败原因: @objectstack/cli:test: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • test/validate-build-gate-parity.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

席位核对 · Queue failure triage · 2026-11-06T03:03Z · #30633

Both queue failures (builds 42149417291 and 42150976034) are the same case: Test Core (4/8), packages/cli/test/validate-build-gate-parity.test.ts › "the command sources use no call shape this scan cannot read", Test timed out in 5000ms. It is not this PR's:

  • this PR touches only packages/lint and one changeset; the case reads packages/cli command sources with a pure character scan and imports nothing from lint;
  • measured on an idle container, the case takes 1669 / 1857 ms on origin/main and 1735 ms at this head, the same cost, so a loaded shard pushes it past the 5 s budget.

No fix exists yet; filed as #30789 (mask each source once, or reuse the shared masker; not a raised timeout). This seat re-queues this PR once more. If it fails a third time on the same case, it waits for #30789 instead of burning the queue again.

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 Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant