semaphoreui/semaphore · error
persist signing key
Error message
persist signing key: %w
What it means
Returned by loadOrCreateJWTKey in util/jwt.go at first-run initialisation: a fresh ECDSA P-256 key was generated and encrypted, but store.SetOption failed to persist it under the jwt_signing_key option. The %w wraps the database write error; the in-memory key is usable for this run, but without persistence the next start generates a different key and all tokens signed now become invalid.
Solutions
- Check the wrapped DB error — write permission on the options table or a connectivity blip at first boot are typical
- Retry startup; if a later attempt stores successfully the key becomes stable
- Verify the service's DB user has INSERT/UPDATE permission on the options table
- If a different key was generated on a subsequent start, expect previously issued JWTs to fail verification and re-issue them
Defensive patterns
Strategy: retry
When it happens
Trigger: Thrown at util/jwt.go:98 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/8928a5db8e74acb3.
Report an issue: GitHub.
Appendix: source
Thrown at util/jwt.go:98
}
if stored != "" {
return decryptJWTKey(stored)
}
// No key in DB yet
pemBytes, err := jwt.GenerateKeyPEM()
if err != nil {
return nil, err
}
encrypted, err := encryptJWTKey(pemBytes)
if err != nil {
return nil, err
}
if err := store.SetOption(jwtSigningKeyOption, encrypted); err != nil {
return nil, fmt.Errorf("persist signing key: %w", err)
}
return pemBytes, nil
}
// CheckJWTSigningKey classifies the stored JWT signing key for `vault check`:
// "" (none), "active:<id>", "rekey pending:<id>", "legacy (no id)", or
// "MISSING KEY <id>". Read-only.
func CheckJWTSigningKey(store OptionStore) (slot string, err error) {
stored, err := store.GetOption(jwtSigningKeyOption)
if err != nil {
return "", fmt.Errorf("read jwt signing key option: %w", err)
}
if stored == "" {
return "", nil
}
return Config.classifyOptionSecret(stored), nil
}View on GitHub (pinned to 1774ccb71a)