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
- Stop the existing instance holding api.sock (check with `lsof <dataDir>/run/api.sock` or `ss -x | grep api.sock`), then retry
- Remove the stale socket manually if the process failed to: rm <dataDir>/run/api.sock
- Shorten dataDir so the full socket path stays under ~100 characters
- Ensure the run directory is writable by the process user
- 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
- Use a PID/lockfile to prevent two instances on the same dataDir
- Clean up stale sockets on graceful shutdown
- Keep dataDir shallow so the socket path fits sun_path limits
- For systemd, use Restart=on-failure with a small delay to avoid socket races
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
- listen for Agy permission hooks: %w
- antigravity permission bridge: %w
- request usage endpoint: %w
- create run dir: %w
- chmod socket: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/f8914637d3f4ed9c.
Report an issue: GitHub.