fatedier/frp · error

failed to read file %s: %v

Error message

failed to read file %s: %v

What it means

FileSource.Resolve() wraps the os.ReadFile error when the configured path cannot be read. The underlying error (permissions, missing file, path too long, etc.) is preserved with %v, so the message tells you both which file failed and why. Content is trimmed of surrounding whitespace, so a token file with a trailing newline is fine.

Source

Thrown at pkg/config/v1/value_source.go:109

	if f == nil {
		return errors.New("fileSource cannot be nil")
	}

	if f.Path == "" {
		return errors.New("file path cannot be empty")
	}
	return nil
}

// Resolve reads and returns the content from the specified file.
func (f *FileSource) Resolve(_ context.Context) (string, error) {
	if err := f.Validate(); err != nil {
		return "", err
	}

	content, err := os.ReadFile(f.Path)
	if err != nil {
		return "", fmt.Errorf("failed to read file %s: %v", f.Path, err)
	}

	// Trim whitespace, which is important for file-based tokens
	return strings.TrimSpace(string(content)), nil
}

// Validate validates the ExecSource configuration.
func (e *ExecSource) Validate() error {
	if e == nil {
		return errors.New("execSource cannot be nil")
	}

	if e.Command == "" {
		return errors.New("exec command cannot be empty")
	}

	for _, env := range e.Env {
		if env.Name == "" {

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Verify the file exists and is readable: ls -l /path/to/file and run frp as a user with read permission.
  2. Use an absolute path in file.path to avoid working-directory surprises.
  3. If the file is injected asynchronously (sidecar, Vault agent), make frp start after the file exists (initContainer, systemd dependency) — there is no retry built in.
  4. In containers, double-check the mount target matches file.path exactly.

Example fix

# before (frpc.toml)
[auth.token.valueSource]
type = "file"
file.path = "token.txt"   # relative; missing in container

# after
type = "file"
file.path = "/etc/frp/token.txt"   # absolute, mounted read-only
Defensive patterns

Strategy: try-catch

Validate before calling

func fileReadable(path string) error {
	info, err := os.Stat(path)
	if err != nil {
		return err
	}
	if info.IsDir() {
		return fmt.Errorf("%s is a directory", path)
	}
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	return f.Close()
}

Try / catch

val, err := vs.Resolve(ctx)
if err != nil {
	if strings.HasPrefix(err.Error(), "failed to read file") {
		// config/startup error: report path and fix mount or permissions; do not retry blindly
		log.Fatalf("value source file unreadable: %v", err)
	}
	return err
}

Prevention

When it happens

Trigger: ValueSource{Type: "file"}.Resolve(ctx) where the path does not exist, is a directory, or is not readable by the frp process user; also triggered by a symlink loop or a path on an unmounted volume at startup.

Common situations: Docker/Kubernetes: token file not mounted into the container, or mounted at a different path than configured; file permissions 0600 owned by root while frpc runs as a non-root user; secret (e.g. a Vault-injected token file) not yet written when frp starts; relative path resolved against a different working directory.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/5260ca0e1cfe43c0. Report an issue: GitHub.