Repository navigation
Context cache creation fails with 'contents are required' when cacheable prefix is empty (cache_contents_count == 0) #6363
Description
🔴 Required Information
Describe the Bug:
When context caching is enabled for an agent with a large, stable system_instruction and tools, GeminiContextCacheManager gets stuck trying to create a cache with an empty contents list.
From the second turn onward, every request logs:
Failed to create cache: contents are required.
As a result, no cache is ever created, and every turn performs a failed caches.create() call.
The issue is that the cacheable prefix can legitimately be 0 on the first turn (when only trailing user messages are present). That 0 is then reused when creating the cache, resulting in:
contents = llm_request.contents[:0] # []The GenAI SDK treats an empty list differently from None: contents=[] raises ValueError("contents are required."), while contents=None is valid when system_instruction and tools are provided.
Passing None instead of an empty list would allow the cache to be created successfully.
Steps to Reproduce:
- Install
google-adk==2.4.0. - Build an
Appwithcontext_cache_config=ContextCacheConfig()and a rootLlmAgentwhosesystem_instruction+ tools comfortably exceed the 4096-token minimum and don't change between turns. - Run a normal multi-turn session (turn 1 = a single user message).
- From turn 2 onward, observe the warning below on every turn;
cache_metadata.cache_namestaysNone.
Expected Behavior:
When the cacheable content prefix is empty (cache_contents_count == 0), the stable system_instruction + tools should still be cached (or creation cleanly skipped). The manager must not send contents=[] and then permanently skip caching.
Observed Behavior:
Cache creation fails on every turn and is never established:
WARNING [google_adk.google.adk.models.gemini_context_cache_manager] [gemini_context_cache_manager.py:352] - Failed to create cache: contents are required.
Environment Details:
- ADK Library Version (
pip show google-adk): 2.4.0 - Desktop OS: macOS (also reproduces on Linux / Cloud Run)
- Python Version (
python -V): 3.12
Model Information:
- Are you using LiteLLM: No
- Which model is being used: gemini-3.1-flash-lite (Vertex AI)
🟡 Optional Information
Regression:
Yes. Worked in 2.3.0. The contents_count = 0 seed was introduced by commit 7c7f1e7 (PR #6067, "fix: fingerprint cacheable context prefix", Fixes #6062), which changed the fingerprint-only seed from len(llm_request.contents) (always ≥ 1) to _find_count_of_contents_to_cache(...) (returns 0 for a single trailing user batch). The prefix-token size gate added in #6137 (Fixes #5847) does not catch the empty prefix because it still counts system_instruction + tools.
Logs:
WARNING [google_adk.google.adk.models.gemini_context_cache_manager] [gemini_context_cache_manager.py:352] - Failed to create cache: contents are required. [genAiConversationId= gcpVertexAgentInvocationId= gcpVertexAgentEventId=]
How often has this issue occurred?:
- Always (100%) — for agents with a large, stable system instruction + tools.
Activity
hxaxd commentedon Jul 10, 2026
Thanks for the detailed report. I checked the current code and history, and the diagnosis looks correct.
PR #6067 had a reasonable goal: exclude request-scoped trailing user contents from the stable cache fingerprint. A zero-length content prefix is therefore valid; the regression is at the SDK boundary, where that zero becomes
contents=[]instead ofNone.The existing zero-prefix tests mock
caches.create()but do not inspectconfig.contents, so the invalid empty list is accepted by the mock.I’d like to submit a focused fix that passes
Nonefor an empty prefix and adds a regression test for the zero-prefix case. Would it be okay for me to take this?pp-azhoraven commentedon Jul 10, 2026
Hey @hxaxd , I think this proposal only fixes the issue partially. It removes the warning, but the cache still won't be created.
cache_contents_countshould be derived from_find_count_of_contents_to_cache(llm_request.contents)rather thanold_cache_metadata.contents_count(which is a stale 0). Additionally, the failure path should re-reserve using the current count so that it converges over time instead of getting permanently stuck at 0hxaxd commentedon Jul 10, 2026
Hey @pp-azhoraven, thanks for taking a look. I’d like to clarify two misunderstandings here.
First,
old_cache_metadata.contents_countis not a stale live count. It is the prefix length paired with the stored fingerprint, so ADK must reuse it to compare the same cache identity. Recomputing it on every request would turn the prefix into a moving target—roughly 0, 2, 4, 6 in a normal conversation. Each new fingerprint would then fail to match the previous one, and ADK would keep reseeding fingerprint-only metadata without reachingcaches.create().Second,
contents=Nonedoes not mean no cache is created. It only omits conversation contents; the system instruction and tools are still sent tocaches.create(). Once that call succeeds, ADK receives and immediately applies acache_name. Preserving the original count after a transient failure is also intentional: it lets the next request retry the same cache identity. Re-seeding after an operational failure would at best add another fingerprint-only round and, combined with a continuously recalculated count, could prevent cache creation indefinitely.So the issue is the
[]versusNoneSDK boundary, not a stale prefix count.pp-azhoraven commentedon Jul 10, 2026
@hxaxd , thank you for clarification! Then your fix is reasonable
fix(models): Allow context caches with empty content prefixes
fix(models): Allow context caches with empty content prefixes