ginuerzh/gost · error
empty chain
Error message
empty chain
What it means
ErrEmptyChain is a sentinel error indicating the proxy Chain has no node groups (it is empty). It is returned by Chain.getConn (used by Chain.Conn) when no route is configured, and callers like the SOCKS4 BIND handler explicitly treat it as a benign 'no chain' signal rather than a connection failure.
Source
Thrown at chain.go:16
package gost
import (
"context"
"errors"
"fmt"
"net"
"syscall"
"time"
"github.com/go-log/log"
)
var (
// ErrEmptyChain is an error that implies the chain is empty.
ErrEmptyChain = errors.New("empty chain")
)
// Chain is a proxy chain that holds a list of proxy node groups.
type Chain struct {
isRoute bool
Retries int
Mark int
Interface string
nodeGroups []*NodeGroup
route []Node // nodes in the selected route
}
// NewChain creates a proxy chain with a list of proxy nodes.
// It creates the node groups automatically, one group per node.
func NewChain(nodes ...Node) *Chain {
chain := &Chain{}
for _, node := range nodes {
chain.nodeGroups = append(chain.nodeGroups, NewNodeGroup(node))View on GitHub (pinned to a33fdbf4c9)
Solutions
- Add at least one node/group to the Chain (via AppendNode or the -F/--peer config) before dialing through it
- Check Chain.IsEmpty() before calling Conn() and skip chaining when empty
- Compare returned error against ErrEmptyChain to treat 'no chain' as direct connection instead of failure
- Verify config file actually populates the chain field (typo or empty list)
Example fix
// before
cc, err := h.options.Chain.Conn()
if err != nil {
log.Logf("dial failed: %s", err)
}
// after
cc, err := h.options.Chain.Conn()
if err != nil {
if errors.Is(err, ErrEmptyChain) {
cc, err = net.Dial("tcp", target) // direct connection, no chain
}
if err != nil {
log.Logf("dial failed: %s", err)
}
} Defensive patterns
Strategy: validation
Validate before calling
if chain == nil || chain.IsEmpty() {
// no proxy chain configured: dial directly instead of via chain
return net.Dial("tcp", addr)
}
conn, err := chain.Conn() Type guard
func chainUsable(c *Chain) bool {
return c != nil && !c.IsEmpty()
} Try / catch
conn, err := chain.Conn()
if err != nil {
if errors.Is(err, ErrEmptyChain) {
// handle: no chain configured
return fallbackDial(addr)
}
return err
} Prevention
- Always populate the chain in config before enabling chain-based handlers
- Check IsEmpty() before Conn() when chains are optional in your app
- Compare against the exported ErrEmptyChain sentinel rather than string matching
- Log chain configuration at startup to catch empty-chain mistakes early
When it happens
Trigger: Calling Chain.Conn() / getConn on a Chain constructed without any nodes, e.g. &Chain{} or a chain whose node groups were never added via Append/Compose, then using it to dial.
Common situations: Users configure a gost handler without specifying forward/proxy hops, load a config where the chain section is missing or empty, or build a Chain programmatically and forget to add nodes.
Related errors
AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02).
Data as JSON: /api/errors/fbe317ccecda3b16.
Report an issue: GitHub.