multica-ai/multica · error

could not start the local login callback server (used to rec

Error message

could not start the local login callback server (used to receive the browser sign-in); a firewall or another process may be blocking local ports: %w

What it means

Browser login starts a temporary local HTTP server on a random port to receive the OAuth callback; net.Listen("tcp4", bindAddr+":0") binds it. If the bind fails (address in use on the resolved bind addr, firewall blocking loopback listeners, no IPv4 stack) the flow cannot start and returns this wrapped error.

Source

Thrown at server/cmd/multica/cmd_auth.go:251

		return v4
	}
	return local.IP
}

func runAuthLoginBrowser(cmd *cobra.Command) error {
	serverURL := resolveHumanServerURL(cmd)
	appURL := resolveAppURL(cmd)

	flagHost := callbackHostFlagValue(cmd)
	callbackHost, bindAddr := resolveCallbackBinding(flagHost, serverURL, appURL, detectOutboundIP)

	// Pin to "tcp4" — a bare "tcp" on macOS can produce an IPv6-only socket
	// that IPv4 clients (including browsers resolving localhost → 127.0.0.1)
	// cannot reach. The callback URL is always an IPv4 literal or hostname,
	// so an IPv4 listener is what the browser actually needs.
	listener, err := net.Listen("tcp4", bindAddr+":0")
	if err != nil {
		return fmt.Errorf("could not start the local login callback server (used to receive the browser sign-in); a firewall or another process may be blocking local ports: %w", err)
	}
	defer listener.Close()

	port := listener.Addr().(*net.TCPAddr).Port
	callbackURL := fmt.Sprintf("http://%s:%d/callback", callbackHost, port)

	// Generate a random state parameter for CSRF protection.
	stateBytes := make([]byte, 16)
	if _, err := rand.Read(stateBytes); err != nil {
		return fmt.Errorf("failed to generate state: %w", err)
	}
	state := hex.EncodeToString(stateBytes)

	loginURL := fmt.Sprintf("%s/login?cli_callback=%s&cli_state=%s", appURL, url.QueryEscape(callbackURL), url.QueryEscape(state))

	// Channel to receive the JWT from the browser callback.
	jwtCh := make(chan string, 1)
	errCh := make(chan error, 1)

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Use token login which needs no listener: `multica login --token <PAT>`
  2. Pin the callback host flag to loopback (e.g. --callback-host 127.0.0.1) so binding targets localhost
  3. Temporarily allow the CLI to listen on loopback in endpoint firewall policy
  4. If in a sandbox without networking, perform login on the host and mount/copy the profile config

Example fix

# before (bind on detected outbound IP fails)
multica login

# after
multica login --callback-host 127.0.0.1
# or skip the listener entirely
multica login --token mul_ABCdef123...
Defensive patterns

Strategy: fallback

Validate before calling

# cheap pre-check that loopback IPv4 binding works
python3 - <<'EOF' || echo 'loopback bind blocked — use token login'
import socket
s=socket.socket(socket.AF_INET); s.bind(('127.0.0.1',0)); s.close()
EOF

Prevention

When it happens

Trigger: A firewall (corporate endpoint protection) blocking bind on the resolved callback host; the outbound-IP detection path resolving to an address that cannot be bound; IPv4 disabled/unavailable in the environment (some containers); unusually restrictive sandbox denying socket creation.

Common situations: Locked-down corporate laptops; container/agent sandboxes with no network or IPv4-only restrictions; SSH sessions where detectOutboundIP picks an unroutable bind address.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/8a98599eabfbf9ab. Report an issue: GitHub.