grpc/grpc-go · error

token file %q is empty: %w

Error message

token file %q is empty: %w

What it means

After os.ReadFile succeeds the content is trimmed and, if it equals "", readToken returns 'token file %q is empty: %w' wrapping errJWTValidation (file_reader.go:55-58). The file exists and is readable but holds no usable token bytes.

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 03255a9237)

Solutions

  1. Ensure whatever writes the token file atomically renames a fully-written temp file into place so readers never see empty content.
  2. Delay process start until the token file is non-empty (e.g. a readiness check on the mounted volume).
  3. Re-check the Secret/ConfigMap data key name matches what is written.
  4. Log the file size at startup to catch zero-length mounts quickly.

Example fix

// before: writer truncates then writes; reader sees empty
os.WriteFile(path, []byte(""), 0o600)

// after: atomic write via temp + rename
tmp := path + ".tmp"
os.WriteFile(tmp, []byte(tok), 0o600)
os.Rename(tmp, path)
Defensive patterns

Strategy: validation

Validate before calling

// Reject an empty token file at startup.
fi, err := os.Stat(tokenFilePath)
if err != nil { return err }
if fi.Size() == 0 {
    return fmt.Errorf("token file %q is empty", tokenFilePath)
}

Type guard

func isTokenFileEmptyErr(err error) bool {
    return errors.Is(err, errJWTValidation) && strings.Contains(err.Error(), "is empty")
}

Try / catch

_, _, err := r.readToken()
if err != nil && strings.Contains(err.Error(), "is empty") {
    // writer has not populated the file yet; wait for non-zero size then retry.
    return err
}

Prevention

When it happens

Trigger: The token file exists but is zero-length or contains only whitespace/newlines. Common with a freshly created but not-yet-populated Secret, a mounted empty ConfigMap key, or a token-rotating sidecar that truncated the file mid-write when read.

Common situations: Projected service-account token volume not yet refreshed by kubelet on pod start, an init container that created the file but did not write content, a Secret referenced with the wrong data key (empty file created by mount), or a writer that opens with O_TRUNC and the reader races the write.

Related errors


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