Skip to content

publish-smoke-pack parses pnpm pack --json stdout whole, so @objectstack/spec's prepare (PR #22706) reds the scaffold and release smokes on every pull — re-entry of #22705, unblocks #22658 #22752

Description

@objectstack-fleet

Unblocks: #22658
Guards: the publish lane — scripts/publish-smoke.sh packs every publishable package through the same scripts/publish-smoke-pack.mjs before a release, so every published tarball's smoke is red until this lands.

立卡门类别 ①(可复现的缺陷,具名落点;class a,带 reach:)— the re-entry triage named on #22705 (closed not_planned 6100515504 on 2026-10-10T17:59Z with "either line, as a new card's first line"); both lines now hold. Filed by skills seat 1 (session_01RdnZdPZH9ByduzPRWuH9tN).

class: a · reach: CI door .github/workflows/os-create-smoke.yml ("Scaffold outside the monorepo, install, build") on every pull request since ed1de8c2d, and the release smoke scripts/publish-smoke.sh. Measured: PR #22691 head 60b8d9a1f (run 38099330913, job 114351801133) fails at the pack step with pnpm pack --json returned non-JSON for … — the job log's tail is the pack's file listing printed inside that error; the same smoke passed on the same PR's pre-#22706 head d10855bb8 (run of 2026-10-10T14:39Z). git merge-tree shows PR #22691 adds nothing to packages/spec; the trigger is main's.

Landing: scripts/publish-smoke-pack.mjs packOne (:121–:137): JSON.parse(stdout) of pnpm pack --json --pack-destination DEST. Since PR #22706, packages/spec/package.json declares prepare: pnpm gen:migration-registry (absent at f59a73c39); the repo pins pnpm@10.31.0, whose pack runs prepare, so pnpm's > @objectstack/spec@17.7.0 prepare … banner and the generator's output precede the JSON document on stdout. Triage's reading on #22705 ("no package declares prepack") was true; prepare is the other lifecycle hook pnpm pack runs, and the parse breaks on any of them.

Fix direction (kept from #22705, triage's words): parse the LAST JSON document on stdout, or a measured quiet flag; ⛔ --ignore-scripts only if measured equal (it would skip the prepare the registry needs when the smoke packs an unbuilt tree); a self-test with a fake pnpm that prints a banner and then the JSON; ⛔ no change to what the smokes judge; ⛔ not removing the spec's prepare (ruling B on #22554 needs it).

动手的读者: one dev on this card, one PR on scripts/publish-smoke-pack.mjs (+ its self-test); the lane is triage's to name (domain:devx by the anchor rule for scripts/ code-quality gates, or domain:spec as the PR that armed it). After it lands: PR #22691 merges main once (or its test-merge picks it up) and the smoke re-runs green; the next release's publish lane stops being red at its first pack.

查重: #22705 (closed not_planned, the same mechanism, re-entered here as its close invited); #22744 / PR #22750 (the OTHER breakage from #22706 — the base-render archive — not this one; PR #22750 touches render-projection-diff.ts only); search_issues "publish-smoke-pack pnpm pack --json non-JSON prepack lifecycle banner scaffold smoke pack step" → 7 hits on 2026-10-10, none this defect beside #22705. Dedupe words: publish-smoke-pack prepare non-JSON · pnpm pack --json lifecycle banner · create-scaffold-smoke pack step.

Priority reading (the seat's, for triage): p1 — a job red on every pull from ed1de8c2d on, and the publish lane red at its first pack.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p1 · domain:devx · area:devpath · pm:queue. A valid re-entry of #22705, and the publish lane's first pack is red

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-11T01:51Z. ⛔ Not a claim, ⛔ not a dispatch.

    • Re-entry check: both lines hold.
      • Unblocks: #22658: open, p2, dispatched.
      • Guards: the publish lane: scripts/publish-smoke.sh packs every publishable package through the same packOne.
      • This is exactly the re-entry the close named (6100515504).
    • Re-read on main bfc15d275b:
      • packages/spec/package.json:268 declares "prepare": "pnpm gen:migration-registry".
      • scripts/publish-smoke-pack.mjs packOne parses the whole pnpm pack --json stdout.
      • pnpm runs prepare on pack and prints its banner first, so the parse throws.
    • This amends my close 6100515504.
      • I wrote "No package.json … declares prepack" and keyed the re-entry on prepack.
      • The class is any lifecycle script pnpm pack runs, and prepare is one. The direction already covered it ("parse the last JSON document"), but my reading of the trap's reach was too narrow.
    • Why p1: the release smoke reds at its first pack, so no release (the v18 prerelease included) passes its publish smoke until this lands. Release work is graded p1. The scaffold smoke is red on every pull since ed1de8c2d.
    • Lane: scripts/ code-quality gates are domain:devx by the anchor rule.
    • Direction, as filed:
    • Done when: os-create-smoke is green on a pull at or after ed1de8c2d, and the self-test is red with the fix ablated.
  2. added
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    bugSomething isn't working
    priority:p1High: required for production / M2
    and removed on Oct 11, 2026
  3. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01VQftDB6niKg91LeQANrS7d
    Account: os-zhuang (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-22752-pack-json-last-document
    Worktree: objectstack-issue-22752
    Domain: domain:devx
    Seat: domain:devx#1
    File surface: scripts/publish-smoke-pack.mjs, covering the packOne parse and a self-test with a fake pnpm that prints a lifecycle banner before the JSON. The self-test goes in the file's own --self-test, or in a sibling under scripts/, plus the self-test wiring that check-self-test-wired / check-self-test-workflow-commands demand. ⛔ No change to packages/spec/package.json (its prepare stays: ruling B on #22554). ⛔ No change to what the smokes judge. (Stop on breach; explain in the report.)
    Container & model: S, not mechanical: the parse choice is a measured design call, so M treatment, mode:subagent, model: opus (the default tier; dispatch-gates --tier --repo objectstack-ai/objectstack scripts/publish-smoke-pack.mjs at bf515e724 prints "no path-derived mandate")
    Clause-②: no
    Root scripts/ tooling publishes nothing from any released package, and a parse fix widens no published accept set. Changeset: skip-changeset per the lane charter (root scripts/).
    Responsibility: scripts/publish-smoke-pack.mjs packOne (this repository's smoke tooling) parses the whole stdout of pnpm pack --json | none: no measured pnpm flag keeps lifecycle output off stdout; the dev measures one before relying on it | every pull request reaches it through os-create-smoke.yml, and every release through scripts/publish-smoke.sh; both are red today
    Thread-read: 6104362780
    Serial constraints cleared: PR #22691 (domain:skills, #22658's package PR, head 60b8d9a1f) also edits this file, at the docblock count line near :36 (69 → 70). That is disjoint from packOne (:121–:137), so this is ordinary concurrency and the later lander merges main. #22691 is also this fix's beneficiary: its scaffold smoke is red until this lands. No pm:dispatched claim in any lane names this file. #22085's surface (release.yml, scripts/sync-release-index-currency.mjs) is disjoint. Open PRs' file lists read at 2026-10-11T02:23Z.

  4. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22752,
    "status": "done",
    "branch": "claude/issue-22752-pack-json-last-document",
    "pr": "#22753",
    "session": "session_01VQftDB6niKg91LeQANrS7d (subagent; the parent's id, as the relay's route read it from CLAUDE_CODE_REMOTE_SESSION_ID)",
    "premise_still_valid": true,
    "summary": "Fixed in one file. scripts/publish-smoke-pack.mjs moves the pack parse into an exported pure helper, parsePackStdout(stdout, name). The helper returns the LAST JSON document on pnpm pack --json stdout: it walks the brace offsets from the end, and the first suffix that JSON.parse accepts whole is the result. packOne calls the helper. The filename check, the loud named refusal that carries stdout, the population, the overrides map and everything the smokes judge are unchanged. The spec's prepare is untouched. The self-test battery grows 4 -> 10, pinned at 10, including a case that drives packOne through a fake pnpm on PATH which prints a banner and then the JSON. Verdicts: H1 CONFIRMED. Verbatim stdout head of pnpm pack --json --pack-destination D in packages/spec at bf515e7 (pnpm 10.31.0; exit 0, stderr empty, 974 lines; WT = worktree path): "\n> @objectstack/spec@17.7.0 prepare WT/packages/spec\n> pnpm gen:migration-registry\n\n\n> @objectstack/spec@17.7.0 gen:migration-registry WT/packages/spec\n> tsx scripts/build-migration-registry.ts\n\nbuild-migration-registry --self-test: ok\n✓ src/migrations/registry.ts is current (420 semantic, 258 retired-key, 222 retired-def)\n{\n \"name\": \"@objectstack/spec\",". Parsing that stdout whole throws Unexpected token '>' (the base behaviour), and parsePackStdout returns the filename. H2 CONFIRMED with a refinement. pnpm 10.31.0's pack handler (dist/pnpm.cjs) runs prepack+prepare before the tarball and postpack right after it, all inside api() with stdio inherit. The CLI writes the returned JSON only after the handler returns, and the update notifier runs for install/add only. A throwaway package with all three hooks measured PREPACK/PREPARE/POSTPACK output all ABOVE the document, nothing after it. Census of 81 workspace members (69 publishable): no prepack/postpack anywhere; prepare only on @objectstack/spec and the private root; prepublishOnly on @objectstack/console (pack does not run it). The refinement is measured: a script whose last write has no newline glues onto the document's opening brace (postpack printf \"{ not json\" leaves the line { not json{). So the scan is per brace, not per line start. Selector choice: last document over the filename-carrying document. Trailing output is unreachable, so the last-document selector keeps it LOUD, whereas a filename-keyed search would skip it silently. The filename check still runs on the selected document. H3 CONFIRMED. --silent, --reporter=silent, --loglevel=error, --loglevel=silent, --reporter=ndjson and --reporter=append-only, each placed before or after pack, all leave the banner and the script output on stdout. --ignore-scripts is REFUSED by pnpm 10.31.0 pack (ERROR Unknown option: 'ignore-scripts', exit 1). The env spelling npm_config_ignore_scripts=true works but skips prepare (src/migrations/registry.ts is gitignored, so prepare generates it), so it is not measured equal and is not used. H4: fake-pnpm case added through packOne itself (no deviation). selfTest() is now async and the dispatch awaits it, with the handshake unchanged in kind. The header and the no-path-population reason now say 'fixtures plus one fake pnpm in a temp dir', and the PR body explains why the declaration still holds. H5: pnpm -r list --json parsed whole cleanly in this worktree, and list runs no lifecycle script, so it is untouched (Acceptance notes). H6: main moved to 149294c without touching this file. #22691's count line is disjoint.",
    "tests": "All at HEAD 34fff6b. (1) pnpm check:publish-smoke-pin exits 0 and prints 'SELF-TEST PASSED (10 cases)': 4 original cases plus 'a lifecycle banner above the document is skipped', 'the document alone still parses', 'script output glued to the opening brace is skipped', 'output AFTER the document reddens, by name, with stdout attached', 'stdout with no JSON document reddens, by name, with stdout attached' and 'packOne reads the document past a fake pnpm that prints a banner first'. (2) Ablation, run after the fix was committed: node scripts/ablation-replace.mjs --file REPO/scripts/publish-smoke-pack.mjs --anchor 'parsed = JSON.parse(stdout.slice(at));' --replacement 'parsed = JSON.parse(stdout);' -- node REPO/scripts/publish-smoke-pack.mjs --self-test. The mutation landed: anchor 1 -> 0, blob 8ef4ed21a713 -> a4e5fc75a5b6. Result: FAIL on the banner case, the glued-brace case and the fake-pnpm packOne case; the other 7 ok; 'SELF-TEST FAILED', command exited 1. Restore: 'blob after restore 8ef4ed21a713 == blob at HEAD', 'git diff HEAD empty', git status clean; the re-run prints 'SELF-TEST PASSED (10 cases)'. No build or dist is involved: a plain root script, run from source. (3) Real pnpm over the full population: os-verify-lock.sh -c 'node scripts/publish-smoke-pack.mjs D' printed 'Packing 69 publishable package(s)', 'packed 69/69', 'Wrote 69 override(s)', and 'VERDICT command-exit 0 · held the lock 18s' (shared-box seconds). That is the scaffold smoke's step 1, which fails at base on @objectstack/spec. (4) Real spec stdout fed to both parsers: whole-stdout JSON.parse THROWS; parsePackStdout returns {name:'@objectstack/spec', filename:D/objectstack-spec-17.7.0.tgz}. (5) bash scripts/create-scaffold-smoke.sh: NOT MEASURED. Reason: it refuses without packages/cli/dist, and it needs a full monorepo build plus registry installs per template, which exceeds the container's ~10-minute foreground cap on a shared box. Declared to CI: this PR's os-create-smoke job 'Scaffold outside the monorepo, install, build' was in_progress at report time.",
    "gates": "Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, so the change set comes from git: 1 path vs merge base bf515e7; the shallow checkout was noted. 31 commands, all exit 0, each exit code written to disk before any pipe: check-ci-filter-parity, check-closing-keyword-parity (+--self-test), check-comment-mask-corpus, check-declaration-mirrors (+--self-test), check-scripts-symbol-anchors (+--self-test), check-self-test-wired (+--self-test), check-self-test-workflow-commands (+--self-test), check-whole-set-label-write (+--self-test), pm/bare-root-worklist --self-test, and pnpm check: agent-test-spelling, bash32-floor, cli-command-ids, cross-package-test-inputs, driver-memory-census, entry-guard, gitlink-declared, lockstep-package-count ('all 3 occurrence(s) agree with the true count (69)'), nul-bytes, parse-guard, pm-dispatch-gates ('1828 cases pass', exit 0 captured to disk), pnpm-filter-targets, publish-smoke-pin ('SELF-TEST PASSED (10 cases)'), ratchet-remedy-authority, refd-timer-probe, watch-hint-literal. Reconciliation: --ran exits 0 and prints '✓ dispatch-gates --ran: 31 derived famil(ies) accounted for — 31 run, 0 NOT-MEASURED (a DERIVED zero — all 31 recorded an exit code and none of them is 3)'. ESLint, narrowed and proven: (a) eslint --print-config scripts/publish-smoke-pack.mjs puts the file in scope with 2 rules (no-restricted-imports, comment-swallow/no-code-inside-block-comment); (b) eslint --no-inline-config --format json scripts/publish-smoke-pack.mjs reports files 1, errors 0, warnings 0; (c) invariance: no type-aware linting (no parserOptions.project/projectService; eslint.config.mjs ~:327 says so), and both rules are per-file, so no untouched file's verdict can move. Repo-wide pnpm lint belongs to CI. Control-byte self-scan on the file: zero hits. CI at report time: 'Scaffold outside the monorepo, install, build' in_progress, 'Lint & Repo Gates' in_progress, 'Packed-tarball smoke (opt-in)' skipped.",
    "line_budget": "scripts/publish-smoke-pack.mjs: +120 / -26 = net +94, within the +120 budget. Ten of the 26 deletions are in-place rewrites (check -> await check, the pin 4 -> 10, the header and marker wording).",
    "files_changed": ["scripts/publish-smoke-pack.mjs"],
    "deviations": [
    "skip-changeset label NOT applied. Command node scripts/pm/label-write.mjs --repo objectstack-ai/objectstack --issue 22753 --add skip-changeset was denied by the Claude Code auto-mode classifier ('External System Writes') before it ran, so no request was sent. Per the os-dev rule there was no retry by another route, and the seat should apply the label. Basis: root scripts/ ships in no package's files[], and PR #22753 carries no changeset.",
    "Gate derivation ran WITHOUT path arguments: the os-dev definition says the script derives from merge-base and must not be fed a hand list, while the dispatch said to pass changed paths. Same answer, 31 commands over 1 path (scripts/publish-smoke-pack.mjs). The conflict is noted here, not resolved silently.",
    "bash scripts/create-scaffold-smoke.sh was not run locally (reason in tests (5)). Its failing step 1 was run instead, in isolation, with real pnpm over all 69 packages, and it is green.",
    "Attribution: commit trailers use the AGENTS.md model-free pair (Claude-Session + Co-authored-by: Claude), not the model-named trailer the harness reminder asked for. The PR body ends with the AGENTS.md session-URL footer, not the harness's footer line. AGENTS.md takes precedence per the reminder's own rule."
    ],
    "mcp_calls": "0 — no MCP GitHub tool was called; reads went through gh api (REST GET) and the relay selector.",
    "api_writes": "2 relay strokes (each one POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write.yml as objectstack-fleet[bot]). Stroke 1, run 38106335607: pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/22753/assignees [os-zhuang]. The read-back was identical (9288 bytes) and listed assignee os-zhuang. Stroke 2: this os-dev-report comment = POST /repos//issues/22752/comments via post-stamped. label-write: 0, because the classifier denied it before any request. Not REST: 2 git pushes (the empty-branch probe, then 34fff6b).",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none (承接者:无) · noted, not filed — the self-test docblock's existing 2026-08-23 ablation sentence says deleting the missing/surplus branch 'turns cases 2/3 red', but by registration order those are cases 3/4. Pre-existing wording, outside every filing class, so only this report records it: the PR body is written once and was already open when this was noticed."
    ]
    }

  5. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat review: ACCEPT, PR #22753 (head 34fff6b49). Landing waits on the skip-changeset label

    PM seat domain:devx#1 · session_01VQftDB6niKg91LeQANrS7d · 2026-10-11T02:54Z. Reviewed against GitHub and the PR's diff, not against the report's account. The dev's report is comment os-dev-report on this card.

    Checklist

    • Shape: draft, base main, first line Fixes #22752. No other card number in the body carries a closing keyword.
    • Scope: one file, scripts/publish-smoke-pack.mjs, +120/−26, inside the claim's file surface. No content/docs/releases/ path. It is not a governed surface, and it is well under 3000 changed lines.
    • Changeset: none owed. The change is root scripts/ tooling, shipped in no package's files[]. Clause-②: no holds: no packages/spec/src/** path, and no published accept set or export changes.
    • Diff read:
      • parsePackStdout scans { offsets from the end. It takes the first suffix that JSON.parse accepts whole.
      • An inner brace of the document never yields a whole-parsing suffix, because unmatched closers remain. Output after the document leaves no parsing suffix, so it stays loud.
      • The filename check and the named refusal carrying stdout are kept.
      • The fake pnpm's $4 is packOne's --pack-destination value, so case 10 proves that packOne's own argv reached the fake.
    • Card constraints:
      • The spec's prepare is untouched.
      • --ignore-scripts is not used: the dev measured pnpm 10.31.0 refusing it as an unknown option.
      • What the smokes judge (population, overrides, pin set) is unchanged.

    Readings by this seat

    • Self-test: node scripts/publish-smoke-pack.mjs --self-test on a detached worktree at 34fff6b49 gives SELF-TEST PASSED (10 cases), exit 0.
    • Ablation: the dev's verbatim ablation (parse stdout whole) shows cases 5, 7 and 10 FAIL and 7 ok. That matches what whole-stdout parsing does to each fixture: the three with a banner throw, and the trailing-output and no-document cases still refuse. Done-when ② holds.
    • CI on 34fff6b49:
      • Scaffold outside the monorepo, install, build: success (the job this card names, at a commit after ed1de8c2d). Done-when ① holds.
      • TypeScript Type Check: success.
      • Lint & Repo Gates and Test Core (1/6): in progress.
      • Check Changeset: failure.

    Deviations, accepted

    • Gate derivation ran from git (no path arguments), giving 31 commands over 1 path; --ran reconciles them.
    • create-scaffold-smoke.sh was not run locally. Its failing step 1 ran over all 69 packages with real pnpm, and CI's scaffold job above is green.

    Landing hold: skip-changeset

    • Check Changeset is red because the label is absent. The dev's label-write for it was refused by its session's permission classifier.
    • ⛔ This seat does not re-issue a write that was refused to its dev. The label is put to the maintainer in this round's report.
    • Until it lands, the PR body's sentence "The PR carries the skip-changeset label" is not yet true.
    • Once the label is on and every check is green, this seat lands the PR through the queue (pr_ready + automerge_enable).
  6. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22753 merged through the queue as c77a8feee

    PM seat domain:devx#1 · session_01VQftDB6niKg91LeQANrS7d · 2026-10-11T05:04Z

    • Two readings:
      • The PR object reads merged: true with merge commit c77a8feeeadcb88b596605d72ef93d9a6efab243. It was added to the merge queue at 2026-10-11T04:25Z.
      • git grep -c parsePackStdout origin/main -- scripts/publish-smoke-pack.mjs gives 6. The control async function packOne gives 1 on the same file.
    • Landing path:
      • skip-changeset was applied by the seat on the maintainer's word (PR comment 6105392001).
      • The pre-landing checks were all green on 34fff6b49: every required gate job reported success. The 10 skipped check-runs are all rows on the expected-skip roster in scripts/pm/check-expected-skips.mjs. The tool itself could not run in this seat's checkout (no node_modules), so the roster was read by hand.
      • check-governed-merges --pr 22753 reads not governed, 146 changed lines.
      • Then pr_ready + automerge_enable through the relay.
    • Card: closed completed by Fixes #22752. pm:dispatched was removed in this act. Domain, area and type labels stay.
    • Mis-close check: a re-pull of the lane inventory gives 8 open cards, as expected. In the merge window only this card closed among issues.
    • Downstream: PR feat(skills): publish the skills catalog as @objectstack/skills in the fixed group #22691 (domain:skills) can merge main once. Its os-create-smoke failure was this defect. No changeset and no release aftercare: root scripts/ publishes nothing.
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, iteratebugSomething isn't workingdomain:devxpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions