grpc/grpc-go · error

%v: %w

Error message

%v: %w

What it means

Returned by jwtFileReader.readToken when os.ReadFile fails to read the token file (the %v is the OS error and the wrapped sentinel is errTokenFileAccess). The call site in jwtTokenFileCallCreds converts this into a gRPC codes.Unavailable status, since the file (often a mounted token volume) is temporarily unreadable.

Solutions

  1. Check the exact token file path exists and is readable by the process user (ls -l, stat).
  2. In Kubernetes, ensure the ServiceAccount token is projected and add an initContainer or readiness probe that waits for the file.
  3. Use an absolute path for tokenFilePath.
  4. Run the binary as a user/group that has read permission on the token file.

Example fix

// before
creds, err := jwt.NewTokenFileCallCredentials("token.jwt")
// after
creds, err := jwt.NewTokenFileCallCredentials("/var/run/secrets/tokens/my-sa-token")
Defensive patterns

Strategy: validation

Validate before calling

// Validate readability before constructing the credential.
func checkTokenFile(path string) error {
    info, err := os.Stat(path)
    if err != nil {
        return fmt.Errorf("token file %q: %w", path, err)
    }
    if info.Mode().Perm()&0400 == 0 {
        return fmt.Errorf("token file %q not readable", path)
    }
    return nil
}

if err := checkTokenFile(tokenPath); err != nil {
    log.Fatal(err)
}

Try / catch

// The RPC surfaces codes.Unavailable; detect and fail fast on startup:
if status.Code(err) == codes.Unavailable && strings.Contains(err.Error(), errTokenFileAccess.Error()) {
    log.Fatal("token file unreadable; check path and permissions")
}

Prevention

When it happens

Trigger: The configured token file path does not exist; the process lacks read permission on the file; the file is on a volume that has not yet been mounted (Kubernetes projected service-account token) or has been rotated away; a path typo or wrong working directory.

Common situations: Kubernetes pod starting before the projected token volume is mounted; running as a user without read access to /var/run/secrets/...; relative path resolved against an unexpected working directory; NFS/containerd volume mount race.

Related errors


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

Appendix: source

Thrown at credentials/jwt/file_reader.go:52

)

// jwtClaims represents the JWT claims structure for extracting expiration time.
type jwtClaims struct {
	Exp int64 `json:"exp"`
}

// jwtFileReader handles reading and parsing JWT tokens from files.
// It is safe to call methods on this type concurrently as no state is stored.
type jwtFileReader struct {
	tokenFilePath string
}

// readToken reads and parses a JWT token from the configured file.
// Returns the token string, expiration time, and any error encountered.
func (r *jwtFileReader) readToken() (string, time.Time, error) {
	tokenBytes, err := os.ReadFile(r.tokenFilePath)
	if err != nil {
		return "", time.Time{}, fmt.Errorf("%v: %w", err, errTokenFileAccess)
	}

	token := strings.TrimSpace(string(tokenBytes))
	if token == "" {
		return "", time.Time{}, fmt.Errorf("token file %q is empty: %w", r.tokenFilePath, errJWTValidation)
	}

	exp, err := r.extractExpiration(token)
	if err != nil {
		return "", time.Time{}, fmt.Errorf("token file %q: %v: %w", r.tokenFilePath, err, errJWTValidation)
	}

	return token, exp, nil
}

const tokenDelim = "."

// extractClaimsRaw returns the JWT's claims part as raw string. Even though the

View on GitHub (pinned to 0c51461d27)