hashicorp/nomad · error

error stream is no longer running: %w

Error message

error stream is no longer running: %w

What it means

This error is returned by the NDJSON stream's Send method in nomad/stream/ndjson.go when the stream's context has been cancelled while trying to publish an event. It wraps the underlying send error and signals that consumers can no longer receive events because the stream pipeline is shut down. It exists so callers draining a subscription learn immediately that the output channel is dead rather than blocking forever on a send.

Source

Thrown at nomad/stream/ndjson.go:90

}

// Send encodes an object into Newline delimited json. An error is returned
// if json encoding fails or if the stream is no longer running.
func (n *JsonStream) Send(v any) error {
	if n.ctx.Err() != nil {
		return n.ctx.Err()
	}

	var buf bytes.Buffer
	enc := codec.NewEncoder(&buf, structs.JsonHandleWithExtensions)
	err := enc.Encode(v)
	if err != nil {
		return fmt.Errorf("error marshaling json for stream: %w", err)
	}

	select {
	case <-n.ctx.Done():
		return fmt.Errorf("error stream is no longer running: %w", err)
	case n.outCh <- &structs.EventJson{Data: buf.Bytes()}:
	}

	return nil
}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Stop calling Send once the stream is stopped: check stream context cancellation (or the error from Send) and exit the producer loop.
  2. Ensure the producer goroutine's lifetime is tied to the stream context — use errgroup or a done channel derived from the request context so sends stop when the client disconnects.
  3. If this surfaces during exec streaming, verify the exec session is not being driven after the raw stream (ExecTaskStreamingRaw) has returned.
  4. If the wrapped cause is a json.Marshal error on a stopped stream, fix the marshal failure too — that error indicates an event payload with unsupported types or an EncodeValue missing.

Example fix

// before
for event := range events {
    if err := stream.Send(event); err != nil {
        return err
    }
}
// after
for event := range events {
    if err := stream.Send(event); err != nil {
        if strings.Contains(err.Error(), "error stream is no longer running") {
            return nil // client disconnected; stop producer cleanly
        }
        return err
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

select {
case <-streamCtx.Done():
    // skip send; stream already stopped
    return nil
default:
}
// proceed to stream.Send(event)

Type guard

func streamAlive(ctx context.Context) bool {
    select {
    case <-ctx.Done():
        return false
    default:
        return true
    }
}

Try / catch

if err := stream.Send(event); err != nil {
    if strings.Contains(err.Error(), "error stream is no longer running") {
        return nil // graceful shutdown
    }
    return fmt.Errorf("stream send failed: %w", err)
}

Prevention

When it happens

Trigger: Calling Send on a JsonStream after its context (n.ctx) was cancelled — e.g. the HTTP request ended, the client disconnected, Stop() was called, or the subscription was torn down — while the internal outCh buffer path also failed, so the send falls through to the ctx.Done() branch. Notably it wraps `err` from the preceding json.Marshal, so when marshaling fails on a stopped stream this message appears with the marshal error as the cause.

Common situations: A WebSocket/HTTP streaming client disconnects mid-stream and the handler keeps calling Send for events that arrive afterwards (e.g. exec task streaming, event streams in `nomad monitor`). Also seen in tests like TestJson_Send_After_Stop. A latent bug: the ctx.Done() branch wraps the marshaling `err`, which is nil in the plain cancel case, yielding the odd message 'error stream is no longer running: %!w(<nil>)'.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/e1f9e4823ed26b67. Report an issue: GitHub.