{"record":{"id":"bd3c3693be38bbcb","repo":"grpc/grpc-go","slug":"token-file-q-is-empty-w","errorCode":null,"errorMessage":"token file %q is empty: %w","messagePattern":"token file %q is empty: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/jwt/file_reader.go","lineNumber":57,"sourceCode":"}\n\n// jwtFileReader handles reading and parsing JWT tokens from files.\n// It is safe to call methods on this type concurrently as no state is stored.\ntype jwtFileReader struct {\n\ttokenFilePath string\n}\n\n// readToken reads and parses a JWT token from the configured file.\n// Returns the token string, expiration time, and any error encountered.\nfunc (r *jwtFileReader) readToken() (string, time.Time, error) {\n\ttokenBytes, err := os.ReadFile(r.tokenFilePath)\n\tif err != nil {\n\t\treturn \"\", time.Time{}, fmt.Errorf(\"%v: %w\", err, errTokenFileAccess)\n\t}\n\n\ttoken := strings.TrimSpace(string(tokenBytes))\n\tif token == \"\" {\n\t\treturn \"\", time.Time{}, fmt.Errorf(\"token file %q is empty: %w\", r.tokenFilePath, errJWTValidation)\n\t}\n\n\texp, err := r.extractExpiration(token)\n\tif err != nil {\n\t\treturn \"\", time.Time{}, fmt.Errorf(\"token file %q: %v: %w\", r.tokenFilePath, err, errJWTValidation)\n\t}\n\n\treturn token, exp, nil\n}\n\nconst tokenDelim = \".\"\n\n// extractClaimsRaw returns the JWT's claims part as raw string. Even though the\n// header and signature are not used, it still expects that the input string to\n// be well-formed (ie comprised of exactly three parts, separated by a dot\n// character).\nfunc extractClaimsRaw(s string) (string, bool) {\n\t_, s, ok := strings.Cut(s, tokenDelim)","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/credentials/jwt/file_reader.go#L39-L75","documentation":"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.","triggerScenarios":"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.","commonSituations":"Token-injecting sidecar (Vault agent, gcp-creds) starting after the main container; Kubernetes Secret referenced but empty; CI copying a placeholder file.","solutions":["Verify the token file has content: wc -c /path/to/token.","Ensure the token injector (sidecar/init) runs to completion before the gRPC client reads the file; use a shared volume with proper ordering.","Regenerate the token/Secret so it contains a valid JWT.","Add a startup check that waits until the file is non-empty before dialing."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"func nonEmptyTokenFile(path string) error {\n    b, err := os.ReadFile(path)\n    if err != nil {\n        return err\n    }\n    if strings.TrimSpace(string(b)) == \"\" {\n        return fmt.Errorf(\"token file %q is empty\", path)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["At startup, verify the token file is non-empty before dialing.","Ensure the token injector (sidecar/init) completes before the gRPC client reads the file.","Use a readiness probe that checks file content, not just existence."],"tags":["grpc","jwt","filesystem","credentials","configuration"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}