The tray app launches the background webserver through a login shell — bash -lc inside WSL on Windows, $SHELL -ilc on macOS — which means your .profile gets sourced every time it starts. That’s usually what you want, except when the profile does something blocking, like prompting to unlock an ssh key. At boot there’s nobody there to answer, so the profile hangs and takes the webserver with it.

Now the tray sets PROJECT_SWITCH_WEBSERVER=1 when launching, so the profile can detect who’s calling and skip the interactive parts. The subtlety is that it has to go on the process environment rather than into the -c string: the marker needs to already exist while the profile is being sourced, not after the command starts. On Windows there’s an extra hop — the variable has to be listed in WSLENV to cross into WSL at all, and I append to whatever WSLENV already holds so I’m not clobbering someone else’s shared vars.

I documented the whole webserver config block in the README while I was there. The guard is narrower than you’d first assume: you want to suppress the passphrase prompt, not the agent setup itself, or the webserver won’t be able to reach the key at all. With keychain that’s just --noask on the webserver branch, and since it keeps one persistent agent per host, unlocking the key in any interactive shell unlocks it for the running webserver too.