Skip to content

Conversation

@colinbennettbrain
Copy link

Summary

  • Fixes cross-org prompt cache collision bug (Pylon case #10589)
  • Adds org_id to cache key format for proper organization isolation
  • Adds tests for cross-org cache behavior

Problem

The prompt cache was using a key format of {project_name}:{slug}:{version} which did not include the organization ID. This caused a cross-org cache collision bug where two organizations with the same project name and prompt slug could retrieve each other's cached prompts.

This was reported by HubSpot where a UI-based Python scorer returned a "legal-related prompt" instead of their SDC coverage prompt because the disk cache (~/.braintrust/prompt_cache) returned a prompt from a different org that had been previously cached on the same scorer runtime.

Root Cause

In prompt_cache.py:17-34, the _create_cache_key() function was:

def _create_cache_key(project_id, project_name, slug, version="latest", id=None):
    if id:
        return f"id:{id}"
    prefix = project_id or project_name
    return f"{prefix}:{slug}:{version}"  # No org_id!

When project_name is used (common in scorers), two different organizations with the same project name and prompt slug would have the same cache key, causing cache collisions.

Solution

  • Add org_id parameter to _create_cache_key()
  • Update cache key format to {org_id}:{prefix}:{slug}:{version} when org_id is available
  • Thread org_id through PromptCache.get() and PromptCache.set()
  • Pass _state.org_id from load_prompt() to cache operations

Test plan

  • Added test_handle_different_orgs_with_same_project_and_slug - verifies prompts from different orgs with same project/slug are isolated
  • Added test_org_id_isolation_with_disk_cache - verifies isolation works after memory eviction (via disk cache)
  • All 15 prompt cache tests pass

Backward Compatibility

The fix is backward compatible:

  • Existing cached prompts without org_id in the key will simply miss and trigger a fresh API fetch
  • The org_id parameter is optional and defaults to None
  • No breaking changes to the public API

🤖 Generated with Claude Code

The prompt cache was using a key format of `{project_name}:{slug}:{version}`
which did not include the organization ID. This caused a cross-org cache
collision bug where two organizations with the same project name and prompt
slug could retrieve each other's cached prompts.

This was reported by HubSpot (Pylon case #10589) where a UI-based Python
scorer returned a "legal-related prompt" instead of their SDC coverage
prompt because the disk cache returned a prompt from a different org.

Changes:
- Add `org_id` parameter to `_create_cache_key()` function
- Update cache key format to `{org_id}:{prefix}:{slug}:{version}` when
  org_id is available
- Add `org_id` parameter to `PromptCache.get()` and `PromptCache.set()`
- Pass `_state.org_id` from `load_prompt()` to cache operations
- Add tests for cross-org cache isolation

The fix is backward compatible - existing cached prompts without org_id
in the key will simply miss and trigger a fresh API fetch.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@colinbennettbrain
Copy link
Author

Hey Olmo, this came up because a customer realized the wrong prompt was getting sent to OpenAI. I am attempting to fix the issue with this. I also have a repro script if that's helpful.



def _create_cache_key(
org_id: str | None,
Copy link
Collaborator

Choose a reason for hiding this comment

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

if org_id is required to prevent cross org issues, it should be required (e.g. not none)

"""Creates a unique cache key from project identifier, slug and version, or from ID."""
"""Creates a unique cache key from org ID, project identifier, slug and version, or from prompt ID.
The org_id is included to ensure cache isolation between organizations. Without it,
Copy link
Collaborator

Choose a reason for hiding this comment

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

i think the org_id can always be in the cache key

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.

4 participants