{"record":{"id":"8e4e2d30766d1a73","repo":"temporalio/temporal","slug":"cannot-use-put-api-in-pin-mode-use-delete-and-put","errorCode":null,"errorMessage":"Cannot use Put API in Pin mode. Use Delete and PutIfNotExist if necessary","messagePattern":"Cannot use Put API in Pin mode\\. Use Delete and PutIfNotExist if necessary","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/cache/lru.go","lineNumber":220,"sourceCode":"\tentry := element.Value.(*entryImpl)\n\n\tif c.isEntryExpired(entry, c.timeSource.Now().UTC()) {\n\t\t// Entry has expired\n\t\tc.deleteInternal(element)\n\t\treturn nil\n\t}\n\n\tmetrics.CacheEntryAgeOnGet.With(c.metricsHandler).Record(c.timeSource.Now().UTC().Sub(entry.createTime))\n\n\tc.updateEntryRefCount(entry)\n\tc.byAccess.MoveToFront(element)\n\treturn entry.value\n}\n\n// Put puts a new value associated with a given key, returning the existing value (if present)\nfunc (c *lru) Put(key any, value any) any {\n\tif c.pin {\n\t\tpanic(\"Cannot use Put API in Pin mode. Use Delete and PutIfNotExist if necessary\")\n\t}\n\tval, _ := c.putInternal(key, value, true)\n\treturn val\n}\n\n// PutIfNotExist puts a value associated with a given key if it does not exist\nfunc (c *lru) PutIfNotExist(key any, value any) (any, error) {\n\texisting, err := c.putInternal(key, value, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif existing == nil {\n\t\t// This is a new value\n\t\treturn value, err\n\t}\n\n\treturn existing, err","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/common/cache/lru.go#L202-L238","documentation":"common/cache/lru.go's Put panics when the cache is created in Pin mode. A pinned cache has a fixed working set (entries are effectively locked in), so unconditional Put would violate pinning semantics; the API instead directs users to Delete followed by PutIfNotExist. The panic enforces the restricted API surface for pinned caches.","triggerScenarios":"Creating a cache with the pin option enabled (Cache(Options{Pin: true}) or equivalent) and then calling c.Put(key, value) directly.","commonSituations":"Cache size set equal to expected working set with Pin to prevent eviction, then generic cache-writing code that uses Put; refactored code sharing a Put helper between pinned and non-pinned caches; migration from a non-pinned to a pinned cache without updating call sites.","solutions":["On a pinned cache, replace Put with PutIfNotExist (and Delete first if replacement is truly required)","Remove Pin mode if the workload genuinely needs unconditional overwrite semantics","Route all writes through a wrapper that branches on the cache's pin mode"],"exampleFix":"// before\nif old := cache.Put(key, val); old != nil { ... }\n// after (pinned cache)\ncache.Delete(key)\nif cache.PutIfNotExist(key, val) { ... }\n","handlingStrategy":"validation","validationCode":"if cacheIsPinned {\n    cache.Delete(key)\n    cache.PutIfNotExist(key, val)\n} else {\n    cache.Put(key, val)\n}","typeGuard":"func canPut(c *lru) bool { return !c.pin }","tryCatchPattern":null,"preventionTips":["Branch write logic on the cache's pin mode","Prefer PutIfNotExist for read-through caching patterns","Document pinned caches at creation sites so callers know Put is unavailable"],"tags":["cache","lru","pin-mode","panic","misuse-guard"],"backgroundTag":"put-forbidden-in-pin-mode","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}