forward: 1006 reconnect never triggers (wrapped error vs IsCloseError), and exits with a usage dump
- Dominant language
- Go
- Stars
- 43
- Forks
- 16
- PR merge metrics
- No merged PRs in 30d
Description
### Summary
`gh webhook forward` has retry logic for a 1006 abnormal closure, but it never runs. The error is wrapped before it reaches the check, and `websocket.IsCloseError` does not unwrap. The result is that the command exits on the first server-side disconnect — and, because the command sets neither `SilenceUsage` nor `SilenceErrors`, prints its entire help text on the way out.
In my case GitHub closes the forwarding socket roughly every five minutes, so a long-running `forward` dies about twelve times an hour and each death writes ~26 lines of usage output. 73% of my log is cobra help text.
### The cause
`webhook/forward.go`, in `runFwd`:
```go
for i := 0; i < 3; i++ {
err := handleWebsocket(out, url, token, wsURL, activateHook)
if err != nil {
// If the error is a server disconnect (1006), retry connecting
if websocket.IsCloseError(err, websocket.CloseAbnormalClosure) {
time.Sleep(5 * time.Second)
continue
}
...
return err
}
}
```
`handleWebsocket` wraps the read error before returning it:
```go
err := c.ReadJSON(&ev)
if err != nil {
return fmt.Errorf("error receiving json event: %w", err)
}
```
and `gorilla/websocket` v1.5.0's `IsCloseError` is a bare type assertion, not `errors.As`:
```go
func IsCloseError(err error, codes ...int) bool {
if e, ok := err.(*CloseError); ok {
```
So the assertion fails on the wrapped error, the `continue` is unreachable, and `runFwd` returns immediately. The observed error confirms it — this is the wrapped text, not `unable to connect to webhooks server, forwarding stopped`, which is what exhausting the loop would produce:
```
Error: error receiving json event: websocket: close 1006 (abnormal closure): unexpected EOF
```
### Suggested fix
```go
var closeErr *websocket.CloseError
if errors.As(err, &closeErr) && closeErr.Code == websocket.CloseAbnormalClosure {
time.Sleep(5 * time.Second)
continue
}
```
and `SilenceUsage: true` on the command, since a runtime network error is not a usage error.
### One more thing worth considering
Fixing the unwrap alone may not be enough for a long-running forwarder. `i` counts *total* disconnects across the process lifetime and is never reset after a successful session, so with a server that closes on a timer the command would still exit permanently after three closes — for me, about fifteen minutes. Resetting the counter after a session that lasted a meaningful length of time would make `forward` survivable as a long-running process.
### Versions
- `gh` 2.98.0
- `gh-webhook` v0.2.0
- macOS 15 (arm64)
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.