Skip to content

Daemon connection refused on macOS: client uses localhost but server binds 127.0.0.1 (IPv4-only) #435

@perbu

Description

@perbu

Symptom

Started crit daemon at http://localhost:60045 (PID 16325)
Error: could not reach daemon on port 60045: Get "http://localhost:60045/api/session": dial tcp [::1]:60045: connect: connection refused

The daemon starts and binds successfully, but the client can't connect to it on macOS.

Root cause

  • The server binds IPv4 only in cli_serve.go:312:
    listener, err = net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
  • The internal HTTP client calls use http://localhost:<port>.
  • On macOS, localhost resolves to ::1 (IPv6) before 127.0.0.1. Go's HTTP client hits IPv6 loopback first, gets ECONNREFUSED (nothing listening on ::1:<port>), and surfaces the error instead of falling back to IPv4.

Verified locally:

$ dscacheutil -q host -a name localhost
name: localhost
ipv6_address: ::1

name: localhost
ip_address: 127.0.0.1

Suggested fix

Switch the internal HTTP client URLs to 127.0.0.1 so they match the server bind. Five call sites need the change:

  • daemon.go:281daemonAlive health probe
  • daemon.go:302daemonHasBrowser health probe
  • main.go:1538waitForDaemonReady (/api/session)
  • main.go:1566runReviewClientRaw (/api/review-cycle)
  • main.go:1714 — review-cycle POST in runReviewClient
  • focus_cli.go:308fetchSessionFocus

User-facing display strings ("Started crit daemon at http://localhost:...") and openBrowser(...) calls can stay as localhost — browsers do happy-eyeballs fallback and localhost is friendlier to read/copy.

Alternative: bind the listener dual-stack (e.g. listen on both 127.0.0.1 and [::1], or on localhost). This is more invasive and changes the security posture documented in CLAUDE.md ("Server binds to 127.0.0.1 only"), so the targeted client-side fix is preferable.

Repro

macOS where localhost resolves to ::1 first (default on recent macOS). Run crit in any git repo with changes; the daemon spawns, the client immediately fails to reach it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions