{"record":{"id":"46c4236f998d3b3e","repo":"benbjohnson/litestream","slug":"lease-not-held","errorCode":null,"errorMessage":"lease not held","messagePattern":"lease not held","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"leaser.go","lineNumber":10,"sourceCode":"package litestream\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n)\n\nvar ErrLeaseNotHeld = errors.New(\"lease not held\")\n\ntype LeaseExistsError struct {\n\tOwner     string\n\tExpiresAt time.Time\n}\n\nfunc (e *LeaseExistsError) Error() string {\n\tif e.Owner != \"\" {\n\t\treturn fmt.Sprintf(\"lease already held by %s until %s\", e.Owner, e.ExpiresAt.Format(time.RFC3339))\n\t}\n\treturn fmt.Sprintf(\"lease already held until %s\", e.ExpiresAt.Format(time.RFC3339))\n}\n\ntype Leaser interface {\n\tType() string\n\tAcquireLease(ctx context.Context) (*Lease, error)\n\tRenewLease(ctx context.Context, lease *Lease) (*Lease, error)\n\tReleaseLease(ctx context.Context, lease *Lease) error","sourceCodeStart":1,"sourceCodeEnd":28,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/leaser.go#L1-L28","documentation":"ErrLeaseNotHeld signals that a distributed replication lease could not be renewed or released because this instance no longer holds it. In s3/leaser.go it is returned when a conditional (ETag/precondition) write or delete fails — proof that another node took over the lease or it expired.","triggerScenarios":"RenewLease when the stored ETag no longer matches (another owner acquired the lease), ReleaseLease when isPreconditionFailed on the delete, and s3 acquisition when a LeaseExistsError indicates an already-leased (or expired-lease race) condition that is normalized to ErrLeaseNotHeld.","commonSituations":"Two litestream replicas pointing at the same S3 bucket where failover occurred and the old primary tries to renew; clock skew causing lease expiry; leftover lease objects from a crashed instance with a stale ETag.","solutions":["Stop the old replica instance — it lost the lease and must not write","Check for a second litestream process replicating to the same bucket and remove it","Wait for lease expiry if the previous owner crashed, then retry acquisition","Verify system clocks (NTP) on all replicas to avoid premature expiry"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// before starting a replica, confirm no other instance is replicating:\n// check lease object existence/owner via `litestream ltx` or bucket listing","typeGuard":null,"tryCatchPattern":"if err := db.SyncAndWait(ctx); err != nil {\n    if errors.Is(err, litestream.ErrLeaseNotHeld) {\n        // lost the lease: another node took over; stop this replica\n        slog.Warn(\"lease lost, shutting down replication\")\n        return\n    }\n    return err\n}","preventionTips":["Run NTP on all hosts to prevent premature lease expiry","Ensure only one replica instance targets the same bucket per DB","After failover, terminate the old primary before it retries writes"],"tags":["lease","s3","distributed","replication"],"backgroundTag":"invalid-state-transition","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}