Skip to content

Start a login shell in the home directory when launched from the desktop - #123

Merged
pg83 merged 1 commit into
pg83:masterfrom
lexasoft123:macos-desktop-launch-login-and-home
Sep 28, 2026
Merged

pg83 merged 1 commit into
pg83:masterfrom
lexasoft123:macos-desktop-launch-login-and-home

Conversation

@lexasoft123

Copy link
Copy Markdown
Contributor

The problem

Launching Shitty.app (or Pretty.app) from Finder, the Dock or open gives a shell that can't find Homebrew commands, and a prompt that opens at /:

➜  / htop
zsh: command not found: htop

Both come from the launch context, not from the terminal:

  • launchd starts the app with a bare PATH=/usr/bin:/bin:/usr/sbin:/sbin and no shell environment to inherit, in /.
  • login defaults to false, so the shell runs non-login and never reads .zprofile, which is where Homebrew's eval "$(brew shellenv)" conventionally lives.

Running the raw binary from another terminal hid it, because the child then inherits that terminal's environment. Since #120 ships an app bundle, a desktop launch is now the normal path.

The fix

Only when launchd is the parent process (macOS only):

  • login defaults to true, unless the user set it, in the config file or on the command line. +login and login = false still win.
  • A working directory of / becomes $HOME.

Launches from a shell are untouched. The predicate takes the parent pid as a parameter (launchedFromDesktop(pid_t)), so it is unit-testable; the login default is read off the option's source (HardDefault means the user never chose).

I went with "only when launched from launchd" rather than making every macOS launch a login shell (as Terminal.app and iTerm2 do), because it changes nothing for people who start st from a shell today. Happy to switch if you'd rather follow the other convention.

Verified

Launched the built binary orphaned to launchd (ppid 1) with a small C probe as the shell, so it could report its true argv[0] and cwd:

launch login shell cwd
launchd-style, default yes $HOME (was /)
launchd-style, +login no, so the explicit setting wins $HOME
from a live shell, default no, unchanged untouched
from a live shell, -login yes untouched
  • New Startup unit test; the whole unit suite passes through ./build's own runner.
  • Not verified: a real double-click of the bundle. In my session open started the app but never spawned the shell, so the launchd case is covered by the orphaned-process test above rather than by an actual Finder launch.

🤖 Generated with Claude Code

A .app started from Finder, the Dock or open(1) is launched by launchd,
which hands it a bare PATH (/usr/bin:/bin:/usr/sbin:/sbin) and no
inherited shell environment, and starts it in "/". The shell inside then
runs non-login (login defaults to false), so it never reads .zprofile,
which is where Homebrew's `brew shellenv` normally lives: commands from
/opt/homebrew/bin are not found, and the prompt opens at "/".

Running the raw binary from another terminal hid this, since the child
inherits that terminal's environment. Shipping an app bundle makes the
desktop launch the normal path.

When launchd is the parent process (macOS only):

- login defaults to true, unless the user set it explicitly, in config
  or on the command line, so +login and login = false still win.
- a working directory of "/" becomes $HOME.

Launches from a shell are untouched. The parent-pid test is the same one
other macOS terminals use to tell a desktop launch from a shell launch;
it is taken as a parameter so the predicate is unit-testable.

Checked by launching the built binary orphaned to launchd (ppid 1) with
a probe as the shell: default gives a login argv[0] and cwd $HOME; +login
gives a plain argv[0]; the same launches from a live shell change nothing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@pg83
pg83 merged commit bdd6457 into pg83:master Sep 28, 2026
15 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