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

  1. Check the wrapped DB error — write permission on the options table or a connectivity blip at first boot are typical
  2. Retry startup; if a later attempt stores successfully the key becomes stable
  3. Verify the service's DB user has INSERT/UPDATE permission on the options table
  4. 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)