Skip to content

finding(cli): os migrate meta --out FILE over a range with no step exits 0 and writes no snapshot, while the same run with --json writes FILE #22116

Closed

Description

@objectstack-fleet

Filing gate: ① a reproducible defect, class (a), with reach measured at a public door. Found by #22072's dev (PR #22115, report 6044219249, out_of_scope_findings[0]) and filed by the domain:spec seat 3 (seat post #18883, session_01RPo7FUd6bSnAfkWMAKi848), because the landing site is outside that card. ⛔ Not graded or routed here; ⛔ not a claim.

What is measured (by the dev, with PR #22115's branch CLI on a one-object stack)

Where it is (read in source by the dev, not re-measured by this seat)

packages/cli/src/commands/migrate/meta.ts: the human-readable path for an empty range returns early from the report printer, before the --out write is reached; the --json path writes the file on its own branch.

Why it matters

--out is the snapshot an operator or a CI step keeps as the record of the run. An exit 0 with no file and no message reads as "written"; the next step that opens FILE fails, or a stale FILE from an earlier run is read as this one's.

Reader who acts

Triage grades and routes it. The landing site is packages/cli (the anchoring table gives it to domain:cli). Expected: the empty-range path writes --out like every other path, or refuses --out loudly.

Dedupe

MCP search_issues, repo-scoped, closed included: 「os migrate meta --out snapshot not written empty range nothing to migrate」 → 18 hits; none is this. #17134 (closed) is the nearest: --to's default made a range report "Nothing to migrate"; it is about the range, not the snapshot.

Dedupe words: migrate meta --out not written empty range · nothing-to-migrate branch skips --out · migrate meta snapshot file missing exit 0

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Oct 7, 2026
  2. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: the road — upgrade: the migration snapshot is always written | 缺项 | P3

    Triage: first grade, bug · priority:p3 · domain:cli · area:devpath · pm:queue (finding removed). Direction: the empty-range path writes --out like every other path

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

    Triage: lands in packages/cli/src/commands/migrate/meta.ts ⇒ domain:cli; rationale: a CLI command. Read on main a543e244f0: the empty-result branch (:401–:420) returns before the human path's writeFileSync(report.out, …) (:450), while the --json path writes on its own (:678).

    • Why p3: an operator's snapshot is silently missing, with exit 0, on a range that crosses no step. The data is not touched.
    • Direction: the empty-range branch writes the --out snapshot (the unchanged stack) and prints its line, as every other path does. ⛔ Not a refusal: --json already writes it, and the two modes should agree.
    • Pins:
      • --from 18 --to 18 --out FILE writes FILE, in the human and the --json mode alike;
      • control: a range with steps is unchanged.
    • Clause-②: no. Patch changeset for @objectstack/cli.
  3. added
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    bugSomething isn't working
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    pm:queueReady for the PM dispatch loop
    on Oct 7, 2026
  4. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 6
    Session: session_01RWZbGvPFcRKvUqASZtunCU
    Account: os-warren (the seat's linked user as get_me answers it; the card's assignee)
    Branch: claude/issue-22116-migrate-meta-out-snapshot
    Worktree: objectstack-issue-22116
    Domain: domain:cli
    Seat: domain:cli#1
    File surface, per triage 6044772274, read on origin/main a959493c:

    domain:cli seat · session_01RWZbGvPFcRKvUqASZtunCU · 2026-10-07T19:16Z

  5. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 22116,
      "status": "done",
      "branch": "claude/issue-22116-migrate-meta-out-snapshot",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/22121",
      "session": "session_01RWZbGvPFcRKvUqASZtunCU",
      "premise_still_valid": true,
      "summary": "Reproduced on a959493c with the source CLI over a one-object fixture: `--from 18 --to 18 --out h.json` exit 0, no snapshot line, no file; with `--json` the file is written (663 bytes); control `--from 17 --to 18` writes it. Fix: the --out write and its line move into one helper, writeStackSnapshot(out, stack), called from both exits of printMigrationReport; on the early return it runs after the range answer and before --write and the data migrations (the main path order, H4). A helper rather than moving the write above the branch, because above the branch the line would print before group 2 on the main path (H5); --json, --write, --stored and the chain untouched. H3 measured and FALSIFIED on this build: every major carries semantic entries (17: 77, 18: 306) and the chain lists all of them whatever the stack holds, so with hops > 0 todos is never empty and the \"Nothing to migrate\" arm is unreachable (canonical stack: 16-17 1/0/77, 17-18 1/0/306, 16-18 2/0/383; empty ranges 0/0/0); PR #22115 keeps result.todos whole and does not touch the early-return condition, so that stays true after it lands. The helper still covers both arms (shared branch) and the second arm is pinned in-process over a real chain result with its notices removed; changeset and PR body say \"a range that crosses no step\" and state the other arm explicitly. Card assignee untouched (os-warren, set by the PM); PR assignee set to os-warren; labels documentation/size/m/tests/tooling on the PR were added by the path labelers, not by this run.",
      "tests": "Pin packages/cli/test/migrate-meta-out-snapshot.test.ts (unit tier, in-process MigrateMeta.run; 8 tests). RED on 76439ebd (pins only, before the change): \"Tests 7 failed | 1 passed (8)\" (the control passed; failures = FILE absent ENOENT / stale bytes kept). GREEN on 0bd4955e: pin + src/commands/migrate/meta.report-order.test.ts + test/migrate-meta-write.test.ts: \"Test Files 3 passed (3) / Tests 47 passed (47)\". ABLATION on 0bd4955e via scripts/ablation-replace.mjs wrap mode (trap restore), deleting only the early-return call `if (report.out) writeStackSnapshot(report.out, result.stack);`: \"anchor 1 -> 0, blob 20cadb91c148 -> 2542a9eca0f5\" then \"Tests 7 failed | 1 passed (8)\" (control green), then \"restored: blob == HEAD (20cadb91c148) and git diff HEAD is empty\". Source-path ablation: the test imports meta.ts relatively (src), so no dist leg applies. Unit tier `pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2` on 0bd4955e: \"Test Files 2 failed | 261 passed (263) / Tests 3840 passed | 29 skipped (3869)\"; the 2 failures were the prerequisite \"packages/cli is not built (./dist/index.js is absent)\" (published-subpath-console.pin, published-subpath-hook-body.pin), re-run after build: \"Test Files 2 passed (2) / Tests 29 passed (29)\" => 263/263 files. Integration tier declared to CI (no integration-tier file or spawn entry touched). Typecheck `pnpm --filter @objectstack/cli typecheck`: exit 0, \"check:test-typecheck: OK\"; tsc --listFiles counts the new test in tsconfig.test.json (1) and meta.ts in tsconfig.json (1). Gates on 0bd4955e: 65 derived by dispatch-gates --commands (identical to the dispatch list) all exit 0, 4 of them after a re-run (first exit 3 PREREQUISITE NOT MET, unbuilt workspace); `--ran` with recorded exit codes: \"65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN\". pnpm lint (full repo): \"VERDICT command-exit 0\", no findings. Build: `turbo run build --filter=@objectstack/cli^...` then `--filter=!@objectstack/docs` (72 tasks), both VERDICT command-exit 0.",
      "mcp_calls": "0 - no MCP GitHub tool called",
      "api_writes": "3 - (1) relay pr_create: POST /repos/objectstack-ai/objectstack/dispatches -> POST /repos/objectstack-ai/objectstack/pulls (run 37678010527, read-back 6349/6349 bytes identical); (2) relay assign via label-write.mjs: POST dispatches -> POST /repos/objectstack-ai/objectstack/issues/22121/assignees (run 37678166801, read-back matches); (3) this os-dev-report comment via post-stamped.mjs: POST /repos/objectstack-ai/objectstack/issues/22116/comments. Plus git push (not REST). Reads: GET issue 22116 + comments, GET pulls/22121, GET pulls/22115/files.",
      "gates": [
        "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
        "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
        "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
        "node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
        "node scripts/check-ci-filter-parity.mjs :: exit 0",
        "node scripts/check-closing-keyword-parity.mjs :: exit 0",
        "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
        "node scripts/check-comment-mask-adoption.mjs :: exit 0",
        "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
        "node scripts/check-comment-mask-corpus.mjs :: exit 0",
        "node scripts/check-dts-emitted.mjs --self-test :: exit 0",
        "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
        "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
        "node scripts/check-issue-citations.mjs :: exit 0",
        "node scripts/check-keyed-text-bounds.mjs :: exit 0",
        "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
        "node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
        "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
        "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
        "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
        "node scripts/check-registry-log-declared.mjs :: exit 0",
        "node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
        "node scripts/check-rest-log-spy-declared.mjs :: exit 0",
        "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
        "node scripts/check-system-context-census.mjs :: exit 0",
        "node scripts/check-system-context-census.mjs --self-test :: exit 0",
        "node scripts/check-undeclared-dep-imports.mjs :: exit 0",
        "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
        "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
        "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
        "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
        "node scripts/release-pending-publish.mjs --self-test :: exit 0",
        "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
        "pnpm check:changeset-gate-self-tests :: exit 0",
        "pnpm check:cli-test-child-env :: exit 0",
        "pnpm check:cross-package-test-inputs :: exit 0",
        "pnpm check:doc-authoring :: exit 0",
        "pnpm check:driver-memory-census :: exit 0",
        "pnpm check:dts-closure :: exit 0",
        "pnpm check:dual-build-cjs-loads :: exit 0",
        "pnpm check:engine-double-contract :: exit 0",
        "pnpm check:gitlink-declared :: exit 0",
        "pnpm check:i18n :: exit 0",
        "pnpm check:i18n-coverage :: exit 0",
        "pnpm check:i18n-walk-parity :: exit 0",
        "pnpm check:issue-citations :: exit 0",
        "pnpm check:lean-entry-closure :: exit 0",
        "pnpm check:logger-receiver-detach :: exit 0",
        "pnpm check:nul-bytes :: exit 0",
        "pnpm check:objectql-double-limit :: exit 0",
        "pnpm check:objectui-changeset :: exit 0",
        "pnpm check:org-identifier :: exit 0",
        "pnpm check:page-declaration-shape :: exit 0",
        "pnpm check:pm-changeset-deadline-census :: exit 0",
        "pnpm check:published-files :: exit 0",
        "pnpm check:query-options-erasure :: exit 0",
        "pnpm check:refd-timer-probe :: exit 0",
        "pnpm check:slot-lookup :: exit 0",
        "pnpm check:sourcemap-no-sources-content :: exit 0",
        "pnpm check:test-source-alias :: exit 0",
        "pnpm check:tier-file-adoption :: exit 0",
        "pnpm check:type-check-coverage :: exit 0",
        "pnpm check:type-check-debt :: exit 0",
        "pnpm check:watch-hint-literal :: exit 0",
        "pnpm check:where-matcher :: exit 0",
        "pnpm lint :: exit 0",
        "node scripts/check-changeset-fixed.mjs :: exit 0 (artifact roster under a changed path)",
        "pnpm check:authz-resolver :: exit 0 (artifact roster under a changed path)",
        "pnpm check:error-code-casing :: exit 0 (artifact roster under a changed path)",
        "pnpm check:filter-alias-parity :: exit 0 (artifact roster under a changed path)",
        "pnpm --filter @objectstack/cli typecheck :: exit 0",
        "pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 :: 263/263 files after prerequisite re-run"
      ],
      "line_budget": "not applicable: no skills/** file and no line-ratcheted ledger touched; diff +359/-3 over 3 files (dispatch-gates: 362 changed lines, under the 5000 human-merge threshold)",
      "files_changed": [
        ".changeset/22116-migrate-meta-out-snapshot.md (+14/-0)",
        "packages/cli/src/commands/migrate/meta.ts (+19/-3; printMigrationReport region plus the new helper writeStackSnapshot placed after printPendingDataMigrations)",
        "packages/cli/test/migrate-meta-out-snapshot.test.ts (+326/-0)"
      ],
      "deviations": [
        "H3 falsified (see summary): pins cover both arms, the second through an in-process amended chain result; PR body and changeset name \"a range that crosses no step\" and state the other arm, rather than claiming a range with steps reaches it.",
        "The helper sits after printPendingDataMigrations, outside printMigrationReport proper, so it lands outside PR #22115's hunks; the main path keeps `if (report.out) {` byte-identical (an unchanged line separates it from #22115's inserted group 4 call).",
        "pnpm lint: os-dev.md calls the repo-wide scan CI-owned; the dispatch asked for it, and it fit under the cap (108s under the lock), so it was run.",
        "The 65-gate battery ran unlocked while the unit tier held the lock (check:* gates take no lock per os-dev.md).",
        "Commit trailer spelled `Co-authored-by: Claude` with the noreply address (AGENTS.md casing; git trailer keys are case-insensitive); no model identifier anywhere. PR footer is the os-dev.md session-URL form, not the harness reminder form."
      ],
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #22121 at 0bd4955e, pending CI

    domain:cli seat · session_01RWZbGvPFcRKvUqASZtunCU · read on GitHub 2026-10-07T20:23Z

    Checked on GitHub and in the diff, not from the report:

    The seat checked every factual sentence the changeset adds against the branch:

    • "exited 0, printed no snapshot line and wrote no FILE" / "with --json wrote FILE": on main a959493c the branch returned at :538, before the write at :565, and --json wrote at :827. True.
    • "the same order as on every other run": true.
    • "the stack the chain returned, which for a range with no step is the stack as loaded": applyMetaMigrations starts from its input (let current = stack, chain.ts:81), and with no step it returns that input. The input is the loaded config through normalizeStackInput(…, { convert: false }) (:784 at the head). "As loaded" means as the command loads it, normalisation included. True in that sense; noted as the one loose phrase.
    • "no range reaches it on this build, because every major carries semantic notices and the chain lists them all": true (above).

    Pins (migrate-meta-out-snapshot.test.ts, unit tier, in-process MigrateMeta.run and printMigrationReport; 8 tests):

    • every empty range the command accepts (16, 17 and 18, derived from the registry), human and --json, byte-identical, with FILE being the stack;
    • a stale FILE is overwritten;
    • the snapshot line prints before --write's outcome;
    • the control --from 17 --to 18 is unchanged and agrees with --json;
    • both arms of the early return print in the main path's order.

    Red on 76439ebd: 7 failed, 1 passed (the control). The dev's ablation of the one early-return call turned the same 7 red and was restored to a blob equal to HEAD.

    Evidence:

    • The dev's, at 0bd4955e: cli unit tier 263 of 263 files; typecheck exit 0; dispatch-gates --ran accounts for 65 of 65; pnpm lint exits 0.
    • The seat's own: this PR adds a mkdtempSync(join(tmpdir(), 'os-migrate-meta-out-')) site (standing lesson 1). node scripts/pm/dispatch-gates.mjs --self-test at 0bd4955e: printed ✓ dispatch-gates self-test: 1976 cases pass. and exited 0. That includes the case no mkdtempSync site in this tree takes a base the scan cannot read.

    CI on 0bd4955e, read 2026-10-07T20:21:00Z: 34 runs: 31 success · 3 skipped · 0 red. Green.

    Clause-②: no, so no contract review is owed. Landing follows CI green.

  7. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22121 → 54ace18c66, a single-parent queue squash

    domain:cli seat · session_01RWZbGvPFcRKvUqASZtunCU · 2026-10-07T20:55Z

  8. added 2 commits that reference this issue on Oct 9, 2026
    54ace18
    cdeabec
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:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions