From 5614a56ccd3939087494918f2cbd5c07c2c5f66b Mon Sep 17 00:00:00 2001 From: didericis Date: Mon, 27 Jul 2026 09:56:51 -0400 Subject: [PATCH] fix(harness): run as the user inside their launchd domain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `test` passes. `test-ready` got as far as the service start and failed: Launching container-apiserver... Error: failed to get a response from apiserver: invalidState: "unauthorized request" which is the limitation this script's own header predicted — "may need a full launchd user session (`launchctl asuser`)". `container system start` registers com.apple.container.apiserver as a per-USER launchd agent and then talks to it over XPC. `launchctl list` confirms the shape: the apiserver and every container-network/runtime job are agents in the invoking user's domain. Under plain `sudo -u` the caller stays in root's bootstrap namespace, so the lookup crosses domains and the apiserver rejects it as unauthorized — the agent launched fine, the client just could not reach it. run_as_user now enters the target user's domain with `launchctl asuser `, which is also what a real user gets from Terminal, so it is the more faithful way to run everything here rather than a special case for the service start. A never-GUI-logged-in account may have no bootstrappable domain at all, so availability is probed once and cached, and everything falls back to plain `sudo -u` when it is missing. Without that fallback a missing domain would break `test` — which passes today and does not need a session — for a reason unrelated to what it tests. The rig covers that path explicitly. If the start still fails, the error now names the cause and the way out (log into the account once to get it a domain) instead of just relaying the CLI's message. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WEfZZhakx13bxTfXcZCoS5 --- scripts/macos-install-test.sh | 46 ++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/scripts/macos-install-test.sh b/scripts/macos-install-test.sh index ad3732b9..b66bfc58 100755 --- a/scripts/macos-install-test.sh +++ b/scripts/macos-install-test.sh @@ -106,6 +106,36 @@ require_root() { user_exists() { dscl . -read "/Users/$USER_NAME" >/dev/null 2>&1; } +user_uid() { + dscl . -read "/Users/$USER_NAME" UniqueID 2>/dev/null | awk '{print $2}' +} + +# Whether we can enter the target user's launchd domain. +# +# This matters more than it sounds. `container system start` registers +# com.apple.container.apiserver as a per-USER launchd agent and then talks to +# it over XPC. Under plain `sudo -u` the caller is still in root's bootstrap +# namespace, so the lookup crosses domains and the apiserver answers +# invalidState: "unauthorized request" +# even though it launched fine. `launchctl asuser ` puts the whole +# invocation in that user's domain, which is also what a real user gets from +# Terminal — so it is the more faithful way to run *everything* here, not just +# the service start. Falls back to plain sudo when unavailable, since a +# never-logged-in account may have no bootstrappable domain at all. +_SESSION_OK="" +user_session_available() { + if [ -z "$_SESSION_OK" ]; then + local uid + uid="$(user_uid)" + if [ -n "$uid" ] && launchctl asuser "$uid" true >/dev/null 2>&1; then + _SESSION_OK=1 + else + _SESSION_OK=0 + fi + fi + [ "$_SESSION_OK" = "1" ] +} + # Run a shell snippet as the throwaway user in a fresh login shell. # # The snippet goes in on stdin, never as an argument. `sudo -i` joins its argv @@ -113,7 +143,13 @@ user_exists() { dscl . -read "/Users/$USER_NAME" >/dev/null 2>&1; } # argument is not preserved and a newline ends the command outright ("sh: -c: # line 1: syntax error: unexpected end of file"). Feeding `sh -s` from stdin # sidesteps the joining entirely and lets snippets be multi-line. -run_as_user() { printf '%s\n' "$1" | sudo -u "$USER_NAME" -i sh -s; } +run_as_user() { + if user_session_available; then + printf '%s\n' "$1" | launchctl asuser "$(user_uid)" sudo -u "$USER_NAME" -i sh -s + else + printf '%s\n' "$1" | sudo -u "$USER_NAME" -i sh -s + fi +} # `bot-bottle doctor` as the throwaway user. Non-zero when no entry point was # installed at all, or when doctor itself is unhappy. @@ -333,8 +369,16 @@ cmd_prereqs() { return 1 } echo "starting the per-user Apple 'container' service for $USER_NAME" + user_session_available || { + echo "warning: no launchd session for $USER_NAME; the service start is" >&2 + echo " likely to fail with 'unauthorized request'. See user_session_available." >&2 + } run_as_user 'container system start' || { echo "error: 'container system start' failed for $USER_NAME" >&2 + echo " If this said invalidState/\"unauthorized request\", the CLI could not" >&2 + echo " reach its per-user apiserver over XPC. A never-GUI-logged-in account" >&2 + echo " may not have a usable launchd domain; logging into $USER_NAME once" >&2 + echo " (fast user switching) gives it one." >&2 return 1 } run_as_user 'container system status' || {