grpc/grpc-go · error

token file is empty

Error message

token file %q is empty: %w

What it means

Returned by jwtFileReader.readToken when the file was read successfully but contains only whitespace. The sentinel is errJWTValidation, which the call site maps to codes.Unauthenticated. An empty token file usually means an external token injector (e.g. sidecar, agent) has not yet written the token, or wrote it incorrectly.

Solutions

  1. Verify the token file has content: wc -c /path/to/token.
  2. Ensure the token injector (sidecar/init) runs to completion before the gRPC client reads the file; use a shared volume with proper ordering.
  3. Regenerate the token/Secret so it contains a valid JWT.
  4. Add a startup check that waits until the file is non-empty before dialing.
Defensive patterns

Strategy: validation

Validate before calling

func nonEmptyTokenFile(path string) error {
    b, err := os.ReadFile(path)
    if err != nil {
        return err
    }
    if strings.TrimSpace(string(b)) == "" {
        return fmt.Errorf("token file %q is empty", path)
    }
    return nil
}

Prevention

When it happens

Trigger: The token file exists but is zero-byte or whitespace-only; a token-refresh agent created/truncated the file but crashed before writing; a ConfigMap/Secret mounted as an empty file.

Common situations: Token-injecting sidecar (Vault agent, gcp-creds) starting after the main container; Kubernetes Secret referenced but empty; CI copying a placeholder file.

Related errors


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

Appendix: source

Thrown at credentials/jwt/file_reader.go:57

}

// 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
// header and signature are not used, it still expects that the input string to
// be well-formed (ie comprised of exactly three parts, separated by a dot
// character).
func extractClaimsRaw(s string) (string, bool) {
	_, s, ok := strings.Cut(s, tokenDelim)

View on GitHub (pinned to 0c51461d27)