#10280: fix(infra): add max size limit to Tailscale whois cache to prevent memory leak
stale
## Problem
The `whoisCache` in `src/infra/tailscale.ts` is a global `Map` that only has TTL-based expiration but no size limit:
```typescript
const whoisCache = new Map<string, TailscaleWhoisCacheEntry>();
```
If a gateway handles requests from many different IP addresses over time, the cache will grow unbounded, potentially causing memory issues in long-running processes.
## Comparison
The same codebase already implements proper bounded caching in `src/infra/dedupe.ts`:
```typescript
export function createDedupeCache(options: DedupeCacheOptions): DedupeCache {
const ttlMs = Math.max(0, options.ttlMs);
const maxSize = Math.max(0, Math.floor(options.maxSize)); // Has maxSize!
// ...
while (cache.size > maxSize) {
const oldestKey = cache.keys().next().value;
if (!oldestKey) break;
cache.delete(oldestKey);
}
}
```
## Solution
Add a `WHOIS_CACHE_MAX_SIZE` constant (default 1000) and enforce it in `writeCachedWhois`:
1. When cache reaches max size, first prune expired entries
2. If still over limit, evict oldest entries (LRU-style, leveraging Map insertion order)
3. Then insert the new entry
This ensures the cache stays bounded while maintaining the existing TTL behavior.
## Impact
- Prevents unbounded memory growth in long-running gateway processes
- Consistent with existing caching patterns in the codebase
- No behavioral change for normal usage (1000 entries is generous for typical deployments)
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
- Adds a fixed maximum size (`WHOIS_CACHE_MAX_SIZE = 1000`) to the global `whoisCache` in `src/infra/tailscale.ts`.
- On write, prunes expired entries and then evicts the oldest `Map` entries until under the limit.
- Intends to prevent unbounded memory growth in long-running gateway processes while keeping TTL-based caching semantics.
<h3>Confidence Score: 4/5</h3>
- This PR is likely safe to merge, but the current eviction behavior is mislabeled and may cause unexpected cache churn.
- Change is localized and prevents unbounded growth, but the implementation does FIFO/oldest-insert eviction rather than true LRU as documented in-code, which can lead to higher miss rates than expected under repeated access patterns.
- src/infra/tailscale.ts
<!-- greptile_other_comments_section -->
<sub>(5/5) You can turn off certain types of comments like style [here](https://app.greptile.com/review/github)!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#23744: fix(memory): add max size eviction to session manager cache
by kevinWangSheng · 2026-02-22
77.5%
#12997: feat(infra): Add query caching layer with TTL and LRU eviction
by trevorgordon981 · 2026-02-10
76.6%
#7592: fix(slack): prevent unbounded memory growth in channel type cache
by namratabhaumik · 2026-02-03
76.0%
#13127: perf(gateway): reduce idle CPU by increasing config and manifest ca...
by VintLin · 2026-02-10
73.3%
#10997: fix: enable cache-ttl pruning on first load after restart
by anotb · 2026-02-07
72.9%
#23639: fix(agents): stop re-resizing session history images on every turn ...
by yinghaosang · 2026-02-22
71.9%
#13877: perf: Comprehensive performance optimizations - caching, model rout...
by trevorgordon981 · 2026-02-11
71.9%
#23662: fix: cache sanitized images to avoid redundant re-processing per turn
by davidemanuelDEV · 2026-02-22
71.4%
#23706: perf: cache image resize results to avoid redundant processing (#23...
by echoVic · 2026-02-22
70.5%
#10636: fix: setTimeout integer overflow causing server crash
by devmangel · 2026-02-06
70.0%