fix(branch merge): don't crash on the apply confirmation in non-interactive shells - #231
fix(branch merge): don't crash on the apply confirmation in non-interactive shells#231agent-zhang-beihai[bot] wants to merge 1 commit into
Conversation
`branch merge` prompted for apply confirmation with `clack.confirm` directly. @clack/core opens `new tty.WriteStream(0)` unconditionally, so in a non-interactive shell (CI, agent sandbox) the command printed the full reviewed SQL and then died with `TTY initialization failed: uv_tty_init returned EBADF` — after which the only way through was to re-run the identical merge with `-y`. Route the prompt through the TTY-safe `src/lib/prompts.ts` wrapper, and when stdin is not interactive and neither `-y` nor `--json` is set, throw a `CLIError` naming `-y` (and `--dry-run`) before the merge plan is computed and printed — same convention as `confirmPurchase` in `src/commands/domains/index.ts`. Nothing is applied on that path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Greptile SummaryThe PR prevents branch merges from reaching TTY-dependent confirmation in non-interactive shells and routes interactive confirmation through the shared prompt wrapper.
Confidence Score: 5/5The PR appears safe to merge, with no actionable changed-code defects identified. The new guard fails before preview or execution when confirmation cannot be obtained, while dry-run and explicit non-confirming modes continue through their existing paths.
|
| Filename | Overview |
|---|---|
| src/commands/branch/merge.ts | Adds a fail-closed non-interactive confirmation guard and replaces direct Clack calls with the shared TTY-safe prompt wrapper. |
| src/commands/branch/merge.test.ts | Updates the prompt mock to match the command dependency and verifies that non-interactive merges fail before prompting or execution. |
Reviews (1): Last reviewed commit: "fix(branch merge): don't crash on confir..." | Re-trigger Greptile
jwfing
left a comment
There was a problem hiding this comment.
Review — fix(branch merge): don't crash on the apply confirmation in non-interactive shells
Summary: Small, well-scoped fix that routes the merge confirmation through the TTY-safe prompts wrapper and fails fast with an actionable error in non-interactive shells before printing the merge plan — the change is correct, idiomatic, and covered by a regression test.
Requirements context
No /docs/superpowers/ in this repo. Specs live under docs/specs/ (per repo convention), and none there matches a branch-merge/TTY concern — this is a feedback-fix PR (feedback/a6be7db1), so I assessed against the PR description and the reported feedback item alone. The implementation faithfully addresses the reported symptom (uv_tty_init returned EBADF crash on the confirm step in non-TTY shells).
Findings
Critical
(none)
Suggestion
(none)
Information
- Functionality — piped-stdin confirmation is now foreclosed for
merge(src/commands/branch/merge.ts:43-50). The guard fires whenever!prompts.isInteractive, i.e. any non-TTY stdin — including a genuine pipe (yes | insforge branch merge feat-x) that theprompts.confirmwrapper'snonTtyConfirmpath (src/lib/prompts.ts:195-211) could otherwise have answered. This is not a regression (the oldclack.confirmpath crashed on any non-TTY stdin anyway) and it matches the establishedconfirmPurchaseprecedent (src/commands/domains/index.ts:140-152), where an explicit flag is required rather than reading a piped "y" for a destructive/irreversible action. Requiring-yexplicitly is the safer choice for an apply; noting only so the trade-off is on record. - Scope — other direct
clack.confirm/@clack/promptscallers remain (src/lib/credentials.ts,src/lib/auth.ts,src/lib/apify-bridge.ts,src/commands/webscraper/apify/*, etc.). They may share the same non-TTY crash mode, but fixing them is out of scope for this feedback item — leaving them untouched is correct scope discipline, flagged only for follow-up awareness.
Dimension coverage
- Software engineering: ✅ Regression test added (
src/commands/branch/merge.test.ts:143-179) asserting exit code 1, error text containsRe-run with -y, confirm prompt never invoked, andmergeBranchExecuteApinever called. The test mock was correctly switched from@clack/promptsto../../lib/prompts.jsto match the module the command actually imports (same pattern asposthog/setup.test.ts).CLIError(msg, 1, 'MERGE_CONFIRMATION_REQUIRED')matches the repo's error-code convention. - Functionality: ✅ The guard is placed before
mergeBranchDryRunApi(line 53), so the plan is no longer printed before the failure — exactly what the feedback asked for.-y,--json, and--dry-runcorrectly bypass the guard; the confirm branch (line 102) is now only reachable when interactive, soprompts.confirmalways takes theclackpath there. No behavior change for existing paths. - Security: ✅ No security-relevant changes — no new user input reaching SQL/shell/HTTP, no secrets/PII logged, no auth/authorization change.
rendered_sqlprinting is unchanged. - Performance: ✅ No new queries or loops; the early guard actually saves the dry-run network round-trip in the non-interactive-without-
-ycase.
Verdict
approved (informational — human approval is a separate action). Zero Critical findings; the two notes are informational only and need not block merge.
What was broken
insforge branch merge <name>calledclack.confirmfrom@clack/promptsdirectly.@clack/coreconstructsnew tty.WriteStream(0)unconditionally, so in a shell without a usable TTY (CI, agent sandboxes) the command printed the full reviewed SQL and the change summary, then died on the confirmation step with:Nothing was applied, and the only way through was to re-run the identical, already-reviewed merge with
-y.What changed
src/commands/branch/merge.ts:src/lib/prompts.ts(prompts.confirm/prompts.isCancel) instead of@clack/promptsdirectly, so the confirmation can no longer blow up on TTY init.-ynor--jsonis set, throwCLIError('Applying a merge requires confirmation, but stdin is not interactive. Re-run with -y to apply, or with --dry-run to preview the SQL.', 1, 'MERGE_CONFIRMATION_REQUIRED'). This follows the existing convention inconfirmPurchase(src/commands/domains/index.ts), and fires before the dry-run is computed and the plan printed — the feedback specifically asked to be told about-ybefore the full merge plan is dumped.--dry-run,--json, and-ybehavior are unchanged; the guard cannot apply a merge, it only fails fast.How it was verified
src/commands/branch/merge.test.ts:merge feat-xwithisInteractive: falseand no-yexits 1, the error text containsRe-run with -y, the confirm prompt is never invoked, andmergeBranchExecuteApiis never called.../../lib/prompts.jsinstead of@clack/prompts, matching the module the command actually uses (same pattern asposthog/setup.test.tsandwebscraper/apify/connect.test.ts).npm run lint(vitest + eslint): 782 passed / 13 skipped, eslint clean.Skill sync
Per DEVELOPMENT.md §3: no
InsForge/agent-skillsupdate needed — no command, flag, default, or output shape changed; only the non-TTY failure mode did.Addresses user feedback a6be7db1-7051-4d7d-99b4-a6933362bd2f (cli): Branch merge requires explicit -y in non-TTY Codex shell
🤖 Generated with Claude Code
Summary by cubic
Prevent
insforge branch merge <name>from crashing on apply confirmation in non-interactive shells. Previously it printed the merge plan and exited with EBADF; now it fails fast with an actionable error unless-yor--jsonis set, and uses a TTY-safe prompt.src/lib/prompts.ts(prompts.confirm/prompts.isCancel) instead of@clack/promptsto avoid@clack/coreTTY initialization.-ynor--jsonis provided (and not--dry-run), throwCLIError('MERGE_CONFIRMATION_REQUIRED')instructing to re-run with-yor--dry-runbefore computing/printing the plan.--dry-run,--json, and-ybehavior unchanged; the guard never applies changes and prints nothing on that path.src/commands/branch/merge.test.tsthat asserts the early error, no prompt, and no execute call; tests mock../../lib/prompts.jsrather than@clack/prompts.-yin non-TTY environments.Written for commit 1621f32. Summary will update on new commits.