Skip to content

fix(local): install OrbStack from DMG when Homebrew is missing#3372

Merged
la14-1 merged 1 commit intoOpenRouterTeam:mainfrom
AhmedTMM:orbstack-no-brew
Apr 29, 2026
Merged

fix(local): install OrbStack from DMG when Homebrew is missing#3372
la14-1 merged 1 commit intoOpenRouterTeam:mainfrom
AhmedTMM:orbstack-no-brew

Conversation

@AhmedTMM
Copy link
Copy Markdown
Collaborator

Summary

ensureDocker() on macOS unconditionally called brew install orbstack. When Homebrew isn't installed, the call fails and the fallback message printed "install OrbStack manually: brew install orbstack" — circular dead-end.

This PR:

  • Probes which brew. If brew is present, behavior is unchanged (existing happy path).
  • If brew is missing, downloads the official OrbStack DMG from orbstack.dev (arch-aware: arm64 vs amd64) over HTTPS, mounts via hdiutil, copies OrbStack.app into /Applications, and clears the quarantine xattr so first launch is clean.
  • If both paths fail, the new error points at https://orbstack.dev/download instead of back at brew.

Safety

  • Download URL: https://orbstack.dev/download/stable/latest/{arch} — same artifact OrbStack publishes for manual install. HTTPS to a known apex.
  • DMG size sanity check (>1MB) catches HTML error pages or truncated downloads.
  • Cleanup is sequential (tryCatch + unconditional unmount/rm), per the repo's lint/plugin rule banning try/finally.

Test plan

  • bunx @biomejs/biome check src/ — clean
  • bun test src/__tests__/sandbox.test.ts — 34/34 pass (existing brew-present test updated for the new which brew probe; new falls back to DMG download on macOS when brew is missing test added)
  • Full bun test — only the same 4 pre-existing cross-file fetch-mock pollution flakes (cmdrun-happy-path, hetzner-cov, digitalocean-token), unrelated.

🤖 Generated with Claude Code

ensureDocker() on macOS unconditionally shelled out to `brew install
orbstack`, then on failure printed "install OrbStack manually: brew
install orbstack" — circular dead-end for Macs without Homebrew.

Now:
- Probe `which brew`. If present, keep using brew (existing happy path).
- If brew is missing, download the official OrbStack DMG over HTTPS
  from orbstack.dev (arch-aware: arm64 vs amd64), mount it via
  `hdiutil`, copy OrbStack.app into /Applications, and clear the
  quarantine xattr so it launches cleanly.
- If both paths fail, the new error message points users at the
  OrbStack download page (not back at brew).

DMG handling uses tryCatch + sequential cleanup (no try/finally, per
the `lint/plugin` rule in this repo). Adds a test for the brew-missing
fallback and updates the existing brew-present test to account for the
new `which brew` probe.

Bumps version to 1.0.24.
@AhmedTMM AhmedTMM marked this pull request as ready for review April 29, 2026 22:35
@la14-1
Copy link
Copy Markdown
Member

la14-1 commented Apr 29, 2026

Review

Real user-facing fix. The previous "auto-install failed, try `brew install orbstack`" message was a circular dead-end on any Mac without Homebrew — which is most new machines out of the box. Swapping in the official DMG install path is the right move.

Looks good

  • Probe before invoke. `hasBrew()` runs before we call `brew install`. Clean branching — brew-present path is unchanged, brew-missing path takes the DMG route.
  • Arch-aware download. `uname -m` → `arm64` vs `amd64` matches OrbStack's labeling. URL `https://orbstack.dev/download/stable/latest/{arch}\` is the official artifact.
  • Size sanity check (>1MB). Catches HTML error pages, truncated downloads, or a captive-portal-injected response without needing to validate signatures. Reasonable heuristic for a DMG.
  • Cleanup is unconditional. `attached` flag tracks whether `hdiutil attach` succeeded, so detach only runs when there's something to detach. `rmSync(tempDir, {recursive, force})` afterwards regardless. Follows the repo's "no try/finally" rule by using the boolean + sequential cleanup pattern.
  • Quarantine xattr cleared. `xattr -dr com.apple.quarantine` after copy — otherwise first launch pops the Gatekeeper "downloaded from the internet" dialog. Good attention to UX.
  • Error messaging improved. Old message was circular ("try brew install orbstack" after brew failed); new message points at https://orbstack.dev/download with a secondary "if you have Homebrew..." note. Accurate and actionable.
  • New test covers the DMG fallback path. `sawCurl/sawHdiutilAttach/sawCp/sawHdiutilDetach` flag checks verify each step ran. Existing brew-present test updated to account for the new `which brew` probe.

Minor

  • `xattr` return code is ignored. If it fails (e.g. `xattr` not on PATH, obscure but possible), the app still runs but Gatekeeper may nag. Silent-ignore is fine here, but worth a comment noting it's intentional — currently reads like a bug.
  • Download has no timeout. `curl -fsSL` without `--connect-timeout`/`--max-time` can hang indefinitely on a flaky network. Compare to `agent-tarball.ts:128` which uses `--connect-timeout 10 --max-time 120`. Worth adding for consistency, especially since a slow Mac home network is a very common case.
  • `/Applications` write permission. Standard user accounts can write to `/Applications` on macOS (it's not SIP-protected), but managed/enterprise Macs occasionally lock it. No sudo escalation here — which is good, but means on those Macs the `cp` will fail cleanly and fall through to the "install manually" message. That's the right behavior; just worth knowing.
  • Permissions drift. A pre-existing `OrbStack.app` in `/Applications` will be overwritten by `cp -R`. Current behavior is fine (upgrade-in-place); just flagging that there's no version check — if a user manually installed a newer build, this will downgrade them. Low probability.

Nit

  • `installOrbStackViaDmg` is 100+ lines. Could extract `downloadDmg()`, `mountAndCopy()`, `cleanup()` as separate tiny functions for readability. Not blocking — the inline version is still readable and the single-function form makes the `attached` flag + cleanup pattern obvious.

Merge notes

  • `mergeStateStatus: BLOCKED` — needs approving review. No rebase needed; already on latest main (1.0.26 → 1.0.27 bump is correct).
  • CI 5/5 green.

LGTM with an optional curl-timeout follow-up.

Reviewed by SPA

Copy link
Copy Markdown
Member

@la14-1 la14-1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified both download URLs resolve to real DMGs (arm64: 446 MB, amd64: 483 MB, Content-Type application/x-apple-diskimage, last updated 2026-04-20). curl -fsSL handles the 307 redirect via -L. Full review at the earlier comment.

@la14-1 la14-1 merged commit 1ac0171 into OpenRouterTeam:main Apr 29, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants