AlexxIT/go2rtc · critical
nest: invalid cache key format
Error message
nest: invalid cache key format
What it means
In refreshToken (pkg/nest/api.go:249), the cached refresh key is split on ':' and must yield exactly 3 parts: clientID, clientSecret, refreshToken. The library throws this error when the cached credential string does not have that clientID:clientSecret:refreshToken shape, because it cannot reconstruct the OAuth credentials needed to call NewAPI and refresh the token.
Solutions
- Clear the credential cache and re-authenticate so the key is written in the current clientID:clientSecret:refreshToken format.
- Verify the cached value contains exactly two ':' separators and that clientSecret/refreshToken contain no raw ':' (URL-encode or re-store them if they do).
- Check how the key was stored: ensure the code path that caches credentials uses the same 3-field format that refreshToken expects.
- Upgrade/downgrade the library so the writer and reader of the cache key agree on the format.
Example fix
// before: storing a 2-field key
client.Set("nest creds", clientID + ":" + refreshToken)
// after: store all three fields, colon-free
if strings.ContainsAny(clientSecret+refreshToken, ":") { /* re-encode or error */ }
client.Set("nest creds", clientID + ":" + clientSecret + ":" + refreshToken) Defensive patterns
Strategy: validation
Validate before calling
func validNestCacheKey(key string) bool {
parts := strings.Split(key, ":")
return len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" &&
!strings.ContainsAny(parts[1]+parts[2], ":")
}
if !validNestCacheKey(refreshKey) { return fmt.Errorf("malformed nest cache key") } Type guard
func isNestCacheKey(v any) (clientID, clientSecret, refreshToken string, ok bool) {
s, ok := v.(string)
if !ok { return "", "", "", false }
parts := strings.Split(s, ":")
if len(parts) != 3 { return "", "", "", false }
return parts[0], parts[1], parts[2], true
} Try / catch
creds, err := loadCachedCreds(userID)
if err != nil || strings.Count(creds, ":") != 2 {
// re-authenticate instead of calling ExchangeSDP
return reauthenticate(ctx, userID)
} Prevention
- Always store credentials as exactly clientID:clientSecret:refreshToken via one shared helper.
- Reject or re-encode secrets containing ':' before caching.
- Version the cache key (e.g. "v2:...") so format changes invalidate old entries cleanly.
- Test the parser round-trip (write then read) in CI.
When it happens
Trigger: ExchangeSDP calls refreshToken with a refreshKey whose strings.Split(key, ":") returns != 3 parts — e.g. the key was cached with only 2 fields, with extra colons, or as an empty/malformed string.
Common situations: A credential cache entry written by an older library version with a different key layout; a client secret or refresh token that itself contains a ':' corrupting the field count; hand-edited or truncated cache storage (Redis/file); restoring cache values from a different integration.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- loginResp.ErrorMsg
- tuya:
- milesone: authentication failed:
- wrong auth response
- hap: VerifyServerAuthenticator
AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07).
Data as JSON: /api/errors/25583f4aed5eafc0.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/nest/api.go:249
// Get the cached API with matching token to get credentials
var refreshKey string
cacheMu.Lock()
for key, api := range cache {
if api.Token == a.Token {
refreshKey = key
break
}
}
cacheMu.Unlock()
if refreshKey == "" {
return errors.New("nest: unable to find cached credentials")
}
// Parse credentials from cache key
parts := strings.Split(refreshKey, ":")
if len(parts) != 3 {
return errors.New("nest: invalid cache key format")
}
clientID, clientSecret, refreshToken := parts[0], parts[1], parts[2]
// Get new API instance which will refresh the token
newAPI, err := NewAPI(clientID, clientSecret, refreshToken)
if err != nil {
return err
}
// Update current API with new token
a.Token = newAPI.Token
a.ExpiresAt = newAPI.ExpiresAt
return nil
}
func (a *API) ExtendStream() error {
var reqv struct {
Command string `json:"command"`View on GitHub (pinned to c245815e75)