sipeed/picoclaw · error

irc connect failed: %w

Error message

irc connect failed: %w

What it means

The initial conn.Connect() to the IRC server failed; the underlying ircevent error is wrapped with %w. This covers only the first connection — once connected, conn.Loop() handles reconnection internally, so the error means the channel never came up (DNS failure, refused connection, TLS handshake failure, SASL rejection).

Source

Thrown at pkg/channels/irc/irc.go:102

		}
	}

	// SASL auth (takes priority over NickServ)
	if c.config.SASLUser != "" && c.config.SASLPassword.String() != "" {
		conn.SASLLogin = c.config.SASLUser
		conn.SASLPassword = c.config.SASLPassword.String()
	}

	// Register event handlers
	conn.AddConnectCallback(func(e ircmsg.Message) {
		c.onConnect(conn)
	})
	conn.AddCallback("PRIVMSG", func(e ircmsg.Message) {
		c.onPrivmsg(conn, e)
	})

	if err := conn.Connect(); err != nil {
		return fmt.Errorf("irc connect failed: %w", err)
	}

	c.conn = conn

	// ircevent.Connection.Loop() handles reconnection internally.
	go conn.Loop()

	c.SetRunning(true)
	logger.InfoCF("irc", "IRC channel started", map[string]any{
		"server": c.config.Server,
		"nick":   c.config.Nick,
	})
	return nil
}

// Stop disconnects from the IRC server.
func (c *IRCChannel) Stop(ctx context.Context) error {
	logger.InfoC("irc", "Stopping IRC channel")

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Dial manually from the same host: nc -vz irc.example.com 6697 (or openssl s_client -connect host:6697) to isolate network vs auth.
  2. Check the TLS setting matches the port: TLS on 6697, plaintext on 6667; mispairing produces handshake/connection errors.
  3. If SASL is configured, verify credentials — some networks fail the connect at SASL negotiation.
  4. Wrap channel Start in a bounded retry loop, because ircevent's auto-reconnect only kicks in after a first successful connect (see exampleFix).

Example fix

// before — channel dead if IRC is unreachable at boot
if err := ch.Start(ctx); err != nil { return err }

// after — bounded boot-time retry
var err error
for attempt := 1; attempt <= 5; attempt++ {
    if err = ch.Start(ctx); err == nil { break }
    select {
    case <-ctx.Done(): return ctx.Err()
    case <-time.After(time.Duration(attempt) * 2 * time.Second):
    }
}
Defensive patterns

Strategy: retry

Validate before calling

host, port, serr := net.SplitHostPort(cfg.Server)
if serr != nil {
    return fmt.Errorf("irc server must be host:port, got %q", cfg.Server)
}
if conn, derr := net.DialTimeout("tcp", net.JoinHostPort(host, port), 3*time.Second); derr != nil {
    return fmt.Errorf("irc server unreachable: %w", derr)
} else {
    conn.Close()
}

Type guard

func isIRCConnectErr(err error) bool {
    return err != nil && strings.HasPrefix(err.Error(), "irc connect failed:")
}

Try / catch

if err := ch.Start(ctx); err != nil {
    if isIRCConnectErr(err) {
        // initial dial failed; ircevent only auto-reconnects AFTER a first success,
        // so retry Start with backoff until the network is up
        retryWithBackoff(ctx, func() error { return ch.Start(ctx) })
    }
}

Prevention

When it happens

Trigger: TCP dial to server:port fails (wrong host/port, firewall); TLS expected on a plaintext port or vice versa (6697 vs 6667 mismatch); SASL credentials rejected at connect; DNS not resolvable from the container.

Common situations: Boot-time network not ready in k8s (CNI race); port/transport mismatch in config; IRC network blocking the host (killed after abuse, D-lines for cloud IPs); corporate firewall dropping 6667/6697.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/5f6f97bac47bc885. Report an issue: GitHub.