temporalio/temporal · error

Cannot use Put API in Pin mode. Use Delete and PutIfNotExist

Error message

Cannot use Put API in Pin mode. Use Delete and PutIfNotExist if necessary

What it means

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.

Source

Thrown at common/cache/lru.go:220

	entry := element.Value.(*entryImpl)

	if c.isEntryExpired(entry, c.timeSource.Now().UTC()) {
		// Entry has expired
		c.deleteInternal(element)
		return nil
	}

	metrics.CacheEntryAgeOnGet.With(c.metricsHandler).Record(c.timeSource.Now().UTC().Sub(entry.createTime))

	c.updateEntryRefCount(entry)
	c.byAccess.MoveToFront(element)
	return entry.value
}

// Put puts a new value associated with a given key, returning the existing value (if present)
func (c *lru) Put(key any, value any) any {
	if c.pin {
		panic("Cannot use Put API in Pin mode. Use Delete and PutIfNotExist if necessary")
	}
	val, _ := c.putInternal(key, value, true)
	return val
}

// PutIfNotExist puts a value associated with a given key if it does not exist
func (c *lru) PutIfNotExist(key any, value any) (any, error) {
	existing, err := c.putInternal(key, value, false)
	if err != nil {
		return nil, err
	}

	if existing == nil {
		// This is a new value
		return value, err
	}

	return existing, err

View on GitHub (pinned to bde624efd1)

Solutions

  1. On a pinned cache, replace Put with PutIfNotExist (and Delete first if replacement is truly required)
  2. Remove Pin mode if the workload genuinely needs unconditional overwrite semantics
  3. Route all writes through a wrapper that branches on the cache's pin mode

Example fix

// before
if old := cache.Put(key, val); old != nil { ... }
// after (pinned cache)
cache.Delete(key)
if cache.PutIfNotExist(key, val) { ... }
Defensive patterns

Strategy: validation

Validate before calling

if cacheIsPinned {
    cache.Delete(key)
    cache.PutIfNotExist(key, val)
} else {
    cache.Put(key, val)
}

Type guard

func canPut(c *lru) bool { return !c.pin }

Prevention

When it happens

Trigger: Creating a cache with the pin option enabled (Cache(Options{Pin: true}) or equivalent) and then calling c.Put(key, value) directly.

Common situations: 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.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/8e4e2d30766d1a73. Report an issue: GitHub.