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
- Start the watcher (call its Watch/run method) before invoking DispatchNow
- Check the watcher's context lifecycle — recreate or restart the watcher if its context was cancelled
- Guard DispatchNow calls with a check that the watcher is running (e.g. use errors.Is(err, ErrWatcherNotRunning) to handle gracefully)
- 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
- Always start the watcher before calling DispatchNow
- Use errors.Is against the sentinel to handle shutdown races gracefully
- Structure shutdown so DispatchNow is not invoked after context cancellation
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.