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
- Ensure whatever writes the token file atomically renames a fully-written temp file into place so readers never see empty content.
- Delay process start until the token file is non-empty (e.g. a readiness check on the mounted volume).
- Re-check the Secret/ConfigMap data key name matches what is written.
- 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
- Have writers atomically rename a fully-written temp file into place.
- Delay process start until the token volume is non-empty (readiness probe).
- Double-check the Secret/ConfigMap data key name.
- Log the token file size at startup.
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
- %v: %w
- token file access error
- credentials: audience cannot be empty
- token file %q: %v: %w
- no expiration claims
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/bd3c3693be38bbcb.
Report an issue: GitHub.