ory/hydra · error

watcher is not running

Error message

watcher is not running

What it means

ErrWatcherNotRunning is a sentinel error indicating the watcher's dispatcher has no trigger, i.e. the watcher was never started or its context has already been cancelled. DispatchNow returns it when asked to fire the dispatch signal but no watcher is running to dispatch.

Source

Thrown at oryx/watcherx/definitions.go:36

	EventChannel chan Event
	Watcher      interface {
		// DispatchNow fires the watcher and causes an event.
		//
		// WARNING: The returned channel must be read or no further events will
		// be propagated due to a deadlock.
		DispatchNow() (<-chan int, error)
	}
	dispatcher struct {
		ctx     context.Context
		trigger chan struct{}
		done    chan int
	}
)

var (
	// ErrSchemeUnknown is just for checking with errors.Is()
	ErrSchemeUnknown     = &errSchemeUnknown{}
	ErrWatcherNotRunning = fmt.Errorf("watcher is not running")
)

func (e *errSchemeUnknown) Is(other error) bool {
	_, ok := other.(*errSchemeUnknown)
	return ok
}

func (e *errSchemeUnknown) Error() string {
	return fmt.Sprintf("unknown scheme '%s' to watch", e.scheme)
}

func newDispatcher(ctx context.Context) *dispatcher {
	return &dispatcher{
		ctx:     ctx,
		trigger: make(chan struct{}),
		done:    make(chan int),
	}
}

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Start the watcher (call its Watch/run method) before invoking DispatchNow
  2. Check the watcher's context lifecycle — recreate or restart the watcher if its context was cancelled
  3. Guard DispatchNow calls with a check that the watcher is running (e.g. use errors.Is(err, ErrWatcherNotRunning) to handle gracefully)
  4. Fix shutdown ordering so DispatchNow is not called after context cancellation

Example fix

// before
ch, err := dispatcher.DispatchNow() // watcher never started -> ErrWatcherNotRunning
// after
if err := watcher.Watch(ctx); err != nil { return err }
ch, err := dispatcher.DispatchNow()
if errors.Is(err, watcherx.ErrWatcherNotRunning) {
    // handle: watcher stopped
}
Defensive patterns

Strategy: type-guard

Validate before calling

if watcher == nil || !watcherRunning {
    return errors.New("watcher not started; cannot DispatchNow")
}

Type guard

func isWatcherNotRunning(err error) bool {
    return errors.Is(err, watcherx.ErrWatcherNotRunning)
}

Try / catch

ch, err := dispatcher.DispatchNow()
if errors.Is(err, watcherx.ErrWatcherNotRunning) {
    log.Warn("dispatch requested but watcher is not running; skipping")
    return nil
}
<-ch

Prevention

When it happens

Trigger: Calling DispatchNow() before Watcher/W Dispatch has been started, or after the watcher's context was cancelled; the second usage also returns it when ctx.Done() fires while waiting.

Common situations: Application shutdown racing with a DispatchNow call, forgetting to start the watcher before using the dispatcher, or unit tests calling DispatchNow on a zero-value dispatcher.

Related errors


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/aabee3dadb74cf94. Report an issue: GitHub.