affaan-m/ECC · error

fetch %s: %w

Error message

fetch %s: %w

What it means

Wrapped error from FetchWithTimeout in golang-patterns, raised on the network round-trip. After the request is built, http.DefaultClient.Do(req) is called; any failure is wrapped as fmt.Errorf("fetch %s: %w", url, err). This message means the HTTP client could not successfully complete the exchange with the server - DNS, connection, TLS, or the context deadline fired.

Source

Thrown at skills/golang-patterns/SKILL.md:212

    close(results)
}
```

### Context for Cancellation and Timeouts

```go
func FetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, fmt.Errorf("create request: %w", err)
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("fetch %s: %w", url, err)
    }
    defer resp.Body.Close()

    return io.ReadAll(resp.Body)
}
```

### Graceful Shutdown

```go
func GracefulShutdown(server *http.Server) {
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    <-quit
    log.Println("Shutting down server...")

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Classify with errors.Is against url.Error and net.Error; retry only on Timeout() || Temporary().
  2. Increase the context timeout (or make it configurable) for endpoints known to be slow.
  3. Verify reachability with curl/wget and inspect DNS + TLS from the same environment.
  4. Check HTTP_PROXY/HTTPS_PROXY/NO_PROXY if a proxy is in play.
  5. Use a tuned *http.Client (custom Transport, DialContext, IdleConnTimeout) instead of DefaultClient for production callers.

Example fix

// before
resp, err := http.DefaultClient.Do(req)
if err != nil {
    return nil, fmt.Errorf("fetch %s: %w", url, err)
}

// after - separate retriable network errors from final failures
resp, err := http.DefaultClient.Do(req)
if err != nil {
    var netErr net.Error
    if errors.As(err, &netErr) && (netErr.Timeout() || netErr.Temporary()) {
        return nil, retryable(fmt.Errorf("fetch %s: %w", url, err))
    }
    return nil, fmt.Errorf("fetch %s: %w", url, err)
}
Defensive patterns

Strategy: retry

Validate before calling

func reachable(ctx context.Context, urlStr string) error {
    u, err := url.Parse(urlStr)
    if err != nil || u.Host == "" {
        return fmt.Errorf("no host to dial")
    }
    // best-effort DNS preflight
    _, err = net.DefaultResolver.LookupHost(ctx, u.Hostname())
    return err
}

Type guard

func isRetriableHTTPError(err error) bool {
    var netErr net.Error
    if errors.As(err, &netErr) && (netErr.Timeout() || netErr.Temporary()) {
        return true
    }
    var ue *url.Error
    return errors.As(err, &ue) // url.Error wraps transient op errors
}

Try / catch

var body []byte
err := retryWithBackoff(func() error {
    resp, err := client.Do(req)
    if err != nil {
        if isRetriableHTTPError(err) {
            return err // retriable
        }
        return errwrap.Stop(err) // final
    }
    defer resp.Body.Close()
    if resp.StatusCode >= 500 {
        return fmt.Errorf("status %d", resp.StatusCode)
    }
    body, err = io.ReadAll(resp.Body)
    return err
})

Prevention

When it happens

Trigger: Calling FetchWithTimeout(ctx, url) where http.DefaultClient.Do(req) returns a non-nil error. Concrete triggers: DNS resolution failure, TCP connection refused or timed out, TLS handshake error, the 5-second context deadline exceeded, a proxy error, or a mid-stream reset.

Common situations: Target host is wrong or unreachable from the runtime (network policy, firewall); the 5*time.Second timeout is too short for slow endpoints; TLS certificate is expired or untrusted; HTTP_PROXY/HTTPS_PROXY env vars point at an unreachable proxy; IPv6-only host reached from an IPv4-only network.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/f2cba512f5266fae. Report an issue: GitHub.