schollz/croc · critical
not enough open ports to run local relay
Error message
not enough open ports to run local relay
What it means
Go panic in setupLocalRelay(): utils.FindOpenPorts on 127.0.0.1 starting at RelayPorts[0] returned fewer open ports than len(Options.RelayPorts). The local relay must bind every configured port simultaneously; if any are occupied, croc panics instead of silently degrading.
Source
Thrown at src/croc/croc.go:1083
fmt.Fprintf(os.Stderr, "\r ")
output, colorEnabled := termui.Output(os.Stderr)
if displayName != "" {
fname = quotedFilename(displayName, colorEnabled)
}
if c.TotalNumberFolders > 0 {
fmt.Fprintf(output, "\rSending %s and %s (%s)\n", fname, folderName, utils.ByteCountDecimal(totalFilesSize))
} else {
fmt.Fprintf(output, "\rSending %s (%s)\n", fname, utils.ByteCountDecimal(totalFilesSize))
}
return
}
func (c *Client) setupLocalRelay() {
// setup the relay locally
firstPort, _ := strconv.Atoi(c.Options.RelayPorts[0])
openPorts := utils.FindOpenPorts("127.0.0.1", firstPort, len(c.Options.RelayPorts))
if len(openPorts) < len(c.Options.RelayPorts) {
panic("not enough open ports to run local relay")
}
for i, port := range openPorts {
c.Options.RelayPorts[i] = fmt.Sprint(port)
}
// Capture the local relay control port before any goroutine that handles
// the external relay can overwrite c.Options.RelayPorts.
c.localRelayPort = c.Options.RelayPorts[0]
localRelayPorts := append([]string(nil), c.Options.RelayPorts...)
localRelayBanner := strings.Join(localRelayPorts[1:], ",")
for _, port := range localRelayPorts {
go func(portStr string) {
debugString := "warn"
if c.Options.Debug {
debugString = "debug"
}
err := c.stop.run(
debugString,
"",View on GitHub (pinned to e25f1bdc04)
Solutions
- Find and stop the squatter: lsof -i :9009-9013 or ss -ltnp, then kill the process (pkill -f croc for stale instances)
- Pass a different range: --relay-ports 10001-10006
- Library embedders: probe with utils.FindOpenPorts yourself and supply a confirmed-open list instead of panicking
Example fix
# before croc --relay --relay-ports 9009-9013 send file.bin # 9010 occupied -> panic # after lsof -i :9009-9013 # find and stop the conflicting listener croc --relay --relay-ports 10001-10006 send file.bin
Defensive patterns
Strategy: validation
Validate before calling
// Go: probe ports before starting the local relay
first, _ := strconv.Atoi(opts.RelayPorts[0])
if len(utils.FindOpenPorts("127.0.0.1", first, len(opts.RelayPorts))) < len(opts.RelayPorts) {
return fmt.Errorf("relay ports %v unavailable; pass --relay-ports with a free range", opts.RelayPorts)
} Try / catch
// Panic path: pre-check ports as above, or run croc in a subprocess and map its non-zero exit/panic output to a 'ports busy' message for the user
Prevention
- Check the relay range with ss -ltnp / lsof before starting transfers
- Give concurrent croc transfers distinct --relay-ports ranges
- Kill stale croc processes before relaunching
When it happens
Trigger: Running with a local relay while another process already listens on one of the configured ports: a previous croc instance still running, a concurrent second transfer, or another service (dev server, docker container publishing the range). Default ports occupy several consecutive ports starting at 9009.
Common situations: Two croc transfers at once on one machine; leftover croc process from a crashed session; docker publishing 9009-9013; ephemeral-port pressure breaking the consecutive run.
Related errors
- Could not parse given Upload Limit
- Relay returned an invalid port list: ${banner}
- Relay rejected the connection: ${response}
- Relay could not open the room: ${confirmation}
- Message is too large (${payload.byteLength} bytes)
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/e25fcc4e475ff2f0.
Report an issue: GitHub.