{"record":{"id":"2e340b4e91b7207c","repo":"temporalio/temporal","slug":"membershipexpiry-duration-should-be-atleast-1-seco","errorCode":null,"errorMessage":"membershipExpiry duration should be atleast 1 second","messagePattern":"membershipExpiry duration should be atleast 1 second","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/persistence/cluster_metadata_store.go","lineNumber":15,"sourceCode":"package persistence\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"go.temporal.io/api/serviceerror\"\n\tpersistencespb \"go.temporal.io/server/api/persistence/v1\"\n\t\"go.temporal.io/server/common/log\"\n\t\"go.temporal.io/server/common/persistence/serialization\"\n)\n\nvar (\n\t// ErrInvalidMembershipExpiry is used when upserting new cluster membership with an invalid duration\n\tErrInvalidMembershipExpiry = errors.New(\"membershipExpiry duration should be atleast 1 second\")\n\n\t// ErrIncompleteMembershipUpsert is used when upserting new cluster membership with missing fields\n\tErrIncompleteMembershipUpsert = errors.New(\"membership upserts require all fields\")\n)\n\ntype (\n\t// clusterMetadataManagerImpl implements MetadataManager based on MetadataStore and Serializer\n\tclusterMetadataManagerImpl struct {\n\t\tserializer         serialization.Serializer\n\t\tpersistence        ClusterMetadataStore\n\t\tcurrentClusterName string\n\t\tlogger             log.Logger\n\t}\n)\n\nvar _ ClusterMetadataManager = (*clusterMetadataManagerImpl)(nil)\n\n// NewClusterMetadataManagerImpl returns new ClusterMetadataManager","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/common/persistence/cluster_metadata_store.go#L1-L33","documentation":"ErrInvalidMembershipExpiry is an exported sentinel in common/persistence/cluster_metadata_store.go, returned by UpsertClusterMembership when the membershipExpiry duration is less than one second. Cluster membership records carry an expiry used for liveness/pruning; durations below one second would cause records to be immediately considered stale.","triggerScenarios":"Calling UpsertClusterMembership (via the cluster metadata manager/store) with RecordExpiry (membershipExpiry) set to 0 or any duration < 1s, as covered by TestClusterMembershipUpsertInvalidExpiry.","commonSituations":"Misconfigured membership heartbeat/expiry settings where the value was left at its zero value; unit confusion (configuring milliseconds where seconds are expected); skipping validation on a code path that builds membership upsert requests dynamically.","solutions":["Set membershipExpiry/RecordExpiry to at least 1 * time.Second in the caller","Fix the dynamic config or static config value feeding the expiry","Compare with errors.Is(err, persistence.ErrInvalidMembershipExpiry) and apply a sane default when it is returned"],"exampleFix":"// before\nreq.RecordExpiry = cfg.MembershipExpirySeconds // 0 when unset\n// after\nexpiry := cfg.MembershipExpirySeconds\nif expiry < time.Second {\n    expiry = time.Second\n}\nreq.RecordExpiry = expiry","handlingStrategy":"validation","validationCode":"func validMembershipExpiry(d time.Duration) bool { return d >= time.Second }","typeGuard":"if req.RecordExpiry < time.Second { return persistence.ErrInvalidMembershipExpiry }","tryCatchPattern":"_, err := manager.UpsertClusterMembership(ctx, req)\nif errors.Is(err, persistence.ErrInvalidMembershipExpiry) {\n    return fmt.Errorf(\"membership expiry must be >= 1s, got %v\", req.RecordExpiry)\n}","preventionTips":["Centralize a default/minimum expiry constant and clamp configured values","Watch for millisecond-vs-second unit confusion in config keys","Add a unit test asserting upserts with sub-second expiry are rejected"],"tags":["persistence","cluster-membership","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}