Summary
When a server is configured to listen on a privileged port (e.g. SSH on :22, HTTP on :80) but the process lacks the required capability, net.Listen fails immediately with EACCES. However the process does not exit and no error is logged.
Root cause
In Start() (cmd/soft/serve/server.go:115):
errg, _ := errgroup.WithContext(s.ctx) // derived context is discarded
errg.Go(func() error {
if err := s.SSHServer.ListenAndServe(); !errors.Is(err, ssh.ErrServerClosed) {
return err // EACCES returned here
}
return nil
})
errg.Go(func() error {
// HTTP server runs successfully — blocks here forever
return s.HTTPServer.ListenAndServe()
})
return errg.Wait() // blocks forever because HTTP goroutine never exits
The SSH goroutine fails immediately and returns EACCES to the errgroup. The errgroup cancels its derived context — but the derived context was discarded (_), so no goroutine reacts to the cancellation. The HTTP goroutine (and git daemon) keep running. errg.Wait() blocks indefinitely waiting for them. s.Start() never returns. The caller never receives the error.
Fix
Pre-bind all net.Listeners synchronously in Start() before launching any goroutines. If any net.Listen call fails, return the error immediately (no goroutines have started yet). Pass each pre-bound listener to Serve(l) in the goroutines.
Closes #645