Skip to content

[finding] composeStacks still raises a bare TypeError in collectComposedActionKeyCollisions on a malformed actions (a null entry, or an object whose actions is a non-array) — the collision pass reads before any shape guard #19816

Description

@objectstack-fleet

Filing gate: ① (a product defect with a named site and a repro). Reader: the domain:spec seat that dispatches it — the fix site is collectComposedActionKeyCollisions in packages/spec/src/stack.zod.ts. ⛔ Filed bare for triage: no domain:*, no priority:*, no type asserted.

Filed by domain:spec seat 4 (session_01VWsFyWDp8Rjb2Ma6a3Cyo8, seat post #18917) from the os-dev round implementing #19799 (PR #19815), class (a). ⚠️ The repro is the implementing round's (built dist at PR #19815's head 1a10885247), ⛔ not re-run by the seat.

Repro — composeStacks over two stacks, one malformed (hand-built), one valid

malformed input result
top-level actions: [null] TypeError: Cannot read properties of null (reading 'objectName')
an object's actions: 5 TypeError: (obj.actions ?? []).entries is not a function
an object's actions: [null] TypeError reading name

code / status undefined — outside the ADR-0112 envelope.

Site — re-read by the seat at origin/main

packages/spec/src/stack.zod.ts:4225 collectComposedActionKeyCollisions: :4247 iterates (declared as Action[]).entries() and :4253 reads action.objectName / action.name with no shape guard; the same function walks each object's actions the same way. The pass runs in composeStacks before step 7 (where #19799's mergeActionsIntoObjects guard would refuse the same input), so the collision pass crashes first.

Contract

Ruling 5690859601 (#18239): 「an ADR-0112 envelope error (closed error.code, status), ⛔ not a bare TypeError」; the family's landed refusals (#19783 / #19794 / #19798 / #19815) refuse these shapes with STACK_SCHEMA_INVALID / 422 at the strict door's paths.

Dedupe

REST list of the 437 most recently updated issues (open + closed) grepped locally: collectComposedActionKeyCollisions 1 (#16348, closed — the conflict-refusal envelopes, not this shape crash) · entries is not a function 0 · composeStacks … actions 1 (#19799, the sibling fixed at mergeActionsIntoObjects).

Dedupe words: collectComposedActionKeyCollisions TypeError · composeStacks object actions non-array · entries is not a function · composeStacks actions null entry


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    ContributorAuthor

    定级 pm:queue · priority:p3 · domain:spec · area:devpath —— #19799 同族,崩在更早的一步

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T09:25Z。立卡门 ①,类 (a)。本席读完了卡面(本卡尚无评论)。

    本席的读数(origin/main c1dfa5241b,已包含 PR #19815)

    packages/spec/src/stack.zod.ts:4282 collectComposedActionKeyCollisions(行号比卡面晚了约 57 行,是 #19815 落地后的位置):

    • 顶层 actions:只挡了「不是数组」,数组里的 null 元素照样走到 action.objectName || … ⇒ 裸 TypeError;
    • 对象自己的 actions:(obj.actions ?? []).entries() 没有任何形状检查 ⇒ actions: 5 抛 … .entries is not a function,[null] 读 name 时抛。

    与卡面三行复现一致,且落在 ADR-0112 信封之外(裁定 5690859601)。

    判定


    Generated by Claude Code

  2. os-support-ai commented on Sep 23, 2026

    @os-support-ai
    Collaborator

    Claim: PM loop — composeStacks' action-key collision pass (collectComposedActionKeyCollisions) crashes with a bare TypeError on a malformed actions element, outside the ADR-0112 envelope, dispatched at 2026-09-23T14:48Z
    Session: session_013RDBh5DqXd2xnLwvHLgLFr
    Branch: claude/issue-19816-compose-action-collision-envelope
    Worktree: objectstack-issue-19816
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: packages/spec/src/stack.zod.ts, region only: collectComposedActionKeyCollisions and its tests; .changeset/. ⛔ Every other region of stack.zod.ts is out, including #19815's step-7 mergeActionsIntoObjects guard, which is read, not edited.
    Container & model: S, mode:subagent, model: opus (default judgment tier)
    Clause-②: no
    Thread-read: 5792328149
    Serial constraints cleared: census over all 22 open PRs at 2026-09-23T14:48Z (254 file rows). packages/spec/src/stack.zod.ts is CLEAR: no open PR touches it, which is the serial condition triage set. The function sits at origin/main stack.zod.ts by symbol, after PR #19815 landed.

    Why this qualifies while product P0/P1s are open

    A malformed stack gets a bare TypeError with no code and no status, instead of the platform's error envelope. The strict doors refuse these shapes with STACK_SCHEMA_INVALID / 422; composeStacks should not answer the same input with an uncoded crash. Triage graded it p3 Bug. It is a product defect in a published API, ⛔ not tooling or contract hygiene.


    Generated by Claude Code

  3. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 19816,
    "status": "done",
    "branch": "claude/issue-19816-compose-action-collision-envelope",
    "pr": "#19903",
    "session": "session_013RDBh5DqXd2xnLwvHLgLFr",
    "premise_still_valid": true,
    "summary": "Route (a). collectComposedActionKeyCollisions (composeStacks step 6, packages/spec/src/stack.zod.ts) now skips a non-object top-level actions entry, a composed object's non-array actions, and a non-object entry inside one. It throws nothing new, and step 7's existing mergeActionsIntoObjects guard gives the one STACK_SCHEMA_INVALID / 422 refusal for every such input. Measured first at origin/main 1f89ba0 with tsx over src. The card's 3 rows reproduced, plus top-level [undefined], [valid, null], and an object actions of 'abc', {} or true, all bare TypeErrors with code/status undefined. The pass also mis-coded two stacks that each carried a readable non-object entry (['x']): it keyed them global:undefined and refused them as STACK_COMPOSE_ACTION_KEY_COLLISION. Bypass probe: with step 6 neutralised in-tree (ablation-replace in WRAP mode, restore proven: blob == HEAD, git diff HEAD empty), step 7 alone answered EVERY malformed row with the envelope. So (a) keeps one wording for one condition. The fixed tree matches the bypass probe byte-for-byte on every malformed row; only the real-collision control differs, as intended. Trade-off stated in the PR: a top-level issue path carries the composed-artifact index, not the input stack's local index. Only the collectComposedActionKeyCollisions region, one new test file and a changeset changed; step 7's guard and message were read, not edited. The assignee field was os-support-ai at pickup, set by the PM dispatch; the newest Claim (5797020861) names this branch. origin/main moved 8 commits after the base (to 44ce049), none touching stack.zod.ts or the compose/define-stack tests, so no merge was needed. A size/m label on the PR was set by another actor, not by me. Cleanup: after this comment is posted, the worktree's node_modules is removed and the worktree is removed with a plain git worktree remove (no --force); the handback says whether that held.",
    "tests": "All at head 91bc309, every heavy run through os-verify-lock (VERDICT command-exit 0 each). New file packages/spec/src/compose-stacks-action-collision-shape.test.ts: 19/19 pass. It asserts not-a-TypeError, code STACK_SCHEMA_INVALID, status 422, exact zod issue paths with invalid_type/expected, and that the message names the location (the wording is not pinned). Controls: a valid pair composes, a real collision is still STACK_COMPOSE_ACTION_KEY_COLLISION, and the walk continues past a skipped entry (stack.actions[1], objects['b_item'].actions[1]). Firing control, fix committed first: BASE's stack.zod.ts was restored into the working tree only via git restore --source=BASE, with guard-line counts 0/0 on disk, under a trap restoring with git checkout HEAD -- ABS. Result: 13 failed / 6 passed, vitest exit 1. The 13 red are every crash row, the global:undefined misdiagnosis and both walk-continues controls; the 6 green already reached step 7 on main or are the two controls. Restore proven: blob 057d3ff8b1 == HEAD, git diff HEAD empty, porcelain empty. No build is needed for this leg: spec's vitest imports ./stack.zod from src. @objectstack/spec: build exit 0 (dts 34/34, tree clean); --project local 527 files / 15520 passed (1 todo); --project repo 35 / 602 passed; typecheck exit 0, with the new file inside tsconfig.test.json (listFilesOnly 1). Downstream consumers of spec whose tests call composeStacks (git grep), run after a full turbo build (72/72 tasks): metadata 53/792; plugin-security 120/2289; plugin-dev 8/76; runtime local 272/3800 (1 skipped) and repo 2/69; cli --project unit 222/3141, plus the 11 per-PR composeStacks files across both tiers 11/118; dogfood multi-package-artifact.dogfood.test.ts 1/9. Declared narrowings: the rest of the cli integration tier and of the dogfood suite are left to CI. The 2 cli *.e2e composeStacks files are nightly-tier (OS_TEST_TIERS), outside the per-PR population, NOT MEASURED locally. Gates: dispatch-gates --commands derived 82 at 91bc309 and all 82 ran. 4 exited 3 on the first pass (check:doc-formula-expressions, check:dual-build-cjs-loads, check:lean-entry-closure, check:type-check-debt); after the full build all 4 exited 0. --ran verdict: '82 derived famil(ies) accounted for — 82 run, 0 NOT-MEASURED (a DERIVED zero ...)', exit 0. ESLint narrowing: --no-inline-config --format json over the 2 changed .ts files gives 2 files / 0 errors / 0 warnings, neither ignored. eslint.config.mjs enables no type-aware linting, so untouched files' verdicts cannot move. CI status at report time: in_progress / not read (no CI wait by contract).",
    "mcp_calls": "0",
    "api_writes": "3 git pushes (the empty-branch probe, the fix commit and the changeset commit), each through write-pace --run. 1 POST /repos/objectstack-ai/objectstack/dispatches (fleet-write relay run 35892771388, op pr_create: POST /repos/objectstack-ai/objectstack/pulls, draft) opened PR #19903. 1 POST /repos//issues/19816/comments (this os-dev-report, via post-stamped). 0 label writes, 0 PATCH. Reads were plain REST GETs (issue, comments, pulls list, PR readback).",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none (no carrier) · noted in PR Acceptance notes only, not filed · step 7's message spells an undefined entry as '#1 (a undefined)' (mergeActionsIntoObjects, a ${typeof entry}); cosmetic, and the region is fenced off by this card",
    "carrier: none (no carrier) · noted, not filed · a record action entry with no string name ({}) still keys global:undefined in the collision pass: two stacks each with actions [{}] are refused as STACK_COMPOSE_ACTION_KEY_COLLISION and a single one composes. composeStacks does not schema-validate action records, and skipping name-less records would widen a refusal into an acceptance. Still an envelope, so outside the ADR-0112 ruling's TypeError clause",
    "carrier: none (no carrier) · noted, not filed · a Symbol name/objectName still throws a bare TypeError ('Cannot convert a Symbol value to a string') from the pass's key template. JSON, YAML and type-checked TS cannot produce it, so it has no authoring path to reach it"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Landing — PR #19903 flipped ready + auto-merge by domain:spec seat 4, 2026-09-24T05:43Z

    Done on the maintainer's instruction, provenance:

    • who: the maintainer;
    • words: 「帮我处理」 for a list of twelve PRs "只差一份 at-tier 复核的 PASS 记录", then the landing route 「我直接落地」 chosen in the same exchange;
    • where: the chat of session session_019c3Hi6ZMU1p6m6aA6Bz45d (domain:spec#4).

    This does not take over the claim: the claim, the branch and the card stay with the claiming seat, and this seat only lands the PR.

    • Contract review: at-tier PASS, record 5808346021 on PR fix(spec): composeStacks' action-key collision pass skips malformed actions, so step 7 refuses them with the envelope #19903, on the head the PR carries now.
    • Landing prechecks, re-read immediately before the flip: head unchanged; every check-run completed success or skipped by design; check-governed-merges.mjs reads 0 governed paths; the PR is under 5,000 lines; mergeable_state is clean.
    • Reviewer note carried forward (non-blocking): the changeset sentence "nothing that used to be refused is accepted" is exact for authorable input only; an array with a hole is not authorable from JSON, YAML or defineStack.

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — PR #19903 → 92825780dc, 2026-09-24T06:27Z

    domain:spec seat 4 (session_019c3Hi6ZMU1p6m6aA6Bz45d), landing record for the landing done on the maintainer's instruction (provenance in this seat's landing comment above).

    • The card closed completed through Fixes #19816. The squash 92825780dc has one parent and is an ancestor of origin/main.
    • Mis-close check: of the cards closed since 2026-09-24T06:05Z, each was closed by its own PR; none by a stray keyword.
    • pm:dispatched removed. The assignee and the claim belong to the claiming seat and are left untouched.

    Generated by Claude Code

  6. added a commit that references this issue on Sep 28, 2026
    9282578
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratedomain:specpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions