grpc/grpc-go · error

policyFile( ) read failed

Error message

policyFile(%s) read failed: %v

What it means

Returned by FileWatcherInterceptor.updateInternalInterceptor (grpc_authz_server_interceptors.go:175) when os.ReadFile(options.PolicyFile) fails; the underlying error is wrapped with the file path. This is invoked both at construction time (NewFileWatcherWithOptions) and on every refresh tick (run goroutine). On refresh failure the previously loaded interceptor is kept, so authorization continues with the last good policy; at construction time the error propagates to the caller and the watcher is not started.

Solutions

  1. Confirm the path is absolute and the file exists and is readable by the process uid (ls -l, stat).
  2. Ensure mounted config (Kubernetes ConfigMap/secret, volume) is available before app start; use a readiness check or retry-on-startup.
  3. After fixing, let the file watcher self-heal on the next tick (it logs a warning and keeps the old policy); only a construction-time failure blocks startup.
  4. If using a relative path, switch to an absolute path or resolve relative to the config directory explicitly.

Example fix

// before
fw, err := authz.NewFileWatcher("policy.json", 10*time.Second) // CWD-dependent

// after
fw, err := authz.NewFileWatcher("/etc/app/authz/policy.json", 10*time.Second)
Defensive patterns

Strategy: validation

Validate before calling

if _, err := os.Stat(policyPath); err != nil {
    log.Fatalf("authz policy file not accessible: %v", err)
}
fw, err := authz.NewFileWatcher(policyPath, refresh)

Try / catch

fw, err := authz.NewFileWatcher(policyPath, refresh)
if err != nil {
    if strings.Contains(err.Error(), "read failed") {
        // check existence/permissions; the running watcher keeps the prior policy on refresh errors
    }
}

Prevention

When it happens

Trigger: Policy file does not exist, is not readable (permissions), path is a directory, or the path is on a filesystem that is temporarily unavailable; also symlink breakage or container volume mount not yet ready at startup.

Common situations: Wrong/relative path in a container where CWD differs; ConfigMap/secret not mounted yet when the process starts; file replaced atomically with a brief window of ENOENT; permission mismatch (mode 0600 owned by another uid); refresh interval firing during a deploy that swaps the file.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/bd711cd84488ac5d. Report an issue: GitHub.

Appendix: source

Thrown at authz/grpc_authz_server_interceptors.go:175

			logger.Warningf("authorization policy reload status err: %v", err)
		}
		select {
		case <-ctx.Done():
			ticker.Stop()
			return
		case <-ticker.C:
		}
	}
}

// updateInternalInterceptor checks if the policy file that is watching has changed,
// and if so, updates the internalInterceptor with the policy. Unlike the
// constructor, if there is an error in reading the file or parsing the policy, the
// previous internalInterceptors will not be replaced.
func (i *FileWatcherInterceptor) updateInternalInterceptor() error {
	policyContents, err := os.ReadFile(i.options.PolicyFile)
	if err != nil {
		return fmt.Errorf("policyFile(%s) read failed: %v", i.options.PolicyFile, err)
	}
	if bytes.Equal(i.policyContents, policyContents) {
		return nil
	}
	i.policyContents = policyContents
	policyContentsString := string(policyContents)
	interceptor, err := NewStatic(policyContentsString)
	if err != nil {
		return err
	}
	atomic.StorePointer(&i.internalInterceptor, unsafe.Pointer(interceptor))
	logger.Infof("authorization policy reload status: successfully loaded new policy %v", policyContentsString)
	if i.options.OnPolicyUpdate != nil {
		i.options.OnPolicyUpdate(policyContentsString)
	}
	return nil
}

View on GitHub (pinned to 0c51461d27)