@botbuddy/cli 1.43.0 → 1.43.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/stack.mjs +46 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbuddy/cli",
3
- "version": "1.43.0",
3
+ "version": "1.43.1",
4
4
  "description": "BotBuddy — Swarm coordination CLI for multi-agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
package/src/stack.mjs CHANGED
@@ -60,6 +60,13 @@ const CALLBACK_PORT_REMOTE_REFUSAL =
60
60
  "(pre-forward GatewayPorts probe). Use --local-only for a run that needs a callback port.";
61
61
  export const DEFAULT_RECEIPT_MAX_BYTES = 10240;
62
62
  export const DEFAULT_TIMEOUT_SEC = 3600;
63
+ /**
64
+ * BOT-2154: how long `stack up --local-exec` lets `supabase start` run before it gives up and
65
+ * releases the lease itself (`--provision-timeout` overrides). Deliberately below the server's
66
+ * 1200 s `local_exec_provision_ttl_secs` backstop, so the CLI's own clean `stack done` wins the
67
+ * race against the reaper.
68
+ */
69
+ export const LOCAL_EXEC_PROVISION_TIMEOUT_SEC = 900;
63
70
  /** BOT-2230: the bounds the control plane enforces on an owner place hold (hold_stack_lease_place). */
64
71
  export const HOLD_ON_TIMEOUT_MIN_SEC = 60;
65
72
  export const HOLD_ON_TIMEOUT_MAX_SEC = 14400;
@@ -122,6 +129,10 @@ ${bold("up OPTIONS")}
122
129
  deadline (exit 2) and a failed wait (its own exit code) never retry.
123
130
  --local-exec FALLBACK (no Helper): run 'supabase start' in an ISOLATED --stack-path
124
131
  stack and self-activate. Refused at the worktree root (shared stack).
132
+ --provision-timeout N LOCAL-EXEC: seconds 'supabase start' may run (default ${LOCAL_EXEC_PROVISION_TIMEOUT_SEC}). On
133
+ timeout up runs 'stack done --local-exec' itself and exits 6 with
134
+ park_reason provision_timeout (BOT-2154). The server also fails a local-exec
135
+ lease still provisioning after local_exec_provision_ttl_secs (default 1200).
125
136
  Refused (exit 4, ENGINE_PUBLISHES_BEYOND_LOOPBACK) when the Docker
126
137
  engine would publish the stack's ports beyond loopback (BOT-2146).
127
138
  --docker-context <name> Explicit Docker context for the mandatory local preflight
@@ -1851,7 +1862,16 @@ export function localProvision(opts, dockerTarget, execution, spawn = spawnSync,
1851
1862
  const url = resolveIsolatedStackApiUrl(stackDir, env);
1852
1863
  const spawnEnv = { ...dockerEnvForTarget(dockerTarget, env), VITE_SUPABASE_URL: url };
1853
1864
  process.stderr.write(`${yellow("⚠ LOCAL-EXEC FALLBACK")} — no BotBuddy Helper; running ${bold("supabase start")} in ${stackDir} (VITE_SUPABASE_URL=${url}).\n`);
1854
- const start = spawn("supabase", ["start", "--workdir", stackDir], { encoding: "utf8", env: spawnEnv });
1865
+ // BOT-2154: never an unbounded start — a hung `supabase start` held the host slot for hours.
1866
+ const timeoutSec = opts?.provisionTimeout ?? LOCAL_EXEC_PROVISION_TIMEOUT_SEC;
1867
+ const start = spawn("supabase", ["start", "--workdir", stackDir], {
1868
+ encoding: "utf8", env: spawnEnv, timeout: timeoutSec * 1000, killSignal: "SIGTERM",
1869
+ });
1870
+ if (start.error?.code === "ETIMEDOUT") {
1871
+ const timedOut = new Error(`supabase start did not finish within ${timeoutSec}s (--provision-timeout); stopping it and releasing the lease`);
1872
+ timedOut.code = "PROVISION_TIMEOUT";
1873
+ throw timedOut;
1874
+ }
1855
1875
  if (start.status !== 0) {
1856
1876
  throw new Error(`supabase start failed (${start.status}): ${(start.stderr || start.stdout || "").slice(0, 400)}`);
1857
1877
  }
@@ -1942,11 +1962,21 @@ function emit(receipt, opts, code) {
1942
1962
 
1943
1963
  // ── command orchestration ────────────────────────────────────────────────────
1944
1964
 
1965
+ /** BOT-2154: run `stack done` in-process and capture its receipt instead of printing it, so
1966
+ * `stack up` still emits exactly one receipt. */
1967
+ async function runLocalDone(leaseId, opts, deps = {}) {
1968
+ let receipt = null;
1969
+ const exitCode = await cmdDone(leaseId, opts, { ...deps, emitResult: (r, _o, code) => { receipt = r; return code; } });
1970
+ return { exitCode, receipt };
1971
+ }
1972
+
1945
1973
  export async function cmdUp(opts, {
1946
1974
  runPreflight = runLocalExecPreflight,
1947
1975
  callTool = callToolJson,
1948
1976
  authProvider = stackAuthHeader,
1949
1977
  localProvisionFn = localProvision,
1978
+ // BOT-2154: the self-release after a timed-out local start (`stack done --local-exec`).
1979
+ localDoneFn = null,
1950
1980
  waitFn = waitForLease,
1951
1981
  emitResult = emit,
1952
1982
  machineUuidFn = machineUuid,
@@ -2278,6 +2308,21 @@ export async function cmdUp(opts, {
2278
2308
  try {
2279
2309
  conn = connectionWithDockerTarget(localProvisionFn(opts, localDockerTarget, execution), localDockerTarget);
2280
2310
  } catch (e) {
2311
+ if (e?.code === "PROVISION_TIMEOUT") {
2312
+ // BOT-2154: a bounded start that ran out of time. Release the lease ourselves —
2313
+ // `stack done --local-exec` stops whatever the start left behind and reaps the lease
2314
+ // — instead of leaving it provisioning for the server's backstop to find.
2315
+ const done = await (localDoneFn ?? ((id, doneOpts) => runLocalDone(id, doneOpts, { callTool, authProvider })))(
2316
+ leaseId, { ...opts, localExec: true, disposition: "destroy", json: false });
2317
+ const doneReceipt = done?.receipt ?? {};
2318
+ const cleaned = done?.exitCode === 0;
2319
+ return emitResult(buildReceipt({
2320
+ command: "up", outcome: "timeout", code: "PROVISION_TIMEOUT", park_reason: "provision_timeout",
2321
+ lease_id: leaseId, slot, error: e.message, cleanup: cleaned,
2322
+ done: { outcome: doneReceipt.outcome ?? null, state: doneReceipt.state ?? null, exit_code: done?.exitCode ?? null },
2323
+ ...(cleaned ? {} : { cleanup_error: doneReceipt.error ?? `stack done --local-exec exited ${done?.exitCode}` }),
2324
+ }), opts, EXIT.LEASE_FAILED);
2325
+ }
2281
2326
  // Once the local provisioner has run, `supabase start` may have created
2282
2327
  // (or fully started) containers even on a nonzero exit or a status-parse
2283
2328
  // failure. Cancelling here would reap the lease and free the slot while