From f66f085a416943e589615e53345d876b4ef33c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Sat, 26 Sep 2026 21:05:09 +0100 Subject: [PATCH] fix(cli): a confirm-gated command without a TTY says why it stopped (bug7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `peer kick ` from a script printed a bare «cancelled.» and looked like it ran and did nothing. Without a TTY the gate now explains that it needs confirmation and how to give it (--yes, --approval-mode yolo, or a policy rule), audits it as cancelled-no-tty, and still exits PERMISSION_DENIED. kick/disconnect/ban help lines show [-y]. Spec: .artifacts/specs/2026-09-26-session-identity-and-routing.md §6 Co-Authored-By: Claude Opus 5.5 --- apps/cli/src/entrypoints/cli.ts | 6 +++--- apps/cli/src/services/policy/index.ts | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/cli/src/entrypoints/cli.ts b/apps/cli/src/entrypoints/cli.ts index 2764cba..22d5b43 100644 --- a/apps/cli/src/entrypoints/cli.ts +++ b/apps/cli/src/entrypoints/cli.ts @@ -84,9 +84,9 @@ Mesh (alias: "workspace" — claudemesh workspace mirrors each) Peer (resource form, recommended) claudemesh peer list see who's online (alias: peers) - claudemesh peer kick

end session (alias: kick) - claudemesh peer disconnect

soft disconnect (alias: disconnect) - claudemesh peer ban

ban + revoke (alias: ban) + claudemesh peer kick

[-y] end session (alias: kick) + claudemesh peer disconnect

[-y] soft disconnect (alias: disconnect) + claudemesh peer ban

[-y] ban + revoke (alias: ban) claudemesh peer unban

lift a ban (alias: unban) claudemesh peer bans list banned members (alias: bans) claudemesh peer verify [p] safety numbers (alias: verify) diff --git a/apps/cli/src/services/policy/index.ts b/apps/cli/src/services/policy/index.ts index 5a12a41..59ef9f2 100644 --- a/apps/cli/src/services/policy/index.ts +++ b/apps/cli/src/services/policy/index.ts @@ -314,6 +314,16 @@ export async function gate(ctx: CheckContext, opts?: { policyPath?: string }): P } // prompt if (ctx.yes) return true; + // bug7 (2026-09-26): with no TTY there's no one to ask. It used to print a + // bare "cancelled." — which read like the command ran and did nothing. + if (!process.stdin.isTTY || !process.stdout.isTTY) { + process.stderr.write( + `\n ✘ ${ctx.resource} ${ctx.verb} needs confirmation and there's no terminal to ask on.` + + `\n re-run with --yes (or --approval-mode yolo, or a \`decision: allow\` rule in ${USER_POLICY_PATH}).\n`, + ); + audit({ ...ctx, decision: "cancelled-no-tty" }); + return false; + } const reason = result.reason ? ` — ${result.reason}` : ""; const confirmed = await confirmPrompt( `\n ⚠ ${ctx.resource} ${ctx.verb}${reason}. Continue?`,