{"record":{"id":"216fab218d210e5e","repo":"semaphoreui/semaphore","slug":"jwt-could-not-load-or-create-signing-key-w","errorCode":null,"errorMessage":"jwt: could not load or create signing key: %w","messagePattern":"jwt: could not load or create signing key: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/jwt.go","lineNumber":34,"sourceCode":"\tSetOption(key string, value string) error\n}\n\n// jwtSigningKeyOption is the database option key under which the AES-GCM\n// encrypted ECDSA P-256 private key PEM is stored.\nconst jwtSigningKeyOption = \"jwt_signing_key\"\n\n// InitJWTSignerFromStore initialises the global JWT signer.\n// It must be called once after the db.Store has been opened and after ConfigInit has run.\nfunc InitJWTSignerFromStore(store OptionStore) (singer jwt.Signer, err error) {\n\tif !Config.JWT.Enabled {\n\t\treturn\n\t}\n\n\topts := jwtSignerOptions()\n\n\tpemBytes, err := loadOrCreateJWTKey(store)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"jwt: could not load or create signing key: %w\", err)\n\t}\n\n\tsigner, err := jwt.NewECDSASignerFromPEM(pemBytes, opts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"jwt: failed to initialise signer: %w\", err)\n\t}\n\n\treturn signer, nil\n}\n\n// jwtSignerOptions builds SignerOptions from the current Config.\nfunc jwtSignerOptions() jwt.SignerOptions {\n\tttl := time.Hour\n\tif Config.JWT.DefaultTTL != \"\" {\n\t\tif parsed, err := time.ParseDuration(Config.JWT.DefaultTTL); err == nil {\n\t\t\tttl = parsed\n\t\t} else {\n\t\t\tfmt.Fprintf(os.Stderr, \"jwt: invalid jwt_default_ttl %q, falling back to 1h: %v\\n\", Config.JWT.DefaultTTL, err)","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/util/jwt.go#L16-L52","documentation":"Returned by InitJWTSignerFromStore in util/jwt.go when loadOrCreateJWTKey fails at any step — reading the jwt_signing_key option from the database, decrypting the stored PEM, generating a fresh key, or persisting a newly generated one. It is a wrapping guard around the whole key lifecycle: JWT signing is enabled in config, so the service cannot start without a usable signing key, and the %w carries the concrete underlying failure.","triggerScenarios":"Thrown at util/jwt.go:34 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Read the wrapped error to see which stage failed: option read (DB), decrypt (keyring), generate (crypto/rand), or persist (DB write)","Verify the configured encryption key is correct — a rotated or wrong key makes the stored jwt_signing_key undecryptable","If the stored key is unrecoverable, use the rekey/vault flow (RekeyJWTSigningKey) to install a fresh signing key","Check database connectivity/permissions if the option read or write itself failed"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}