cloudflare/cloudflared · error

ServeTunnel: %v

Error message

ServeTunnel: %v

What it means

In ServeTunnel's deferred recover handler, a panic value that is not an `error` cannot be wrapped directly, so it is converted with fmt.Errorf("ServeTunnel: %v", r). This preserves the panic payload (string, runtime error, etc.) as an error, then attaches the goroutine stack trace and marks the tunnel failure as recoverable so the supervisor can retry.

Source

Thrown at supervisor/tunnel.go:384

// ServeTunnel runs a single tunnel connection, returns nil on graceful shutdown,
// on error returns a flag indicating if error can be retried
func (e *EdgeTunnelServer) serveTunnel(
	ctx context.Context,
	connLog *ConnAwareLogger,
	addr *allregions.EdgeAddr,
	connIndex uint8,
	fuse *booleanFuse,
	backoff *protocolFallback,
	protocol connection.Protocol,
) (err error, recoverable bool) {
	// Treat panics as recoverable errors
	defer func() {
		if r := recover(); r != nil {
			var ok bool
			err, ok = r.(error)
			if !ok {
				err = fmt.Errorf("ServeTunnel: %v", r)
			}
			err = errors.Wrapf(err, "stack trace: %s", string(debug.Stack()))
			recoverable = true
		}
	}()

	defer e.config.Observer.SendDisconnect(connIndex)
	err, recoverable = e.serveConnection(
		ctx,
		connLog,
		addr,
		connIndex,
		fuse,
		backoff,
		protocol,
	)

	if err != nil {

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Read the attached 'stack trace: ...' wrapper in the error to locate the panicking frame.
  2. Fix the underlying panic cause (nil dereference, bad index, concurrent map access) identified by the stack.
  3. Avoid panic(non-error) in your own code; panic with error values so the recover path preserves types.
  4. Run with `GOTRACEBACK=all` / race detector in testing to surface the concurrency or memory bug causing the panic.
  5. Update cloudflared if the stack points into a dependency; the panic may already be fixed upstream.
Defensive patterns

Strategy: retry

Try / catch

err := stream.PipeBidirectional(ctx, tunnelConn, originConn)
if err != nil && strings.Contains(err.Error(), "timeout waiting for second stream") {
	log.Warn().Err(err).Msg("second stream never arrived; connection terminated")
	// classify as benign for single-stream protocols
}

Prevention

When it happens

Trigger: Any goroutine panic inside ServeTunnel's call tree that panics with a non-error value: e.g. panic("string literal"), nil map writes (runtime errors are error, but panic(1) or panic(fmt.Sprintf(...)) are not), or library code panicking with arbitrary values.

Common situations: Concurrency bugs (concurrent map read/write panics are runtime errors but can arrive as non-error values depending on throw path), indexing bugs, misconfigured handlers panicking with strings, third-party dependency panics during tunnel serving.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/9a0eff51fcdeb6a5. Report an issue: GitHub.