Skip to content

fix: isolate combo cache by endpoint and credential#40

Open
RaviTharuma wants to merge 2 commits into
Alph4d0g:mainfrom
RaviTharuma:fix/combo-cache-isolation
Open

fix: isolate combo cache by endpoint and credential#40
RaviTharuma wants to merge 2 commits into
Alph4d0g:mainfrom
RaviTharuma:fix/combo-cache-isolation

Conversation

@RaviTharuma

@RaviTharuma RaviTharuma commented Jul 20, 2026

Copy link
Copy Markdown

Summary

  • replace the singleton combo cache with entries isolated by normalized combo endpoint and a SHA-256 credential digest
  • avoid storing or logging raw credentials in cache identities
  • preserve isComboModel() fallback behavior across all keyed caches
  • handle missing endpoint/credential without throwing
  • add regression coverage for endpoint and credential isolation

Root cause

fetchComboData() previously reused one process-global cache entry based only on age. A second OmniRoute endpoint or credential could therefore receive the first configuration's combo definitions for up to five minutes.

Verification

  • regression tests fail against the previous singleton implementation
  • npm test: 69 passed, 0 failed
  • TypeScript build included in npm test
  • git diff --check

Closes #39

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the in-memory combo cache to isolate entries by endpoint and a non-reversible credential digest (SHA-256) using a Map, and adds corresponding integration tests. The review feedback suggests adding defensive guards to handle missing configuration properties (baseUrl or apiKey) gracefully, and optimizing the fallback check in isComboModel by iterating directly over the cache values instead of allocating a new array via the spread operator.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/omniroute-combos.ts

// Check cache first
if (comboCache && Date.now() - comboCache.timestamp < COMBO_CACHE_TTL) {
const cacheKey = getComboCacheKey(baseUrl, apiKey);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If baseUrl or apiKey is missing or undefined in the configuration, calling getComboCacheKey will throw a synchronous TypeError (e.g., when calling .replace on undefined or passing a non-string to createHash().update()). Adding a defensive guard here ensures that we handle missing configuration gracefully by logging a warning and returning null instead of crashing the process.

if (!baseUrl || !apiKey) {
  warn('Missing baseUrl or apiKey in config');
  return null;
}

const cacheKey = getComboCacheKey(baseUrl, apiKey);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the latest push: fetchComboData() now returns null before deriving a cache key when either value is empty, with regression coverage that proves no fetch occurs.

Comment thread src/omniroute-combos.ts Outdated

return false;
// Fallback: check all endpoint/credential-specific combo caches.
return [...comboCaches.values()].some((cached) => cached.combos.has(model.id));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using the spread operator [...comboCaches.values()] creates a new array allocation on every call to isComboModel. Since this function can be called frequently (e.g., when filtering or enriching lists of models), we can optimize this by iterating directly over the iterator using a for...of loop, avoiding unnecessary memory allocation and improving performance.

for (const cached of comboCaches.values()) {
  if (cached.combos.has(model.id)) {
    return true;
  }
}
return false;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed: isComboModel() now iterates comboCaches.values() directly without allocating an array.

@kilo-code-bot

kilo-code-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Code Review Roast 🔥

Verdict: No Issues Found | Recommendation: Merge

Oh wait, this PR is actually clean. I need to sit down. I had my flamethrower warmed up and everything.

📊 Overall: Like finding a unicorn in production — I didn't think clean PRs existed anymore, but here we are.

Files Reviewed (3 files)
  • src/omniroute-combos.ts
  • src/models.ts
  • test/omniroute-combos.test.mjs
Previous Review Summaries (2 snapshots, latest commit 5f9be7a)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 5f9be7a)

Verdict: 2 Suggestions Found | Recommendation: Address before merge

Overview

Severity Count
🚨 critical 0
⚠️ warning 0
💡 suggestion 2
🤏 nitpick 0
Issue Details (click to expand)
File Line Roast
src/omniroute-combos.ts 52 comboCaches Map grows unboundedly — stale entries are never evicted, turning a TTL cache into a memory leak in long-running processes
src/omniroute-combos.ts 139 clearComboCache() unconditionally wipes ALL combo caches, conflicting with the per-config granularity of clearModelCache(config, apiKey)

🏆 Best part: The credential hashing and cache-key isolation are actually well-designed — SHA-256 digests for cache identities is a clean way to avoid credential leakage.

💀 Worst part: The unbounded comboCaches Map is the most concerning issue — it's a slow memory leak disguised as a cache.

📊 Overall: Like a first pancake — the shape is right and the ingredients are good, but the edges are a bit ragged. Close, but needs a trim.

Files Reviewed (2 files)
  • src/omniroute-combos.ts - 2 suggestions
  • test/omniroute-combos.test.mjs - No issues found

Fix these issues in Kilo Cloud


Model used for review: kilo-auto/free

Previous review (commit 9bffc65)

Verdict: No Issues Found | Recommendation: Merge

Oh wait, this PR is actually clean. I need to sit down. I had my flamethrower warmed up and everything.

📊 Overall: Like finding a unicorn in production — I didn't think clean PRs existed anymore, but here we are.

Files Reviewed (2 files)
  • src/omniroute-combos.ts - Cache isolation by endpoint and credential digest
  • test/omniroute-combos.test.mjs - Regression coverage for isolation

Model used for review: kilo-auto/free

Fix these issues in Kilo Cloud


Reviewed by step-3.7-flash · Input: 193.6K · Output: 16K · Cached: 1.1M

@RaviTharuma
RaviTharuma force-pushed the fix/combo-cache-isolation branch from 9bffc65 to 5f9be7a Compare July 20, 2026 12:34
Comment thread src/omniroute-combos.ts
// In-memory cache for combo data
let comboCache: ComboCache | null = null;
// Cache entries are isolated by endpoint and a non-reversible credential digest.
const comboCaches = new Map<string, ComboCache>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: This Map is a hoarder's paradise — it accepts every endpoint/credential pair it ever meets and never throws anything away. The old comboCache was a single variable that got overwritten on every fetch, naturally bounding memory. Now comboCaches grows indefinitely until clearComboCache() is called. In a long-running process or server that rotates credentials, you've just scheduled a memory leak.

🩹 The Fix: Evict stale entries proactively. On a cache miss, prune any entries older than COMBO_CACHE_TTL before inserting the fresh one. This keeps the Map bounded without relying on explicit cleanup calls.

📏 Severity: suggestion


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the latest push.

fetchComboData() now calls pruneExpiredComboCaches() before inserting a fresh entry, so identities older than COMBO_CACHE_TTL are removed proactively. This keeps the multi-entry map bounded without waiting for an explicit clear.

Regression: expired combo cache entries are pruned on write.

Comment thread src/omniroute-combos.ts
@@ -122,7 +138,7 @@ export async function fetchComboData(
* Clear the combo cache
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: clearComboCache() is still living in the singleton era. It unconditionally wipes ALL endpoint/credential caches, which means callers like clearModelCache(config, apiKey) in src/models.ts:386 — designed to clear a single config's data — also nukes every other endpoint's combo cache. That's emptying the entire office fridge because your yogurt expired.

🩹 The Fix: Either accept an optional cache key parameter for selective deletion, or update clearModelCache to only clear the matching combo cache entry when specific config and apiKey are provided. Match the granularity to the caller's intent.

📏 Severity: suggestion


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the latest push.

clearComboCache(config?) is now selective:

  • with baseUrl + apiKey → delete only that endpoint/credential identity
  • without config → clear the full map (legacy behavior)

clearModelCache(config, apiKey) routes through the selective form so a single-config model-cache clear no longer wipes every combo identity.

Regression: clearComboCache can target one endpoint/credential pair.

Address review feedback on the multi-entry combo cache:
- prune expired endpoint/credential entries before cache writes
- allow clearComboCache(config) to target one identity
- keep full clear when no config is provided
- route clearModelCache through the selective combo clear
- add regression coverage for selective clear and TTL prune
@RaviTharuma

Copy link
Copy Markdown
Author

Review follow-up

Pushed an additional commit addressing the remaining review suggestions on the multi-entry combo cache:

  1. TTL prune on write — expired endpoint/credential entries are removed before a new cache insert.
  2. Selective clearclearComboCache(config?) can target one identity; full clear remains available.
  3. clearModelCache alignment — passes the matching config into the selective combo clear.
  4. Regression coverage for selective clear and TTL prune.

Focused combo tests: all passing. Pre-existing flaky logger alphabetical-mtime test is unrelated to this change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: isolate combo cache by endpoint and credential

1 participant