Skip to content

bug: launchd service cannot reach Tart VM bridge, leaving macOS runners stuck preparing #72

Description

@STRRL

Summary

The macOS Tart runner can get stuck cycling runners because waitForSSH reports SSH not reachable for every VM, even though the VM receives an IP and can be reached manually from the host in some attempts.

This currently blocks the Integration Test (Tart) job on PR #71:

Observed behavior

On eiu-the-runner, elastic-fruit-runner repeatedly prepares a Tart VM, gets 192.168.64.3, waits for SSH for two minutes, then deletes the VM and retries.

Representative log lines:

preparing runner runnerSet=fruit-runner-macos-arm64 runner=fruit-runner-macos-arm64-d9024
VM image not found locally, pulling image=ghcr.io/cirruslabs/macos-tahoe-xcode:latest
cloning VM image=ghcr.io/cirruslabs/macos-tahoe-xcode:latest name=fruit-runner-macos-arm64-d9024
starting VM name=fruit-runner-macos-arm64-d9024
waiting for VM IP name=fruit-runner-macos-arm64-d9024
waiting for SSH name=fruit-runner-macos-arm64-d9024 ip=192.168.64.3 retry_in=1s
...
start runner failed runnerSet=fruit-runner-macos-arm64 runner=fruit-runner-macos-arm64-d9024 err="start runner in VM fruit-runner-macos-arm64-d9024: SSH not reachable on fruit-runner-macos-arm64-d9024 (192.168.64.3:22) after 2m0s"
stopping VM name=fruit-runner-macos-arm64-d9024
deleting VM name=fruit-runner-macos-arm64-d9024

This repeats across multiple runner names, so it does not look like a single corrupt VM instance.

Profiling / debugging notes

Sampling the live daemon did not show CPU pressure or an internal hot loop. Most threads were parked in Go runtime waits, kevent, or read, which matches the process waiting on external VM/network readiness.

The installed Homebrew binary is stripped, so sample and go tool addr2line could not map addresses back to Go symbols. This makes live production profiling harder than necessary.

Manual checks from the same host during the failure window showed that the Tart VM and credentials can be valid:

sshpass -p admin ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 admin@192.168.64.3 "echo ok && sw_vers -productVersion"
ok
26.3

A tcpdump captured a SYN toward the VM SSH port with the physical LAN source address instead of the Tart bridge source address:

IP 192.168.1.87.<ephemeral> > 192.168.64.3.22: Flags [S]

The expected host-side Tart bridge address is 192.168.64.1 on bridge101; route -n get 192.168.64.3 also reported bridge101.

Suspected root cause

internal/tart/manager.go currently treats SSH readiness as a raw TCP probe:

dialer := net.Dialer{Timeout: 2 * time.Second}
conn, err := dialer.DialContext(ctx, "tcp", ip+":22")
if err == nil {
    conn.Close()
    return nil
}

That probe can falsely fail in this macOS/Tart bridge networking setup. Because the retry log only records name, ip, and retry_in, we lose the actual dial error, local address, and interface information needed to distinguish slow boot, route selection, firewall/refused, and timeout cases.

There is also a behavioral mismatch: after waitForSSH succeeds, the next step uses sshpass ssh. In the observed environment, manual sshpass ssh can succeed while the daemon continues to fail the raw TCP readiness gate.

Proposed fix

  1. Log the actual DialContext error on every readiness retry, or at least on timeout.
  2. On successful TCP probe, log conn.LocalAddr() and conn.RemoteAddr() at debug/info level.
  3. Prefer making readiness use the same transport as the real operation, for example sshpass ssh -o ConnectTimeout=... admin@<ip> true, instead of a bare TCP port check.
  4. If keeping TCP probing, consider explicitly selecting/binding the Tart bridge local address/interface for VM IPs.
  5. Add an ops/debug mode to preserve failed Tart VMs instead of deleting them immediately after readiness timeout.
  6. Publish or retain symbolized debug builds for the runner binary used on self-hosted machines so production samples can be mapped back to Go code.

Increasing the two-minute timeout may help slow boots, but it does not address the false-negative readiness signal seen here.

Acceptance criteria

  • A Tart VM that is reachable by sshpass ssh admin@<ip> true is accepted as ready by the runner manager.
  • Readiness timeout logs include the underlying connection error and enough local/remote address data to diagnose routing/interface issues.
  • The Integration Test (Tart) job no longer remains queued because the runner repeatedly fails during VM SSH readiness.

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