From 776c6f9d22066851992e190474a7ba70fcc33c15 Mon Sep 17 00:00:00 2001 From: Bohdan Vilishchuk Date: Tue, 4 Aug 2026 17:43:44 +0300 Subject: [PATCH 1/8] fix(setup): detect OrbStack vs Docker Desktop before relaunching the daemon ensureDocker() always ran `open -a Docker` to relaunch a stopped daemon on macOS, which silently no-ops for OrbStack users (no Docker.app bundle exists), leading to a misleading "GUI license acceptance" timeout error. Now it checks the docker CLI's active context first (accurate regardless of install location) and falls back to checking for OrbStack.app, so the wizard launches and messages the app that's actually installed. --- scripts/setup/docker.ts | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/scripts/setup/docker.ts b/scripts/setup/docker.ts index 5a568c15ec4..77a546c88b2 100644 --- a/scripts/setup/docker.ts +++ b/scripts/setup/docker.ts @@ -1,4 +1,5 @@ import { spawnSync } from 'node:child_process' +import { existsSync } from 'node:fs' import { SetupError } from './errors.ts' import { waitFor } from './probes.ts' import * as p from './prompter.ts' @@ -9,6 +10,10 @@ const INSTALL_HINTS = [ `or OrbStack (lighter on macOS): ${theme.command('brew install orbstack')}`, ] +/** macOS GUI docker providers we know how to launch via `open -a`. */ +const ORBSTACK_APP = { name: 'OrbStack', path: '/Applications/OrbStack.app' } as const +const DOCKER_DESKTOP_APP = { name: 'Docker', path: '/Applications/Docker.app' } as const + function daemonUp(): boolean { return spawnSync('docker', ['info'], { stdio: 'ignore' }).status === 0 } @@ -19,6 +24,24 @@ function installed(): boolean { return Bun.which('docker') !== null } +function currentDockerContext(): string | null { + const result = spawnSync('docker', ['context', 'show'], { encoding: 'utf8' }) + return result.status === 0 ? result.stdout.trim() : null +} + +/** + * Which GUI app owns the `docker` CLI on this Mac. Docker Desktop and OrbStack + * both install a `docker` binary, so presence of the CLI alone doesn't tell us + * which app to relaunch. Prefer the docker CLI's own active context — it's + * accurate regardless of where the app bundle lives — and fall back to + * checking the well-known `.app` install paths when the context doesn't say. + */ +function macDockerApp(): typeof ORBSTACK_APP | typeof DOCKER_DESKTOP_APP { + if (currentDockerContext() === 'orbstack') return ORBSTACK_APP + if (existsSync(ORBSTACK_APP.path)) return ORBSTACK_APP + return DOCKER_DESKTOP_APP +} + /** * Returns whether the Docker daemon is available, offering to launch Docker * Desktop (macOS) when it's installed but stopped. Never installs anything. @@ -41,27 +64,31 @@ export async function ensureDocker(required: boolean): Promise { return false } + const app = macDockerApp() + const launch = await p.confirm({ - message: 'Docker is installed but not running — start Docker Desktop now?', + message: `Docker is installed but not running — start ${app.name} now?`, initialValue: true, }) if (!launch) { if (required) { throw new SetupError('Docker is required for this mode.', [ - 'start Docker Desktop, then re-run the wizard', + `start ${app.name}, then re-run the wizard`, ]) } return false } - spawnSync('open', ['-a', 'Docker'], { stdio: 'ignore' }) + spawnSync('open', ['-a', app.name], { stdio: 'ignore' }) const spin = p.spinner() - spin.start('Waiting for the Docker daemon…') + spin.start(`Waiting for the Docker daemon (${app.name})…`) const up = await waitFor(async () => daemonUp(), 90_000, 2000) spin.stop(up ? 'Docker is running' : `${glyph.fail} daemon did not come up`) if (!up) { - throw new SetupError('Docker Desktop did not start within 90s.', [ - 'first-ever launch needs a GUI license acceptance — open Docker Desktop manually once, then re-run', + throw new SetupError(`${app.name} did not start within 90s.`, [ + app === ORBSTACK_APP + ? 'open OrbStack manually once to finish its first-run setup, then re-run' + : 'first-ever launch needs a GUI license acceptance — open Docker Desktop manually once, then re-run', ]) } return true From 02180dfb5ea5d3477e1ac9b15e98f56762938583 Mon Sep 17 00:00:00 2001 From: Bohdan Vilishchuk Date: Tue, 4 Aug 2026 18:12:05 +0300 Subject: [PATCH 2/8] fix(setup): don't let an installed OrbStack override an explicit Docker Desktop context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit macDockerApp() fell through to the OrbStack.app existence check whenever docker context show returned anything other than "orbstack" — including a known, explicit context like "desktop-linux". With both apps installed but Docker Desktop active and stopped, this launched OrbStack while daemonUp() kept polling Docker Desktop's socket, timing out with OrbStack-flavored guidance for a Docker Desktop problem. The path fallback now only runs when the context command gives no answer at all (null); any resolved context is trusted outright. Flagged identically by Greptile and Cursor Bugbot on PR #6250. --- scripts/setup/docker.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/setup/docker.ts b/scripts/setup/docker.ts index 77a546c88b2..daf104150e3 100644 --- a/scripts/setup/docker.ts +++ b/scripts/setup/docker.ts @@ -33,13 +33,15 @@ function currentDockerContext(): string | null { * Which GUI app owns the `docker` CLI on this Mac. Docker Desktop and OrbStack * both install a `docker` binary, so presence of the CLI alone doesn't tell us * which app to relaunch. Prefer the docker CLI's own active context — it's - * accurate regardless of where the app bundle lives — and fall back to - * checking the well-known `.app` install paths when the context doesn't say. + * accurate regardless of where the app bundle lives, and authoritative when + * both apps are installed but only one is the active context. Only fall back + * to checking the well-known `.app` install path when the context command + * gives no answer at all. */ function macDockerApp(): typeof ORBSTACK_APP | typeof DOCKER_DESKTOP_APP { - if (currentDockerContext() === 'orbstack') return ORBSTACK_APP - if (existsSync(ORBSTACK_APP.path)) return ORBSTACK_APP - return DOCKER_DESKTOP_APP + const context = currentDockerContext() + if (context !== null) return context === 'orbstack' ? ORBSTACK_APP : DOCKER_DESKTOP_APP + return existsSync(ORBSTACK_APP.path) ? ORBSTACK_APP : DOCKER_DESKTOP_APP } /** From 847944f958e73d408ea7fb49f492f589867ff7aa Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 4 Aug 2026 10:42:13 -0700 Subject: [PATCH 3/8] fix(setup): fall back to the installed app when the context isn't OrbStack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context detection only fell back to the app bundle when `docker context show` failed outright, so an OrbStack-only Mac sitting on the `default` context still resolved to Docker Desktop — the same 90s hang this fix exists to remove. Treat an explicit OrbStack selection as the only positive context signal and otherwise pick whichever app is installed. Read `DOCKER_HOST` first: it overrides the active context, so the context name is not authoritative while it is set. --- scripts/setup/docker.ts | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/scripts/setup/docker.ts b/scripts/setup/docker.ts index daf104150e3..6140073acdc 100644 --- a/scripts/setup/docker.ts +++ b/scripts/setup/docker.ts @@ -24,29 +24,34 @@ function installed(): boolean { return Bun.which('docker') !== null } -function currentDockerContext(): string | null { +/** + * Whether the docker CLI is currently pointed at OrbStack. `DOCKER_HOST` wins + * over the active context when set, so it is the only signal worth reading in + * that case; otherwise the active context is authoritative, since OrbStack + * registers and selects a context named `orbstack`. + */ +function orbstackSelected(): boolean { + const host = process.env.DOCKER_HOST + if (host) return host.includes('.orbstack/') const result = spawnSync('docker', ['context', 'show'], { encoding: 'utf8' }) - return result.status === 0 ? result.stdout.trim() : null + return result.status === 0 && result.stdout.trim() === 'orbstack' } /** - * Which GUI app owns the `docker` CLI on this Mac. Docker Desktop and OrbStack - * both install a `docker` binary, so presence of the CLI alone doesn't tell us - * which app to relaunch. Prefer the docker CLI's own active context — it's - * accurate regardless of where the app bundle lives, and authoritative when - * both apps are installed but only one is the active context. Only fall back - * to checking the well-known `.app` install path when the context command - * gives no answer at all. + * Which GUI app owns the `docker` CLI on this Mac. Both apps install a `docker` + * binary, so CLI presence alone doesn't say which one to launch. An explicit + * OrbStack selection wins; otherwise prefer whichever app is actually + * installed, which also covers CLIs too old for `docker context show`. */ function macDockerApp(): typeof ORBSTACK_APP | typeof DOCKER_DESKTOP_APP { - const context = currentDockerContext() - if (context !== null) return context === 'orbstack' ? ORBSTACK_APP : DOCKER_DESKTOP_APP + if (orbstackSelected()) return ORBSTACK_APP + if (existsSync(DOCKER_DESKTOP_APP.path)) return DOCKER_DESKTOP_APP return existsSync(ORBSTACK_APP.path) ? ORBSTACK_APP : DOCKER_DESKTOP_APP } /** - * Returns whether the Docker daemon is available, offering to launch Docker - * Desktop (macOS) when it's installed but stopped. Never installs anything. + * Returns whether the Docker daemon is available, offering to launch the + * installed docker app (macOS) when it's stopped. Never installs anything. * With required=true, unavailability is a SetupError instead of false. */ export async function ensureDocker(required: boolean): Promise { From d08ddbf957ffd275a8fa161a90cc2e20117a082d Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 4 Aug 2026 10:46:51 -0700 Subject: [PATCH 4/8] fix(setup): require OrbStack to be installed before selecting it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A context or DOCKER_HOST left behind by an OrbStack uninstall selected an app that can never launch, turning a working Docker Desktop start into a guaranteed 90s timeout. Gate the OrbStack signal on the bundle being present and fall through to whichever app is. Look in ~/Applications as well as /Applications while here — Homebrew casks honour --appdir, so a user-local install is not unusual and a hardcoded /Applications check would misread it as "not installed". --- scripts/setup/docker.ts | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/scripts/setup/docker.ts b/scripts/setup/docker.ts index 6140073acdc..c79bbc8c884 100644 --- a/scripts/setup/docker.ts +++ b/scripts/setup/docker.ts @@ -1,5 +1,7 @@ import { spawnSync } from 'node:child_process' import { existsSync } from 'node:fs' +import { homedir } from 'node:os' +import { join } from 'node:path' import { SetupError } from './errors.ts' import { waitFor } from './probes.ts' import * as p from './prompter.ts' @@ -11,8 +13,13 @@ const INSTALL_HINTS = [ ] /** macOS GUI docker providers we know how to launch via `open -a`. */ -const ORBSTACK_APP = { name: 'OrbStack', path: '/Applications/OrbStack.app' } as const -const DOCKER_DESKTOP_APP = { name: 'Docker', path: '/Applications/Docker.app' } as const +const ORBSTACK_APP = { name: 'OrbStack', bundle: 'OrbStack.app' } as const +const DOCKER_DESKTOP_APP = { name: 'Docker', bundle: 'Docker.app' } as const + +type DockerApp = typeof ORBSTACK_APP | typeof DOCKER_DESKTOP_APP + +/** Homebrew casks honour `--appdir`, so a user-local install is not unusual. */ +const APP_DIRS = ['/Applications', join(homedir(), 'Applications')] function daemonUp(): boolean { return spawnSync('docker', ['info'], { stdio: 'ignore' }).status === 0 @@ -37,16 +44,22 @@ function orbstackSelected(): boolean { return result.status === 0 && result.stdout.trim() === 'orbstack' } +function appInstalled(app: DockerApp): boolean { + return APP_DIRS.some((dir) => existsSync(join(dir, app.bundle))) +} + /** * Which GUI app owns the `docker` CLI on this Mac. Both apps install a `docker` * binary, so CLI presence alone doesn't say which one to launch. An explicit - * OrbStack selection wins; otherwise prefer whichever app is actually - * installed, which also covers CLIs too old for `docker context show`. + * OrbStack selection wins, but only when OrbStack is still installed — a + * context or `DOCKER_HOST` left behind by an uninstall would otherwise pick an + * app that can never come up. Otherwise fall back to whichever app is present, + * which also covers CLIs too old for `docker context show`. */ -function macDockerApp(): typeof ORBSTACK_APP | typeof DOCKER_DESKTOP_APP { - if (orbstackSelected()) return ORBSTACK_APP - if (existsSync(DOCKER_DESKTOP_APP.path)) return DOCKER_DESKTOP_APP - return existsSync(ORBSTACK_APP.path) ? ORBSTACK_APP : DOCKER_DESKTOP_APP +function macDockerApp(): DockerApp { + if (orbstackSelected() && appInstalled(ORBSTACK_APP)) return ORBSTACK_APP + if (appInstalled(DOCKER_DESKTOP_APP)) return DOCKER_DESKTOP_APP + return appInstalled(ORBSTACK_APP) ? ORBSTACK_APP : DOCKER_DESKTOP_APP } /** From 2010e3fc9379e402c79304ca558ca5ca536f5df3 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 4 Aug 2026 10:53:02 -0700 Subject: [PATCH 5/8] fix(setup): resolve the docker app through LaunchServices, not fixed paths A Homebrew `--appdir` can put OrbStack anywhere, so enumerating install directories will always have a tail that reads a present app as missing and sends setup to the wrong one. Fall back to LaunchServices when the well-known directories miss: that is the same lookup `open -a` performs, so availability now agrees with what the launch will actually do. --- scripts/setup/docker.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/setup/docker.ts b/scripts/setup/docker.ts index c79bbc8c884..428feb6ba81 100644 --- a/scripts/setup/docker.ts +++ b/scripts/setup/docker.ts @@ -44,8 +44,18 @@ function orbstackSelected(): boolean { return result.status === 0 && result.stdout.trim() === 'orbstack' } +/** + * Whether macOS can launch this app. The well-known directories cover every + * normal install without spawning anything; LaunchServices is the authority + * for the rest, since a Homebrew `--appdir` can put the bundle anywhere and + * `open -a` would still find it there. + */ function appInstalled(app: DockerApp): boolean { - return APP_DIRS.some((dir) => existsSync(join(dir, app.bundle))) + if (APP_DIRS.some((dir) => existsSync(join(dir, app.bundle)))) return true + const lookup = spawnSync('osascript', ['-e', `path to application "${app.name}"`], { + stdio: 'ignore', + }) + return lookup.status === 0 } /** From ce25e1d91cd5111102971af23abf1cfac74f5e10 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 4 Aug 2026 11:03:54 -0700 Subject: [PATCH 6/8] fix(setup): settle the docker app with open(1) instead of probing for it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `path to application` can raise a modal "Where is …?" picker when the name does not resolve, which in a terminal wizard reads as a hang. Drop it: the launch itself already answers the question, since `open` exits non-zero when macOS knows no such app, instantly and without UI. That inverts the design. Rather than predict which app is installed and then launch it, pick a provider, try to start it, and let the exit code correct a guess — so the directory probe no longer has to enumerate every possible install location to be right. An explicit OrbStack selection is now never redirected to Docker Desktop. The CLI is addressing OrbStack's socket, so `docker info` keeps failing no matter how well Docker Desktop starts; the earlier fallback only replaced a 90s timeout with a differently worded one. Say the context is stale and how to fix it instead. --- scripts/setup/docker.ts | 77 +++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/scripts/setup/docker.ts b/scripts/setup/docker.ts index 428feb6ba81..47ed1831f3e 100644 --- a/scripts/setup/docker.ts +++ b/scripts/setup/docker.ts @@ -25,9 +25,8 @@ function daemonUp(): boolean { return spawnSync('docker', ['info'], { stdio: 'ignore' }).status === 0 } +/** Uses `Bun.which` rather than `which`, which is not a standard Windows command. */ function installed(): boolean { - // Bun.which resolves PATH cross-platform (incl. PATHEXT on Windows); `which` - // is not a standard Windows command. return Bun.which('docker') !== null } @@ -44,32 +43,48 @@ function orbstackSelected(): boolean { return result.status === 0 && result.stdout.trim() === 'orbstack' } +function appInstalled(app: DockerApp): boolean { + return APP_DIRS.some((dir) => existsSync(join(dir, app.bundle))) +} + +interface DockerChoice { + app: DockerApp + /** The CLI names this provider, so no other app can bring its daemon up. */ + explicit: boolean +} + /** - * Whether macOS can launch this app. The well-known directories cover every - * normal install without spawning anything; LaunchServices is the authority - * for the rest, since a Homebrew `--appdir` can put the bundle anywhere and - * `open -a` would still find it there. + * Which app to offer to start. Both providers install a `docker` binary, so CLI + * presence alone doesn't say which one to launch. An OrbStack selection is + * explicit; anything else is a guess the launch is allowed to correct, which is + * why the install probe here doesn't have to be exhaustive. */ -function appInstalled(app: DockerApp): boolean { - if (APP_DIRS.some((dir) => existsSync(join(dir, app.bundle)))) return true - const lookup = spawnSync('osascript', ['-e', `path to application "${app.name}"`], { - stdio: 'ignore', - }) - return lookup.status === 0 +function macDockerApp(): DockerChoice { + if (orbstackSelected()) return { app: ORBSTACK_APP, explicit: true } + const orbstackOnly = appInstalled(ORBSTACK_APP) && !appInstalled(DOCKER_DESKTOP_APP) + return { app: orbstackOnly ? ORBSTACK_APP : DOCKER_DESKTOP_APP, explicit: false } } /** - * Which GUI app owns the `docker` CLI on this Mac. Both apps install a `docker` - * binary, so CLI presence alone doesn't say which one to launch. An explicit - * OrbStack selection wins, but only when OrbStack is still installed — a - * context or `DOCKER_HOST` left behind by an uninstall would otherwise pick an - * app that can never come up. Otherwise fall back to whichever app is present, - * which also covers CLIs too old for `docker context show`. + * Starts a provider. `open` exits non-zero when macOS knows no such app, which + * settles installation authoritatively and without a dialog — it resolves the + * name the same way the launch does, so the two cannot disagree. */ -function macDockerApp(): DockerApp { - if (orbstackSelected() && appInstalled(ORBSTACK_APP)) return ORBSTACK_APP - if (appInstalled(DOCKER_DESKTOP_APP)) return DOCKER_DESKTOP_APP - return appInstalled(ORBSTACK_APP) ? ORBSTACK_APP : DOCKER_DESKTOP_APP +function openApp(app: DockerApp): boolean { + return spawnSync('open', ['-a', app.name], { stdio: 'ignore' }).status === 0 +} + +/** + * Starts the chosen provider, retrying with the other one when the choice was + * only a guess. An explicit OrbStack selection is never redirected: `docker + * info` would still be addressing OrbStack's socket, so Docker Desktop cannot + * satisfy it however successfully it starts. + */ +function startDockerApp({ app, explicit }: DockerChoice): DockerApp | null { + if (openApp(app)) return app + if (explicit) return null + const other = app === ORBSTACK_APP ? DOCKER_DESKTOP_APP : ORBSTACK_APP + return openApp(other) ? other : null } /** @@ -94,22 +109,32 @@ export async function ensureDocker(required: boolean): Promise { return false } - const app = macDockerApp() + const choice = macDockerApp() const launch = await p.confirm({ - message: `Docker is installed but not running — start ${app.name} now?`, + message: `Docker is installed but not running — start ${choice.app.name} now?`, initialValue: true, }) if (!launch) { if (required) { throw new SetupError('Docker is required for this mode.', [ - `start ${app.name}, then re-run the wizard`, + `start ${choice.app.name}, then re-run the wizard`, ]) } return false } - spawnSync('open', ['-a', app.name], { stdio: 'ignore' }) + const app = startDockerApp(choice) + if (!app) { + if (choice.explicit) { + throw new SetupError('The docker CLI is pointed at OrbStack, which is not installed.', [ + `reinstall it: ${theme.command('brew install orbstack')}`, + `or point the CLI elsewhere: unset DOCKER_HOST / ${theme.command('docker context use ')}`, + ]) + } + throw new SetupError('No docker app is installed.', INSTALL_HINTS) + } + const spin = p.spinner() spin.start(`Waiting for the Docker daemon (${app.name})…`) const up = await waitFor(async () => daemonUp(), 90_000, 2000) From b07b73172c96df43515cef7eb06707e9232bc0dc Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 4 Aug 2026 11:12:59 -0700 Subject: [PATCH 7/8] fix(setup): honour `required` when the docker app fails to launch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit db.ts and redis.ts call ensureDocker(false) and branch on the boolean to offer an external Postgres or Redis instead. Throwing past that aborts the whole wizard when a working non-Docker path was on the table, so every post-confirm failure now warns and returns false unless Docker is required. That covers the 90s-timeout throw too, which ignored `required` before this branch existed — leaving it as the one path that still aborts would make the flag mean two different things in one function. Also name DOCKER_CONTEXT in the stale-selection hint. It overrides the config context, so `docker context use` alone leaves the CLI pointed at OrbStack and the next run fails identically. --- scripts/setup/docker.ts | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/scripts/setup/docker.ts b/scripts/setup/docker.ts index 47ed1831f3e..a243d413076 100644 --- a/scripts/setup/docker.ts +++ b/scripts/setup/docker.ts @@ -87,6 +87,17 @@ function startDockerApp({ app, explicit }: DockerChoice): DockerApp | null { return openApp(other) ? other : null } +/** + * A launch that failed after the user opted into it. Callers passing + * required=false have a non-Docker path to offer, so the reason is worth + * surfacing but must not abort the wizard. + */ +function launchFailed(required: boolean, message: string, hints: string[]): boolean { + if (required) throw new SetupError(message, hints) + p.log.warn([message, ...hints].join('\n')) + return false +} + /** * Returns whether the Docker daemon is available, offering to launch the * installed docker app (macOS) when it's stopped. Never installs anything. @@ -126,13 +137,12 @@ export async function ensureDocker(required: boolean): Promise { const app = startDockerApp(choice) if (!app) { - if (choice.explicit) { - throw new SetupError('The docker CLI is pointed at OrbStack, which is not installed.', [ - `reinstall it: ${theme.command('brew install orbstack')}`, - `or point the CLI elsewhere: unset DOCKER_HOST / ${theme.command('docker context use ')}`, - ]) - } - throw new SetupError('No docker app is installed.', INSTALL_HINTS) + return choice.explicit + ? launchFailed(required, 'The docker CLI is pointed at OrbStack, which is not installed.', [ + `reinstall it: ${theme.command('brew install orbstack')}`, + `or point the CLI elsewhere: unset DOCKER_HOST and DOCKER_CONTEXT, then ${theme.command('docker context use ')}`, + ]) + : launchFailed(required, 'No docker app is installed.', INSTALL_HINTS) } const spin = p.spinner() @@ -140,7 +150,7 @@ export async function ensureDocker(required: boolean): Promise { const up = await waitFor(async () => daemonUp(), 90_000, 2000) spin.stop(up ? 'Docker is running' : `${glyph.fail} daemon did not come up`) if (!up) { - throw new SetupError(`${app.name} did not start within 90s.`, [ + return launchFailed(required, `${app.name} did not start within 90s.`, [ app === ORBSTACK_APP ? 'open OrbStack manually once to finish its first-run setup, then re-run' : 'first-ever launch needs a GUI license acceptance — open Docker Desktop manually once, then re-run', From a127fe7064a2544ae8f17e2e1651b87b29a03fe6 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 4 Aug 2026 11:16:20 -0700 Subject: [PATCH 8/8] improvement(setup): don't tell CLI-runtime users to install Docker Desktop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Having the docker CLI but neither GUI app is exactly what a colima or Rancher Desktop user looks like, and the failure told them to install Docker Desktop — advice for a problem they don't have. Name the situation accurately and add starting an existing runtime as an option. --- scripts/setup/docker.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/setup/docker.ts b/scripts/setup/docker.ts index a243d413076..f116ff8b102 100644 --- a/scripts/setup/docker.ts +++ b/scripts/setup/docker.ts @@ -12,6 +12,16 @@ const INSTALL_HINTS = [ `or OrbStack (lighter on macOS): ${theme.command('brew install orbstack')}`, ] +/** + * Reaching this means the docker CLI exists but neither GUI app does, which is + * also what a colima or Rancher Desktop user looks like — telling them to + * install Docker Desktop would be advice for a problem they don't have. + */ +const NO_APP_HINTS = [ + ...INSTALL_HINTS, + `or start your existing runtime its own way, e.g. ${theme.command('colima start')}`, +] + /** macOS GUI docker providers we know how to launch via `open -a`. */ const ORBSTACK_APP = { name: 'OrbStack', bundle: 'OrbStack.app' } as const const DOCKER_DESKTOP_APP = { name: 'Docker', bundle: 'Docker.app' } as const @@ -142,7 +152,7 @@ export async function ensureDocker(required: boolean): Promise { `reinstall it: ${theme.command('brew install orbstack')}`, `or point the CLI elsewhere: unset DOCKER_HOST and DOCKER_CONTEXT, then ${theme.command('docker context use ')}`, ]) - : launchFailed(required, 'No docker app is installed.', INSTALL_HINTS) + : launchFailed(required, 'Found the docker CLI, but no app to start.', NO_APP_HINTS) } const spin = p.spinner()