Refactor macOS CLI installation to use ~/.local/bin#2937
Open
ivan-ottinger wants to merge 9 commits intotrunkfrom
Open
Refactor macOS CLI installation to use ~/.local/bin#2937ivan-ottinger wants to merge 9 commits intotrunkfrom
ivan-ottinger wants to merge 9 commits intotrunkfrom
Conversation
Replace the sudo-based /usr/local/bin symlink approach with a user-writable ~/.local/bin directory. Auto-install CLI on app startup and manage shell profile PATH if needed.
Add cliAutoInstalled flag to app config so auto-install only runs on first launch. If the user disables the CLI toggle, it won't be re-enabled on the next app start.
Use path.join() for test constants and assertions so paths use the correct separator on Windows CI (backslashes) vs macOS (forward slashes).
Use describe.skipIf for the MacOSCliInstallationManager tests since they use macOS path conventions. Matches the existing codebase pattern of using hardcoded forward-slash paths in test mocks.
The old /usr/local/bin/studio symlink almost always requires sudo to remove, making cleanup a no-op for most users. The old symlink is harmless since ~/.local/bin takes priority in PATH.
Prevents dev mode from creating a symlink pointing to dev resources and setting the cliAutoInstalled flag, which would interfere with production installs.
ivan-ottinger
commented
Mar 27, 2026
|
|
||
| const ERROR_FILE_ALREADY_EXISTS = 'Studio CLI symlink path already occupied by non-symlink'; | ||
| // Defined in @vscode/sudo-prompt | ||
| const ERROR_PERMISSION = 'User did not grant permission.'; |
Contributor
Author
There was a problem hiding this comment.
Since we don't run sudo, the error message is no longer needed.
Contributor
There was a problem hiding this comment.
Pull request overview
Refactors the macOS Studio CLI installation flow to avoid sudo by installing the studio symlink into ~/.local/bin, adds first-run auto-install behavior, and updates user config/types to track whether auto-install has occurred.
Changes:
- Switch macOS CLI symlink target from
/usr/local/bin/studioto~/.local/bin/studioand remove the legacy sudo-based shell scripts. - Add
autoInstallMacOSCliIfNeeded()on app boot (production + darwin only) and persistcliAutoInstalledin app config. - Add PATH management by appending
export PATH="$HOME/.local/bin:$PATH"to a detected shell profile when needed, plus new unit tests.
Reviewed changes
Copilot reviewed 5 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/studio/src/storage/user-data.ts | Allows cliAutoInstalled to be persisted via updateAppdata() safe keys. |
| apps/studio/src/storage/storage-types.ts | Adds cliAutoInstalled?: boolean to the UserData type. |
| apps/studio/src/modules/cli/lib/macos-installation-manager.ts | Implements ~/.local/bin symlink install, profile PATH updates, and auto-install entrypoint. |
| apps/studio/src/index.ts | Calls autoInstallMacOSCliIfNeeded() during app boot. |
| apps/studio/src/modules/cli/lib/tests/macos-installation-manager.test.ts | Adds unit tests for the refactored macOS installation manager and auto-install behavior. |
| apps/studio/bin/install-studio-cli.sh | Removed legacy sudo install script. |
| apps/studio/bin/uninstall-studio-cli.sh | Removed legacy sudo uninstall script. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
apps/studio/src/modules/cli/lib/tests/macos-installation-manager.test.ts
Outdated
Show resolved
Hide resolved
Return early in uninstallCli() when the symlink doesn't exist instead of proceeding to unlink (which would throw ENOENT). Also narrow the test skip from all non-darwin platforms to Windows only, since the POSIX paths work fine on Linux.
Collaborator
📊 Performance Test ResultsComparing bf34da1 vs trunk app-size
site-editor
site-startup
Results are median values from multiple test runs. Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related issues
How AI was used in this PR
Claude Code was used to implement the changes, write tests, and create this PR. The solution was developed iteratively through discussion — we explored the existing codebase patterns (macOS and Windows installation managers), identified edge cases (e.g., respecting user preference when CLI is explicitly disabled), and refined the approach based on the Linear issue description and comments.
Proposed Changes
/usr/local/bin/studiosymlink (requires sudo) with~/.local/bin/studio(user-writable, no sudo needed)autoInstallMacOSCliIfNeeded(), with acliAutoInstalledflag in app config to avoid re-enabling if the user explicitly disabled itexport PATH="$HOME/.local/bin:$PATH"to the user's shell profile (.zshrcby default) if~/.local/binisn't already in PATH.~/.local/binis prepended so the bundled CLI takes priority over any standalone npm-installed versioninstall-studio-cli.sh,uninstall-studio-cli.sh) that are no longer needednpm start) to avoid creating symlinks pointing to dev resourcesTesting Instructions
Setup
rm -f ~/.local/bin/studio~/.studio/app.jsonand delete the"cliAutoInstalled": trueentrynpm run make, then install the resulting DMG to/Applications/Auto-install on first launch
ls -la ~/.local/bin/studio— should point to/Applications/Studio.app/Contents/Resources/bin/studio-cli.shgrep cliAutoInstalled ~/.studio/app.json— should show"cliAutoInstalled": truestudio --version— should print the current versiontail -3 ~/.zshrc— should containexport PATH="$HOME/.local/bin:$PATH"(if it wasn't already there)Shell profile PATH management
$HOME/.local/binfrom your shell profile (e.g., edit~/.zshrcand delete theexport PATH="$HOME/.local/bin:$PATH"line), then remove the CLI symlink and reset the flag:rm -f ~/.local/bin/studio~/.studio/app.jsonand delete the"cliAutoInstalled": trueentrygrep '$HOME/.local/bin' ~/.zshrc— should showexport PATH="$HOME/.local/bin:$PATH"grep -c '$HOME/.local/bin' ~/.zshrc— should show1User preference is respected
ls ~/.local/bin/studio— should not existls ~/.local/bin/studio— should still not existgrep cliAutoInstalled ~/.studio/app.json— should still showtrueRe-enabling works without sudo
ls -la ~/.local/bin/studioNo-op on subsequent launches
Pre-merge Checklist