chenhg5/cc-connect · error

listen unix socket: %w

Error message

listen unix socket: %w

What it means

NewAPIServer binds a Unix domain socket at <dataDir>/run/api.sock via net.Listen("unix", ...); failure is wrapped as "listen unix socket: %w". Common causes: another instance is already bound (address in use), the path is too long, or a stale socket file is unremovable/locked.

Source

Thrown at core/api.go:78

	Videos     []FileAttachment  `json:"videos,omitempty"`
	AtUsers    []string          `json:"at_users,omitempty"`
	AtAll      bool              `json:"at_all,omitempty"`
}

// NewAPIServer creates an API server on a Unix socket.
func NewAPIServer(dataDir string) (*APIServer, error) {
	sockDir := filepath.Join(dataDir, "run")
	if err := os.MkdirAll(sockDir, 0o755); err != nil {
		return nil, fmt.Errorf("create run dir: %w", err)
	}
	sockPath := filepath.Join(sockDir, "api.sock")

	// Remove stale socket
	os.Remove(sockPath)

	listener, err := net.Listen("unix", sockPath)
	if err != nil {
		return nil, fmt.Errorf("listen unix socket: %w", err)
	}
	if err := os.Chmod(sockPath, 0o600); err != nil {
		_ = listener.Close()
		return nil, fmt.Errorf("chmod socket: %w", err)
	}

	s := &APIServer{
		socketPath:         sockPath,
		listener:           listener,
		mux:                http.NewServeMux(),
		engines:            make(map[string]*Engine),
		maxAttachmentBytes: DefaultMaxAttachmentSize,
	}
	s.mux.HandleFunc("/send", s.handleSend)
	s.mux.HandleFunc("/sessions", s.handleSessions)
	s.mux.HandleFunc("/cron/add", s.handleCronAdd)
	s.mux.HandleFunc("/cron/list", s.handleCronList)
	s.mux.HandleFunc("/cron/info", s.handleCronInfo)

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Stop the existing instance holding api.sock (check with `lsof <dataDir>/run/api.sock` or `ss -x | grep api.sock`), then retry
  2. Remove the stale socket manually if the process failed to: rm <dataDir>/run/api.sock
  3. Shorten dataDir so the full socket path stays under ~100 characters
  4. Ensure the run directory is writable by the process user
  5. Check the wrapped inner error: 'address already in use' vs 'permission denied' point to different fixes

Example fix

// before
srv, err := core.NewAPIServer("/var/lib/cc-connect") // second instance
// after
if _, err := os.Stat("/var/lib/cc-connect/run/api.sock"); err == nil {
    return errors.New("cc-connect already running (api.sock exists)")
}
srv, err := core.NewAPIServer("/var/lib/cc-connect")
Defensive patterns

Strategy: validation

Validate before calling

sock := filepath.Join(dataDir, "run", "api.sock")
if len(sock) > 100 {
    return fmt.Errorf("socket path too long (%d bytes): %s", len(sock), sock)
}
if _, err := os.Stat(sock); err == nil {
    return fmt.Errorf("socket already exists; is cc-connect already running?")
}

Try / catch

srv, err := core.NewAPIServer(dataDir)
if err != nil {
    if strings.Contains(err.Error(), "listen unix socket") && strings.Contains(err.Error(), "address already in use") {
        slog.Error("another cc-connect instance is running; stop it or use a different dataDir")
    }
    return err
}

Prevention

When it happens

Trigger: Calling NewAPIServer while a previous cc-connect instance is still running and holding api.sock; the stale-socket os.Remove fails due to permissions; the resolved socket path exceeds the ~104/108 byte sun_path limit; the run directory was made non-writable between mkdir and listen.

Common situations: Starting a second daemon against the same dataDir; a crashed instance left a socket that a service user cannot delete; deep dataDir path making the socket path too long; container restart where old socket persists in a mounted volume.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/f8914637d3f4ed9c. Report an issue: GitHub.