From 99bae52ccb4407f7a8523673e49c3167f31a9a8e Mon Sep 17 00:00:00 2001 From: Bohdan Vilishchuk Date: Tue, 4 Aug 2026 17:43:44 +0300 Subject: [PATCH 1/2] 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 bf93e19ab7801e037945a4e044974f5e487a03d3 Mon Sep 17 00:00:00 2001 From: Bohdan Vilishchuk Date: Tue, 4 Aug 2026 18:12:05 +0300 Subject: [PATCH 2/2] 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 } /**