Skip to content

[Bug]: container system start discards launchctl bootstrap failures, yielding a misleading XPC error in non-login (CI) sessions #2008

Description

@cap10morgan

I have done the following

  • I have searched the existing issues
  • If possible, I've reproduced the issue using the 'main' branch of this project

Steps to reproduce

On a session whose launchd context is not a logged-in user session — a GitHub Actions macos-26 runner reproduces this deterministically:

sudo installer -pkg container-installer-unsigned.pkg -target /
sudo container system start --enable-kernel-install

Output (instant, no retry helps — 15 consecutive attempts fail identically):

Launching container-apiserver...
Testing access to container-apiserver...
Error: internalError: "failed to get a response from apiserver: interrupted: "XPC connection error: Connection invalid""
Ensure container system service has been started with `container system start`.

log show --predicate 'subsystem == "com.apple.container"' over the failure window is completely emptycontainer-apiserver never launched at all.

Problem description

Two layered problems:

1. The launchctl bootstrap failure is silently discarded. ServiceManager.register (Sources/ContainerPlugin/ServiceManager.swift) pipes launchctl's stdout/stderr to /dev/null and ignores its exit status:

public static func register(plistPath: String) throws {
    let domain = try Self.getDomainString()
    _ = try runLaunchctlCommand(args: ["bootstrap", domain, plistPath])
}

When the bootstrap fails, SystemStart proceeds to "Testing access to container-apiserver..." and dies with a generic XPC error that points the user back at the very command that just failed ("Ensure container system service has been started with container system start"). The actual launchctl error — which names the domain and the real reason — is never shown. This cost us several debugging round-trips chasing the XPC layer before reading the source revealed the ping was doomed before it ran.

2. The computed domain is wrong for sudo-in-a-background-session. getDomainString() maps launchctl managername + getuid(): Aqua → gui/<uid>, Background → user/<uid>. Under sudo in a CI runner's session, that computes user/0/gui/0, which can't be bootstrapped into — so registration always fails there. This is the same class of problem as #1514, but note the service plist already includes System in LimitLoadToSessionType, so the system domain looks intended to be supported.

Suggested behavior:

  • At minimum: register() should check launchctl's exit status and surface its stderr in the thrown error. That one change would have made the root cause obvious from the first failure.
  • Ideally: when running as root (euid 0) outside an Aqua session, bootstrap into the system domain. We verified this works end to end as a workaround — after container system start writes the plist, running sudo launchctl bootstrap system <plist> and re-running container system start brings everything up (apiserver, machine-apiserver, plugins, kernel install), and Mach lookups chain gui → user → system so the service is reachable from any session. Working example: https://github.com/cap10morgan/whalebridge/blob/7de7456/.github/workflows/ci.yml (the "Start apple/container services" step; also being adopted by ci: replace deprecated tmate with direct launchd system-domain bootstrap socktainer/socktainer#325).

Environment

  • OS: macOS 26 (GitHub Actions macos-26 runner image, Apple silicon)
  • Xcode: Xcode 26 (runner default)
  • Container: container CLI version 1.1.0 (release pkg)

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions