From 8ab75c74b209cbe33b07dd516445c6e1893019da Mon Sep 17 00:00:00 2001 From: David Susskind Date: Thu, 23 Jul 2026 10:46:40 +0300 Subject: [PATCH 1/5] feat(push): confirm before destructive resource push MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit entities/agents/connectors push overwrite the app's resources and delete any not present locally — previously with no warning. Add a shared confirmPush guard that mirrors `base44 deploy`: warn + confirm interactively, `-y/--yes` to skip, and require `--yes` in non-interactive mode (throws, so `--json`/CI never hang waiting on a prompt). BREAKING CHANGE: non-interactive `base44 entities|agents|connectors push` now require `--yes`, consistent with `base44 deploy`. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/cli/src/cli/commands/agents/push.ts | 26 +++++++++++--- .../cli/src/cli/commands/connectors/push.ts | 15 +++++++- .../cli/src/cli/commands/entities/push.ts | 26 +++++++++++--- packages/cli/src/cli/utils/confirm-push.ts | 34 ++++++++++++++++++ packages/cli/src/cli/utils/index.ts | 1 + packages/cli/tests/cli/agents_push.spec.ts | 21 ++++++++--- .../cli/tests/cli/connectors_push.spec.ts | 35 ++++++++++++------- packages/cli/tests/cli/entities_push.spec.ts | 23 ++++++++---- 8 files changed, 147 insertions(+), 34 deletions(-) create mode 100644 packages/cli/src/cli/utils/confirm-push.ts diff --git a/packages/cli/src/cli/commands/agents/push.ts b/packages/cli/src/cli/commands/agents/push.ts index c20ba83e4..2e04896c5 100644 --- a/packages/cli/src/cli/commands/agents/push.ts +++ b/packages/cli/src/cli/commands/agents/push.ts @@ -1,13 +1,17 @@ import type { Command } from "commander"; import type { CLIContext, RunCommandResult } from "@/cli/types.js"; -import { Base44Command } from "@/cli/utils/index.js"; +import { Base44Command, confirmPush } from "@/cli/utils/index.js"; import { readProjectConfig } from "@/core/index.js"; import { pushAgents } from "@/core/resources/agent/index.js"; -async function pushAgentsAction({ - log, - runTask, -}: CLIContext): Promise { +interface PushOptions { + yes?: boolean; +} + +async function pushAgentsAction( + { isNonInteractive, log, runTask }: CLIContext, + options: PushOptions, +): Promise { const { agents } = await readProjectConfig(); log.info( @@ -16,6 +20,17 @@ async function pushAgentsAction({ : `Found ${agents.length} agents to push`, ); + const proceed = await confirmPush({ + isNonInteractive, + yes: options.yes, + log, + warning: + "This replaces all remote agent configs with your local agents. Remote agents not present locally will be deleted.", + }); + if (!proceed) { + return { outroMessage: "Push cancelled" }; + } + const result = await runTask( "Pushing agents to Base44", async () => { @@ -45,5 +60,6 @@ export function getAgentsPushCommand(): Command { .description( "Push local agents to Base44 (replaces all remote agent configs)", ) + .option("-y, --yes", "Skip the confirmation prompt") .action(pushAgentsAction); } diff --git a/packages/cli/src/cli/commands/connectors/push.ts b/packages/cli/src/cli/commands/connectors/push.ts index fd01ade55..5644d4dc4 100644 --- a/packages/cli/src/cli/commands/connectors/push.ts +++ b/packages/cli/src/cli/commands/connectors/push.ts @@ -2,7 +2,7 @@ import { resolve } from "node:path"; import type { Logger } from "@base44-cli/logger"; import type { Command } from "commander"; import type { CLIContext, RunCommandResult } from "@/cli/types.js"; -import { Base44Command, theme } from "@/cli/utils/index.js"; +import { Base44Command, confirmPush, theme } from "@/cli/utils/index.js"; import { getConnectorsUrl } from "@/cli/utils/urls.js"; import { readProjectConfig } from "@/core/index.js"; import { getAppContext } from "@/core/project/index.js"; @@ -22,6 +22,7 @@ import { interface PushOptions { dir?: string; + yes?: boolean; } /** @@ -133,6 +134,17 @@ async function pushConnectorsAction( } } + const proceed = await confirmPush({ + isNonInteractive, + yes: options.yes, + log, + warning: + "This overwrites your app's connectors with your local copy. Remote connectors not present locally will be removed.", + }); + if (!proceed) { + return { outroMessage: "Push cancelled" }; + } + const { results } = await runTask( "Pushing connectors to Base44", async () => { @@ -180,5 +192,6 @@ export function getConnectorsPushCommand(): Command { "--dir ", "Directory to read connector files from (default: ./connectors when using --app-id)", ) + .option("-y, --yes", "Skip the confirmation prompt") .action(pushConnectorsAction); } diff --git a/packages/cli/src/cli/commands/entities/push.ts b/packages/cli/src/cli/commands/entities/push.ts index 3c459e91e..ea3e1eaa1 100644 --- a/packages/cli/src/cli/commands/entities/push.ts +++ b/packages/cli/src/cli/commands/entities/push.ts @@ -1,13 +1,17 @@ import { Command } from "commander"; import type { CLIContext, RunCommandResult } from "@/cli/types.js"; -import { Base44Command } from "@/cli/utils/index.js"; +import { Base44Command, confirmPush } from "@/cli/utils/index.js"; import { readProjectConfig } from "@/core/index.js"; import { pushEntities } from "@/core/resources/entity/index.js"; -async function pushEntitiesAction({ - log, - runTask, -}: CLIContext): Promise { +interface PushOptions { + yes?: boolean; +} + +async function pushEntitiesAction( + { isNonInteractive, log, runTask }: CLIContext, + options: PushOptions, +): Promise { const { entities } = await readProjectConfig(); if (entities.length === 0) { @@ -17,6 +21,17 @@ async function pushEntitiesAction({ const entityNames = entities.map((e) => e.name).join(", "); log.info(`Found ${entities.length} entities to push: ${entityNames}`); + const proceed = await confirmPush({ + isNonInteractive, + yes: options.yes, + log, + warning: + "This overwrites your app's entities with your local copy. Remote entities not present locally will be deleted.", + }); + if (!proceed) { + return { outroMessage: "Push cancelled" }; + } + const result = await runTask( "Pushing entities to Base44", async () => { @@ -48,6 +63,7 @@ export function getEntitiesPushCommand(): Command { .addCommand( new Base44Command("push") .description("Push local entities to Base44") + .option("-y, --yes", "Skip the confirmation prompt") .action(pushEntitiesAction), ); } diff --git a/packages/cli/src/cli/utils/confirm-push.ts b/packages/cli/src/cli/utils/confirm-push.ts new file mode 100644 index 000000000..91c6cf82f --- /dev/null +++ b/packages/cli/src/cli/utils/confirm-push.ts @@ -0,0 +1,34 @@ +import type { Logger } from "@base44-cli/logger"; +import { confirm, isCancel } from "@clack/prompts"; +import { InvalidInputError } from "@/core/errors.js"; + +interface ConfirmPushOptions { + isNonInteractive: boolean; + yes: boolean | undefined; + log: Logger; + warning: string; +} + +/** + * Guard a destructive push: in non-interactive mode require --yes (throws + * otherwise, so --json/CI never hang on a prompt); interactively, warn and + * ask for confirmation. Returns false when the user declines. + */ +export async function confirmPush({ + isNonInteractive, + yes, + log, + warning, +}: ConfirmPushOptions): Promise { + if (isNonInteractive && !yes) { + throw new InvalidInputError("--yes is required in non-interactive mode"); + } + if (yes) { + return true; + } + log.warn(warning); + const proceed = await confirm({ + message: "Are you sure you want to continue?", + }); + return !isCancel(proceed) && proceed; +} diff --git a/packages/cli/src/cli/utils/index.ts b/packages/cli/src/cli/utils/index.ts index 3cfa787aa..61779b6fd 100644 --- a/packages/cli/src/cli/utils/index.ts +++ b/packages/cli/src/cli/utils/index.ts @@ -1,6 +1,7 @@ export * from "@base44-cli/logger"; export * from "./banner.js"; export * from "./command/index.js"; +export * from "./confirm-push.js"; export * from "./json.js"; export * from "./prompts.js"; export * from "./runTask.js"; diff --git a/packages/cli/tests/cli/agents_push.spec.ts b/packages/cli/tests/cli/agents_push.spec.ts index b59704578..1ccd7e96f 100644 --- a/packages/cli/tests/cli/agents_push.spec.ts +++ b/packages/cli/tests/cli/agents_push.spec.ts @@ -8,7 +8,7 @@ describe("agents push command", () => { await t.givenLoggedInWithProject(fixture("basic")); t.api.mockAgentsPush({ created: [], updated: [], deleted: [] }); - const result = await t.run("agents", "push"); + const result = await t.run("agents", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("No local agents found"); @@ -17,7 +17,7 @@ describe("agents push command", () => { it("fails when not in a project directory", async () => { await t.givenLoggedIn({ email: "test@example.com", name: "Test User" }); - const result = await t.run("agents", "push"); + const result = await t.run("agents", "push", "--yes"); t.expectResult(result).toFail(); t.expectResult(result).toContain("No Base44 app ID found"); @@ -31,7 +31,7 @@ describe("agents push command", () => { deleted: [], }); - const result = await t.run("agents", "push"); + const result = await t.run("agents", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("Found 3 agents to push"); @@ -45,7 +45,7 @@ describe("agents push command", () => { deleted: ["old_agent"], }); - const result = await t.run("agents", "push"); + const result = await t.run("agents", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("Agents pushed successfully"); @@ -57,7 +57,7 @@ describe("agents push command", () => { it("fails with helpful error when agent has empty name", async () => { await t.givenLoggedInWithProject(fixture("invalid-agent")); - const result = await t.run("agents", "push"); + const result = await t.run("agents", "push", "--yes"); t.expectResult(result).toFail(); }); @@ -66,8 +66,19 @@ describe("agents push command", () => { await t.givenLoggedInWithProject(fixture("with-agents")); t.api.mockAgentsPushError({ status: 401, body: { error: "Unauthorized" } }); + const result = await t.run("agents", "push", "--yes"); + + t.expectResult(result).toFail(); + }); + + it("fails when --yes is not provided in non-interactive mode", async () => { + await t.givenLoggedInWithProject(fixture("with-agents")); + const result = await t.run("agents", "push"); t.expectResult(result).toFail(); + t.expectResult(result).toContain( + "--yes is required in non-interactive mode", + ); }); }); diff --git a/packages/cli/tests/cli/connectors_push.spec.ts b/packages/cli/tests/cli/connectors_push.spec.ts index 9490011b2..b508de9fc 100644 --- a/packages/cli/tests/cli/connectors_push.spec.ts +++ b/packages/cli/tests/cli/connectors_push.spec.ts @@ -9,7 +9,7 @@ describe("connectors push command", () => { t.api.mockConnectorsList({ integrations: [] }); t.api.mockStripeStatus({ stripe_mode: null }); - const result = await t.run("connectors", "push"); + const result = await t.run("connectors", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("No local connectors found"); @@ -18,12 +18,23 @@ describe("connectors push command", () => { it("fails when not in a project directory", async () => { await t.givenLoggedIn({ email: "test@example.com", name: "Test User" }); - const result = await t.run("connectors", "push"); + const result = await t.run("connectors", "push", "--yes"); t.expectResult(result).toFail(); t.expectResult(result).toContain("No Base44 app ID found"); }); + it("fails when --yes is not provided in non-interactive mode", async () => { + await t.givenLoggedInWithProject(fixture("with-connectors")); + + const result = await t.run("connectors", "push"); + + t.expectResult(result).toFail(); + t.expectResult(result).toContain( + "--yes is required in non-interactive mode", + ); + }); + it("finds and lists connectors in project", async () => { await t.givenLoggedInWithProject(fixture("with-connectors")); t.api.mockConnectorsList({ integrations: [] }); @@ -34,7 +45,7 @@ describe("connectors push command", () => { already_authorized: true, }); - const result = await t.run("connectors", "push"); + const result = await t.run("connectors", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("Found 3 connectors to push"); @@ -50,7 +61,7 @@ describe("connectors push command", () => { already_authorized: true, }); - const result = await t.run("connectors", "push"); + const result = await t.run("connectors", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("googlecalendar"); @@ -68,7 +79,7 @@ describe("connectors push command", () => { t.api.mockStripeStatus({ stripe_mode: null }); t.api.mockConnectorRemove({ status: "removed", integration_type: "slack" }); - const result = await t.run("connectors", "push"); + const result = await t.run("connectors", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("slack"); @@ -84,7 +95,7 @@ describe("connectors push command", () => { body: { error: "Server error" }, }); - const result = await t.run("connectors", "push"); + const result = await t.run("connectors", "push", "--yes"); // Errors are handled per-connector, command still succeeds t.expectResult(result).toSucceed(); @@ -101,7 +112,7 @@ describe("connectors push command", () => { already_authorized: false, }); - const result = await t.run("connectors", "push"); + const result = await t.run("connectors", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("needs authorization"); @@ -121,7 +132,7 @@ describe("connectors push command", () => { other_user_email: "other@example.com", }); - const result = await t.run("connectors", "push"); + const result = await t.run("connectors", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("Already connected by another user"); @@ -142,7 +153,7 @@ describe("connectors push command", () => { claim_url: "https://connect.stripe.com/setup/claim/xxx", }); - const result = await t.run("connectors", "push"); + const result = await t.run("connectors", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("Found 2 connectors to push"); @@ -161,7 +172,7 @@ describe("connectors push command", () => { already_authorized: true, }); - const result = await t.run("connectors", "push"); + const result = await t.run("connectors", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("Synced: slack, stripe"); @@ -173,7 +184,7 @@ describe("connectors push command", () => { t.api.mockStripeStatus({ stripe_mode: "sandbox" }); t.api.mockStripeRemove({ success: true }); - const result = await t.run("connectors", "push"); + const result = await t.run("connectors", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("Removed:"); @@ -194,7 +205,7 @@ describe("connectors push command", () => { body: { error: "Stripe install failed" }, }); - const result = await t.run("connectors", "push"); + const result = await t.run("connectors", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("Failed: stripe"); diff --git a/packages/cli/tests/cli/entities_push.spec.ts b/packages/cli/tests/cli/entities_push.spec.ts index 7b59e4bad..7d2885f4e 100644 --- a/packages/cli/tests/cli/entities_push.spec.ts +++ b/packages/cli/tests/cli/entities_push.spec.ts @@ -7,7 +7,7 @@ describe("entities push command", () => { it("warns when no entities found in project", async () => { await t.givenLoggedInWithProject(fixture("basic")); - const result = await t.run("entities", "push"); + const result = await t.run("entities", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("No entities found in project"); @@ -16,7 +16,7 @@ describe("entities push command", () => { it("fails when not in a project directory", async () => { await t.givenLoggedIn({ email: "test@example.com", name: "Test User" }); - const result = await t.run("entities", "push"); + const result = await t.run("entities", "push", "--yes"); t.expectResult(result).toFail(); t.expectResult(result).toContain("No Base44 app ID found"); @@ -30,7 +30,7 @@ describe("entities push command", () => { deleted: [], }); - const result = await t.run("entities", "push"); + const result = await t.run("entities", "push", "--yes"); t.expectResult(result).toContain("Found 2 entities to push"); t.expectResult(result).toContain("Customer"); @@ -45,7 +45,7 @@ describe("entities push command", () => { deleted: [], }); - const result = await t.run("entities", "push"); + const result = await t.run("entities", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("Entities pushed successfully"); @@ -56,7 +56,7 @@ describe("entities push command", () => { it("fails with helpful error when entity is missing required fields", async () => { await t.givenLoggedInWithProject(fixture("invalid-entity")); - const result = await t.run("entities", "push"); + const result = await t.run("entities", "push", "--yes"); t.expectResult(result).toFail(); t.expectResult(result).toContain("name"); @@ -65,7 +65,7 @@ describe("entities push command", () => { it("fails with helpful error when config has invalid JSON", async () => { await t.givenLoggedInWithProject(fixture("invalid-json")); - const result = await t.run("entities", "push"); + const result = await t.run("entities", "push", "--yes"); t.expectResult(result).toFail(); t.expectResult(result).toContain("config.jsonc"); @@ -78,8 +78,19 @@ describe("entities push command", () => { body: { error: "Internal server error" }, }); + const result = await t.run("entities", "push", "--yes"); + + t.expectResult(result).toFail(); + }); + + it("fails when --yes is not provided in non-interactive mode", async () => { + await t.givenLoggedInWithProject(fixture("with-entities")); + const result = await t.run("entities", "push"); t.expectResult(result).toFail(); + t.expectResult(result).toContain( + "--yes is required in non-interactive mode", + ); }); }); From 12ec5d8f03e5ca71561061b7abb3509a21be379b Mon Sep 17 00:00:00 2001 From: David Susskind Date: Thu, 23 Jul 2026 10:54:48 +0300 Subject: [PATCH 2/5] chore(push): match deploy's --yes flag wording Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/cli/src/cli/commands/agents/push.ts | 2 +- packages/cli/src/cli/commands/connectors/push.ts | 2 +- packages/cli/src/cli/commands/entities/push.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/cli/commands/agents/push.ts b/packages/cli/src/cli/commands/agents/push.ts index 2e04896c5..5410a5e00 100644 --- a/packages/cli/src/cli/commands/agents/push.ts +++ b/packages/cli/src/cli/commands/agents/push.ts @@ -60,6 +60,6 @@ export function getAgentsPushCommand(): Command { .description( "Push local agents to Base44 (replaces all remote agent configs)", ) - .option("-y, --yes", "Skip the confirmation prompt") + .option("-y, --yes", "Skip confirmation prompt") .action(pushAgentsAction); } diff --git a/packages/cli/src/cli/commands/connectors/push.ts b/packages/cli/src/cli/commands/connectors/push.ts index 5644d4dc4..a60115a9b 100644 --- a/packages/cli/src/cli/commands/connectors/push.ts +++ b/packages/cli/src/cli/commands/connectors/push.ts @@ -192,6 +192,6 @@ export function getConnectorsPushCommand(): Command { "--dir ", "Directory to read connector files from (default: ./connectors when using --app-id)", ) - .option("-y, --yes", "Skip the confirmation prompt") + .option("-y, --yes", "Skip confirmation prompt") .action(pushConnectorsAction); } diff --git a/packages/cli/src/cli/commands/entities/push.ts b/packages/cli/src/cli/commands/entities/push.ts index ea3e1eaa1..bb67c1dda 100644 --- a/packages/cli/src/cli/commands/entities/push.ts +++ b/packages/cli/src/cli/commands/entities/push.ts @@ -63,7 +63,7 @@ export function getEntitiesPushCommand(): Command { .addCommand( new Base44Command("push") .description("Push local entities to Base44") - .option("-y, --yes", "Skip the confirmation prompt") + .option("-y, --yes", "Skip confirmation prompt") .action(pushEntitiesAction), ); } From 8b1b19f5badb0d2bfd3696ea803057b552a7437b Mon Sep 17 00:00:00 2001 From: David Susskind Date: Thu, 23 Jul 2026 11:17:07 +0300 Subject: [PATCH 3/5] test(push): pass --yes where push exercises auth flows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new non-interactive confirm guard requires --yes. authorization.spec and env-token-auth.spec drive entities/agents push to test token refresh and workspace-key auth, and ran push without --yes — so the guard short-circuited them. Add --yes so they reach the real auth/API path (the token-refresh-failure case now exercises the actual failure instead of the guard). Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/cli/tests/cli/authorization.spec.ts | 6 +++--- packages/cli/tests/cli/env-token-auth.spec.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/cli/tests/cli/authorization.spec.ts b/packages/cli/tests/cli/authorization.spec.ts index 20874d60e..d8ca6ff93 100644 --- a/packages/cli/tests/cli/authorization.spec.ts +++ b/packages/cli/tests/cli/authorization.spec.ts @@ -44,7 +44,7 @@ describe("token refresh on 401", () => { deleted: [], }); - const result = await t.run("agents", "push"); + const result = await t.run("agents", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("Agents pushed successfully"); @@ -59,7 +59,7 @@ describe("token refresh on 401", () => { deleted: [], }); - const result = await t.run("entities", "push"); + const result = await t.run("entities", "push", "--yes"); t.expectResult(result).toSucceed(); t.expectResult(result).toContain("Entities pushed"); @@ -78,7 +78,7 @@ describe("token refresh on 401", () => { res.status(401).json({ error: "Unauthorized" }); }); - const result = await t.run("agents", "push"); + const result = await t.run("agents", "push", "--yes"); t.expectResult(result).toFail(); }); diff --git a/packages/cli/tests/cli/env-token-auth.spec.ts b/packages/cli/tests/cli/env-token-auth.spec.ts index a5d4c759b..0b8f0c2db 100644 --- a/packages/cli/tests/cli/env-token-auth.spec.ts +++ b/packages/cli/tests/cli/env-token-auth.spec.ts @@ -69,7 +69,7 @@ describe("env credential seeding", () => { res.status(200).json({ created: ["customer"], updated: [], deleted: [] }); }); - const result = await t.run("entities", "push"); + const result = await t.run("entities", "push", "--yes"); t.expectResult(result).toSucceed(); expect(authHeader).toBe(`Bearer ${jwt}`); @@ -102,7 +102,7 @@ describe("env credential seeding", () => { res.status(200).json({ created: ["customer"], updated: [], deleted: [] }); }); - const result = await t.run("entities", "push"); + const result = await t.run("entities", "push", "--yes"); t.expectResult(result).toSucceed(); expect(apiKeyHeader).toBe(workspaceApiKey); @@ -121,7 +121,7 @@ describe("env credential seeding", () => { body: { error: "Unauthorized", detail: "Invalid API key" }, }); - const result = await t.run("entities", "push"); + const result = await t.run("entities", "push", "--yes"); t.expectResult(result).toFail(); t.expectResult(result).toContain("workspace API key"); @@ -225,7 +225,7 @@ describe("env credential seeding", () => { res.status(200).json({ created: ["customer"], updated: [], deleted: [] }); }); - const result = await t.run("entities", "push"); + const result = await t.run("entities", "push", "--yes"); t.expectResult(result).toSucceed(); expect(apiKeyHeader).toBeUndefined(); From 3b9bc179bcf2e3d0e0563784219e5383a2784505 Mon Sep 17 00:00:00 2001 From: David Susskind Date: Thu, 23 Jul 2026 11:22:08 +0300 Subject: [PATCH 4/5] test(push): assert the real auth-failure reason in the refresh-fail test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Harden the token-refresh-failure test to assert the actual "Unauthorized" error, not just a non-zero exit — a bare toFail() let today's --yes guard false-pass it. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/cli/tests/cli/authorization.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/cli/tests/cli/authorization.spec.ts b/packages/cli/tests/cli/authorization.spec.ts index d8ca6ff93..e15a1e6ec 100644 --- a/packages/cli/tests/cli/authorization.spec.ts +++ b/packages/cli/tests/cli/authorization.spec.ts @@ -81,5 +81,8 @@ describe("token refresh on 401", () => { const result = await t.run("agents", "push", "--yes"); t.expectResult(result).toFail(); + // Assert the real auth-failure reason, not just a non-zero exit — otherwise + // an unrelated early failure (e.g. a missing --yes guard) would pass this. + t.expectResult(result).toContain("Unauthorized"); }); }); From a463dbe26823ce66ca90898f9bb0cc1b5cead531 Mon Sep 17 00:00:00 2001 From: David Susskind Date: Thu, 23 Jul 2026 12:19:16 +0300 Subject: [PATCH 5/5] chore(push): use future-tense "will" in confirm warnings Match the house convention for consequence warnings (deploy.ts, auth push/sso): "This will ... and any not present locally." Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/cli/src/cli/commands/agents/push.ts | 2 +- packages/cli/src/cli/commands/connectors/push.ts | 2 +- packages/cli/src/cli/commands/entities/push.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/cli/commands/agents/push.ts b/packages/cli/src/cli/commands/agents/push.ts index 5410a5e00..31215e4bc 100644 --- a/packages/cli/src/cli/commands/agents/push.ts +++ b/packages/cli/src/cli/commands/agents/push.ts @@ -25,7 +25,7 @@ async function pushAgentsAction( yes: options.yes, log, warning: - "This replaces all remote agent configs with your local agents. Remote agents not present locally will be deleted.", + "This will replace all remote agent configs with your local agents and delete any not present locally.", }); if (!proceed) { return { outroMessage: "Push cancelled" }; diff --git a/packages/cli/src/cli/commands/connectors/push.ts b/packages/cli/src/cli/commands/connectors/push.ts index a60115a9b..a7dbf353d 100644 --- a/packages/cli/src/cli/commands/connectors/push.ts +++ b/packages/cli/src/cli/commands/connectors/push.ts @@ -139,7 +139,7 @@ async function pushConnectorsAction( yes: options.yes, log, warning: - "This overwrites your app's connectors with your local copy. Remote connectors not present locally will be removed.", + "This will overwrite your app's connectors with your local copy and remove any not present locally.", }); if (!proceed) { return { outroMessage: "Push cancelled" }; diff --git a/packages/cli/src/cli/commands/entities/push.ts b/packages/cli/src/cli/commands/entities/push.ts index bb67c1dda..017531a76 100644 --- a/packages/cli/src/cli/commands/entities/push.ts +++ b/packages/cli/src/cli/commands/entities/push.ts @@ -26,7 +26,7 @@ async function pushEntitiesAction( yes: options.yes, log, warning: - "This overwrites your app's entities with your local copy. Remote entities not present locally will be deleted.", + "This will overwrite your app's entities with your local copy and delete any not present locally.", }); if (!proceed) { return { outroMessage: "Push cancelled" };