grpc/grpc-go · error

authz: requires refresh interval(%v) greater than 0s

Error message

authz: requires refresh interval(%v) greater than 0s

What it means

Returned by authz.NewFileWatcherWithOptions (grpc_authz_server_interceptors.go:140) when RefreshDuration is <= 0. The file watcher uses time.NewTicker with this duration to periodically reload the policy; a zero or negative duration would panic the ticker, so the constructor rejects it.

Source

Thrown at authz/grpc_authz_server_interceptors.go:140

// that contains JSON string of authorization policy and a refresh duration to
// specify the amount of time between policy refreshes.
func NewFileWatcher(file string, duration time.Duration) (*FileWatcherInterceptor, error) {
	return NewFileWatcherWithOptions(FileWatcherOptions{PolicyFile: file, RefreshDuration: duration, OnPolicyUpdate: nil})
}

// NewFileWatcherWithOptions returns a new FileWatcherInterceptor from a set of
// options.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func NewFileWatcherWithOptions(options FileWatcherOptions) (*FileWatcherInterceptor, error) {
	if options.PolicyFile == "" {
		return nil, fmt.Errorf("authz: authorization policy file path is empty")
	}
	if options.RefreshDuration <= time.Duration(0) {
		return nil, fmt.Errorf("authz: requires refresh interval(%v) greater than 0s", options.RefreshDuration)
	}
	i := &FileWatcherInterceptor{options: options}
	if err := i.updateInternalInterceptor(); err != nil {
		return nil, err
	}
	ctx, cancel := context.WithCancel(context.Background())
	i.cancel = cancel
	// Create a background go routine for policy refresh.
	go i.run(ctx)
	return i, nil
}

func (i *FileWatcherInterceptor) run(ctx context.Context) {
	ticker := time.NewTicker(i.options.RefreshDuration)
	for {
		if err := i.updateInternalInterceptor(); err != nil {
			logger.Warningf("authorization policy reload status err: %v", err)
		}

View on GitHub (pinned to 03255a9237)

Solutions

  1. Set RefreshDuration to a positive value such as 10*time.Second or 1*time.Minute.
  2. Default the duration to a sane positive value when it is unset in config.
  3. Validate duration > 0 before calling the constructor and surface a clear config error.
  4. If you do not want periodic reload, use authz.NewStatic (one-shot) instead of the file watcher.

Example fix

// before
az, err := authz.NewFileWatcherWithOptions(
    authz.FileWatcherOptions{PolicyFile: path}) // RefreshDuration == 0

// after
dur := cfg.RefreshDuration
if dur <= 0 {
    dur = 30 * time.Second
}
az, err := authz.NewFileWatcherWithOptions(
    authz.FileWatcherOptions{PolicyFile: path, RefreshDuration: dur})
Defensive patterns

Strategy: validation

Validate before calling

if options.RefreshDuration <= 0 {
    options.RefreshDuration = 30 * time.Second
}

Try / catch

if _, err := authz.NewFileWatcherWithOptions(opts); err != nil {
    if strings.Contains(err.Error(), "refresh interval") {
        opts.RefreshDuration = 30 * time.Second
    }
}

Prevention

When it happens

Trigger: Calling NewFileWatcher(file, 0), NewFileWatcher(file, -time.Second), or NewFileWatcherWithOptions with an uninitialized RefreshDuration (the zero value time.Duration(0)).

Common situations: FileWatcherOptions struct constructed without setting RefreshDuration; a config that conditionally sets the duration but leaves it zero when the condition is false; passing time.Duration from a parsed value that defaulted to 0.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/5a2e57ff75c667dd. Report an issue: GitHub.