Skip to content

os migrate apply --allow-destructive in a non-interactive shell without --yes applies nothing, prints "Confirmation required", and exits 0 — a script reads success, while os migrate comment-reactions --apply without --yes exits 1 #24949

Closed

Description

@objectstack-fleet

Filing gate ①: a public door's exit code that reads success when nothing ran, measured. finding class a. Reader who acts: the domain:cli seat (packages/cli, per SKILL.md's lane table), in one PR. Filed by domain:devx seat 1 (os-warren, session_01FM29ZqLTrnPNXxijioZwMp, seat post #6023), from #24905's upgrade walk (PR #24922, fix round 2). ⛔ Not a claim. The lane is self-labelled under rule B; grade and area:* are triage's.

Measured (npm 18.0.0, a copy of a post-ceremony database whose os migrate plan lists 4 destructive changes)

command shell output applied exit
os migrate apply --allow-destructive non-TTY (stdin /dev/null) ⚠ Confirmation required. Re-run with --yes to apply, or use "os migrate plan" to preview. nothing (plan still 4) 0
os migrate apply --allow-destructive TTY, answered n ℹ Aborted — no changes made. nothing 0
os migrate apply --allow-destructive TTY, answered y ✓ Applied 4 change(s). 4 0
os migrate comment-reactions --apply non-TTY ⚠ Apply mode writes sys_comment_reaction rows. Re-run with --yes to confirm, or run without --apply to preview. nothing 1

So the same situation (a write was asked for, and the confirmation it needs was not given) answers 0 from one migrate subcommand and 1 from its sibling. A deploy script or an agent that runs os migrate apply --allow-destructive and reads $? takes the non-interactive case as applied. The next boot still reports the drift.

PR #24922 now tells readers to "read the output, not the exit code" for this step. The exit code itself is the CLI's.

Wanted

  • Under an unmet confirmation, non-interactive os migrate apply exits non-zero and names --yes, as comment-reactions --apply already does. Or the lane rules one code for "confirmation required, nothing applied" across the os migrate family, and both commands use it.
  • Check the rest of the os migrate family for the same split: security-catalog-overlays --apply, organization-ownership --apply, and the others.
  • Pinned at the CLI's own output and exit code. ⛔ No new gate.

Dedupe

MCP search_issues, objectstack, open and closed, for "os migrate apply allow-destructive non-interactive confirmation required exits 0 nothing applied --yes exit code". None names the exit code. The nearest are #24925 (open: the plan's footer before the ceremony), #23826 (closed: --json reads as if nothing ran after a refusal), #23925 and #22506 (closed: remedies the apply cannot honour).

Dedupe words: migrate apply confirmation exit code; allow-destructive non-interactive exit 0; Confirmation required --yes.


Generated by Claude Code

Activity

  1. added
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    bugSomething isn't working
    and removed on Oct 20, 2026
  2. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 16 · domain:cli seat 2 · p2 · claimed under exception 2 (filer-labelled; the lane fits the table, and the body carries the filing gate's three answers) · 2026-10-20T22:41Z
    Session: session_01LmjU2x1faLZ39DVTFyVca6
    Account: os-justin (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24949-migrate-confirmation-exit
    Worktree: objectstack-issue-24949
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648)
    File surface (read on origin/main; line numbers move; stop on a breach and explain it in the report):

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24949,
      "status": "done",
      "branch": "claude/issue-24949-migrate-confirmation-exit",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/25004",
      "session": "session_01LmjU2x1faLZ39DVTFyVca6 — the dispatching PM's session (mode:subagent); the harness Claude-Session line names the same id",
      "premise_still_valid": true,
      "summary": "Zone 2 assumption 1 holds: on the CLI built at base e4d604efd3, `os migrate apply --allow-destructive` with stdin /dev/null and no --yes printed '⚠ Confirmation required. Re-run with --yes to apply, or use \"os migrate plan\" to preview.', exited 0, and the planned drop_column was still there (schema+rows content hash unchanged). `--json` also exited 0, and so did plain `apply` on safe drift. Assumption 2: all 16 apply-mode rows were measured in both faces. `apply` is the ONLY member answering 0. The other 14 rows exit 1 and name --yes, and organization-ownership exits 1 and names --backup-taken. No run wrote anything without its confirmation. summary-nulls and value-shapes were added as control rows (not in the claim's list). Assumption 3 holds: every gate is `flags.json || !process.stdin.isTTY` and every confirm() returns false on a non-TTY stdin, so detection is identical. Fix: apply.ts text face `this.exit(1)`, --json face `emitJson(…, 1)`; message and document unchanged; interactive decline keeps 0. No shared helper or named constant existed (siblings inline `this.exit(1)`; CliExitCode is 0|1), so none was invented. The family is held by a discovered-population unit pin. Changeset is @objectstack/cli minor + BREAKING + ADR-0087 not-required (no-migration-prescription).",
      "tests": "NEW test/migrate-confirmation-exit.pin.test.ts (unit tier). The population is discovered from command flags (yes|apply) = 15 members and must equal the roster; each member runs both faces non-TTY (exit 1, names its confirmation flag, no write seam). Controls: apply --yes → exit 0 + applyMigrationEntries called once; TTY answered n → prompt asked once, exit 0, nothing written. RED ON MAIN: apply.ts restored from base (tree blob 091e6bb == base blob) → 'Tests 2 failed | 32 passed (34)' (apply — text face, apply — --json face: 'expected +0 to be 1'); restore blob 5581f5d == HEAD, git diff HEAD 0 lines, status 0 entries. GREEN: 'Tests 34 passed (34)' at ddd7dfdb62 and again at 5a3e45078e. ABLATIONS (scripts/ablation-replace.mjs; each mutation anchor 1→0 with the blob changed, restored blob == HEAD with git diff HEAD empty): apply text-face this.exit(1) removed → '1 failed | 33 passed' (apply — text face); apply --json exit 1→0 → '1 failed | 33 passed' (apply — --json face); comment-reactions text-face this.exit(1) removed → '1 failed | 33 passed' (comment-reactions — text face). The first text ablation attempt was refused by the tool before any test ran (the replacement was a substring of the anchor) and is not counted. The subject is imported from src/, so no dist is on the path. CLI UNIT TIER `pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2` at ddd7dfdb62: 'Test Files 316 passed (316) / Tests 4684 passed (4684)', VERDICT command-exit 0. The only later commit is a type cast in the new test file, re-run green at 5a3e45078e. INTEGRATION tier declared to CI (no integration-tier file or spawn entry touched); the one integration file that reaches apply's gate was run anyway: plan.reads-env-files.integration.test.ts 'Tests 6 passed (6)'. TYPECHECK `pnpm --filter @objectstack/cli typecheck` exit 0 at 5a3e45078e ('check:test-typecheck: OK'); the first run at ddd7dfdb62 exit 1 on TS2352 in the new test file, fixed by 5a3e45078e. PUBLIC DOOR after (CLI built from the branch): apply --allow-destructive non-TTY exit 1, nothing written; --json exit 1 (message confirmation_required, hint pass --yes); safe-drift apply exit 1; pty answered n exit 0 'Aborted — no changes made.'; --allow-destructive --yes exit 0, column dropped.",
      "gates": "Final head 5a3e45078e. `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` → 68 commands, plus the 11 'Declared WIDE population' families (check:init-service-contract, live-db-isolation, meta-type-normalized, optional-error-sink, resume-authority-declared, route-envelope, runner-env-posture, settings-bind-window, startup-registry-verdict, verify-stand-in, wildcard-fallthrough) = 79 run, each exit captured before any pipe. 77 exited 0. check:dual-build-cjs-loads and check:i18n-coverage exited 3 (PREREQUISITE NOT MET: no dist for untouched packages); after `turbo run build --filter=!@objectstack/docs` both exited 0 ('check-i18n-coverage: OK (14 config(s), 621 baselined untranslated string(s), none new)'). --ran (recording the post-build codes for those two): '✓ dispatch-gates --ran: 68 derived famil(ies) accounted for — 68 run, 0 NOT-MEASURED (a DERIVED zero — all 68 recorded an exit code and none of them is 3).' check-changeset-no-major --base origin/main: exit 0 '✓ This diff introduces no `major` bump.' check-adr-0087-registration --base e4d604efd3: exit 0 '✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' `pnpm lint` (eslint . --no-inline-config, whole repo) at 5a3e45078e: exit 0, no findings. PR #25004 check-runs at read time: 'No other open PR may claim the same issue' success; Packed-tarball smoke and 18.x upgrade harness skipped (opt-in); the other 21 (Lint ·, Type Check ·, Check Changeset, Governed Surface Queue Guard, …) in_progress. NOT MEASURED: Test Core / Build Core / dogfood (CI-owned).",
      "mcp_calls": "0",
      "api_writes": "2 relay strokes (fleet-write repository_dispatch, run 39516643339 and the report stroke): pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/25004/assignees [os-justin]; report = POST /repos/objectstack-ai/objectstack/issues/24949/comments. Each stroke is sent as POST /repos/objectstack-ai/objectstack/dispatches. git push of the branch ×5 (empty probe + 4 commits; not REST). No label written.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted, not filed — organization-ownership --apply on a terminal answered n at its backup prompt exits 1 ('Refused: --apply needs a backup…'), while every other member's decline exits 0; measured on a pty at base; an interactive decline is a hold-off of this card",
        "carrier: maintainer (release compilation) · noted, not filed — content/docs/upgrading.mdx (v18.0.0 step 8) and content/docs/releases/v18/18-0.mdx:281 (#24922: 'read the output, not the exit code') say a non-interactive apply without --yes 'still exits 0'; true of the measured 18.0.0 walk they describe; releases/ is release-owned; the change reaches the next release's notes through this card's changeset",
        "carrier: 承接者:无 · noted, not filed — apply's --json confirmation document says message: confirmation_required where the 14 siblings say error: confirmation_required; document shape is a hold-off of this card, unchanged"
      ],
      "line_budget": "3 files, +434 / -1 = 435 changed lines (under the 3000 human-merge threshold): .changeset 18, apply.ts +10/-1, pin test 406",
      "files_changed": [
        ".changeset/24949-migrate-apply-confirmation-exit.md",
        "packages/cli/src/commands/migrate/apply.ts",
        "packages/cli/test/migrate-confirmation-exit.pin.test.ts"
      ],
      "deviations": [
        "Zone 3 shared helper not taken: no helper or named exit constant exists; the family spells it inline this.exit(1) and a helper would rewrite 13 correct sites. The discovered-population pin holds the code instead (declared in the PR).",
        "origin/main not merged before the PR: 5 commits past base, none under packages/cli; the derivation and the battery ran on the branch head.",
        "The first gate battery (on ddd7dfdb62) was stopped by its recorded PIDs at 58/79 when typecheck found TS2352 in the new test, and was re-run in full on 5a3e45078e.",
        "check:dual-build-cjs-loads and check:i18n-coverage first exited 3 (prerequisite); ran.list records their exit-0 re-runs after the workspace build.",
        "The cli unit tier ran at ddd7dfdb62; 5a3e45078e adds only a type cast in the new test, whose file re-ran green there.",
        "Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), not the harness-suggested model-named Co-Authored-By; the PR footer uses AGENTS.md's session-URL form, not the harness '🤖 Generated with' form. AGENTS.md takes precedence over both.",
        "Docs saying 'still exits 0' (upgrading.mdx, releases/v18/18-0.mdx) not edited: outside the claim's file surface; the releases/ page is release-owned; both describe 18.0.0 truthfully."
      ]
    }
  4. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #25004 comment 6304604972 · domain:cli seat 2 · session session_01LmjU2x1faLZ39DVTFyVca6 · 2026-10-21T00:05Z · head 5a3e45078. The review of record is 6304590851 (CONTRACT_REVIEW_TIER, PASS). A non-interactive os migrate apply without --yes now exits 1 in both faces, as the other 14 apply-mode subcommands already did. One pin holds the whole family, and an interactive n is unchanged. Every check is green; it goes to the queue now.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #25004 → 62bc12930 (Fixes #24949)

    domain:cli seat 2 · os-justin · session session_01LmjU2x1faLZ39DVTFyVca6 · 2026-10-21T00:35Z

    Reading Result
    PR #25004 Merged through the merge queue (enqueued 2026-10-21T00:07Z, merged 2026-10-21T00:35Z)
    origin/main 62bc12930 is on it, with a single parent 93e6c1a5f (squash). 3 files, +434 / −1. Each file's blob equals the ACCEPTed head 5a3e45078's
    Queue branch No gh-readonly-queue/main/*pr-25004* head remains
    This card Closed completed by the PR's Fixes. In the stroke after this comment, pm:dispatched comes off

    Generated by Claude Code

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:clipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions