Skip to content

bug(create-plugin): cancelling at the Author/Description prompt crashes the CLI and leaves an empty target directory; the documented non-TTY default never runs #8786

Description

@os-bill

Filed by the director seat (summon #20, session_01Tep4AYXZvyBA7jsvne5KZV, os-bill) as the follow-up card owed by a post-merge contract review at CONTRACT_REVIEW_TIER that returned CHANGES REQUIRED on an already-merged head: #8779 (comment) (objectui PR #8779 @ a04441f7c9, merged as 6cc48c4e4c at 2026-09-09T06:42:59Z). Refs objectui#8041 (closed by that PR; its ruling — director batch #91, comment 5583986682, option C — is the standing ruling here, ⛔ not reopened).

What is wrong on main (measured by the review on the merged sources, driven through a real pty / pipe)

  1. Cancel at the Author or Description prompt → TypeError, exit 1, an empty packages/plugin-<name>/ left behind. packages/create-plugin/src/index.ts:83 calls prompts([...]) with no onCancel; prompts returns the partial answers on abort, so answers.author is undefined; fs.mkdirpSync(targetDir) at index.ts:116 has already run; the new licenseCopyrightHolder (templates.ts:537, vars.author.trim()) is the first code to dereference it, before any file is written (index.ts:135). The retry then fails at index.ts:108 ("Directory already exists"). Regression: the base commit wrote 9 files and exited 0 on the same cancel. A cancel at the fourth (License:) prompt is fine (MIT + 10 files).
  2. The non-TTY claim is false. packages/create-plugin/README.md:66-68 (published) and .changeset/8041-create-plugin-licence-prompt.md assert "A non-interactive run (no TTY, or a cancelled prompt) takes MIT and still writes the text". Measured: </dev/null → nothing written, exit 0, silent; piped `

→ nothing written. The behaviour is pre-existing (base identical) but the claim is new and was never measured — every piece of evidence in the PR was pty-driven. Ruling C's fourth clause ("non-interactive runs take the default and still write it") is therefore **not met**. 3. Non-blocking, same area:templates.ts:551-556says the MIT fallback keeps manifest and text in agreement, butbuildPackageJsonwritesvars.license verbatim (:148) while buildLicenseFile (:558) emits MIT text, so buildPluginFiles({license:'NOT-A-LICENCE'})yields a manifest claimingNOT-A-LICENCEbeside MIT text (internal-only: the package isbin-only). Nothing pins prompt order or the cancel / non-TTY CLI path; index.ts is unimportable (program.parse()at import,index.ts:158`), which is exactly where item 1 lives.

What the fix must do (from the review, verbatim in substance)

  1. Handle prompts cancellation in packages/create-plugin/src/index.ts: an onCancel that exits non-zero before fs.mkdirpSync (nothing written, no leftover directory), or at minimum coalesce answers.author ?? '' / answers.description ?? initial so licenseCopyrightHolder cannot throw.
  2. Make the non-TTY claim true or remove it: either detect !process.stdin.isTTY and take every prompt's initial (MIT included, still writing LICENSE) — the ruling-conformant direction — or correct README.md:66-68 and the changelog text to the measured behaviour.
  3. Add one test that drives the bundled CLI (or an extracted createPlugin) through cancel-at-Author and EOF stdin, so the emitted-file contract is pinned at the CLI level, not only at buildPluginFiles.
  4. Optional, same commit: make the templates.ts:551-556 fallback throw (or fix the docblock) so manifest and text cannot disagree.

Acceptance

  • Cancel at any of the four prompts: no TypeError; either nothing is written and the exit is non-zero with a message, or (ruling direction) the defaults are taken and the full 10-file set is written — one behaviour, stated in the README.
  • create-plugin </dev/null and piped input: the README's sentence and the measured behaviour agree.
  • A CLI-level test pins both paths; templates.test.ts:565's 10-key set and licenses.test.ts stay green.

Constraints

  • Clause-② at claim time: expect yes — the scaffolder's emitted file set and prompt behaviour are its published behaviour (the ruling's own words); draft PR + needs:contract-review on both carriers; a CONTRACT_REVIEW_TIER verdict on record before enqueue.
  • Changeset: @object-ui/create-pluginpatch if only the crash and the prose move; minor if the non-TTY default path is added (published behaviour grows). Never major (AGENTS.md).
  • ⛔ Do not reopen the licence-set question (four licences, no free text) — the maintainer's call, flagged on the PR body; objectui#8778 (copyright holder in the four emitted headers) stays separate.

Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpackage: create-pluginpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions