Repository navigation
inject_session_state raises KeyError on literal {identifier} patterns in instruction text #5706
Description
Description
inject_session_state() in instructions_utils.py applies template variable substitution using the regex r'{+[^{}]*}+' across the entire instruction string, including embedded tool descriptions, JSON schemas, and other content that the agent author did not intend as template variables. This causes KeyError crashes when instruction text contains literal {identifier} patterns — such as documentation describing interpolation syntax (e.g., ${expression}).
This has already caused a downstream bug in the A2UI project, where the formatString function description in basic_catalog.json contains ${expression} as documentation text. The ADK's regex matches it, expression passes str.isidentifier(), and the lookup against an empty session state raises:
KeyError: 'Context variable not found: `expression`.'
Current Behavior
inject_session_state()scans the full instruction string withr'{+[^{}]*}+'- Any match where the inner text passes
_is_valid_state_name()(i.e.,str.isidentifier()) is treated as a template variable - If that variable doesn't exist in session state, a
KeyErroris raised - There is no way to escape or opt out of this behavior for literal text that happens to match the pattern
Expected Behavior
Instruction text containing literal {identifier} patterns (especially in embedded tool descriptions, JSON schemas, or documentation) should not crash the agent. The template engine should either:
- Only substitute explicitly opted-in template variables — e.g., require a more specific delimiter like
{{var}}or${var}that is less likely to collide with documentation text - Gracefully handle missing variables — treat unresolved identifiers as literal text (return
match.group()as-is) instead of raisingKeyError, or at minimum log a warning. The?suffix opt-in for optional variables exists, but the default should arguably be lenient since instruction strings often contain content the author doesn't control. - Provide an escape mechanism — allow literal braces to be escaped (e.g.,
\{expression\}or{{expression}}passes through without substitution)
Reproduction
from google.adk.agents import Agent
# This instruction contains a literal {expression} in documentation text
agent = Agent(
model="gemini-2.0-flash",
name="test_agent",
instruction="The formatString supports interpolation via ${expression} syntax.",
)
# Running this agent via `adk run` will crash on the first message with:
# KeyError: 'Context variable not found: `expression`.'Impact
- A2UI integration is broken when using
adk run(see google/A2UI#1388). The A2UI team had to work around this by changing their spec documentation to use${<expression>}so it fails theisidentifier()check. - Any agent that embeds third-party tool descriptions, JSON schemas, or documentation examples into its instruction string is vulnerable to this class of crash.
- The existing A2A server samples work around it by pre-seeding
"expression": "{expression}"into session state — a fragile hack.
Relevant Code
src/google/adk/utils/instructions_utils.py Lines 85-128
async def _replace_match(match) -> str:
var_name = match.group().lstrip('{').rstrip('}').strip()
# ...
else:
if not _is_valid_state_name(var_name):
return match.group() # ← safe: invalid identifiers pass through
if var_name in invocation_context.session.state:
value = invocation_context.session.state[var_name]
# ...
else:
# ...
raise KeyError(f'Context variable not found: `{var_name}`.') # ← crash
return await _async_sub(r'{+[^{}]*}+', _replace_match, template)Related Issues
- google/A2UI#1388 — downstream crash
- google/A2UI#1409 — downstream workaround PR
- Template variable escaping with double braces doesn't work as documented #3527 — Template variable escaping with double braces (closed)
- [Regression v1.25.1] Template variable escaping with double braces broken #4606 — Regression in template variable escaping (closed)
Suggested Fix
The least disruptive fix would be to change the default behavior for unresolved-but-valid identifiers from raising KeyError to returning the match as-is (same as what happens for invalid identifiers), and log a debug warning. This makes the template engine lenient by default while preserving the ? suffix mechanism for authors who explicitly want empty-string fallback behavior.
else:
- if optional:
- logger.debug(
- 'Context variable %s not found, replacing with empty string',
- var_name,
- )
- return ''
- else:
- raise KeyError(f'Context variable not found: `{var_name}`.')
+ if optional:
+ logger.debug(
+ 'Context variable %s not found, replacing with empty string',
+ var_name,
+ )
+ return ''
+ else:
+ logger.debug(
+ 'Context variable %s not found in session state, returning as-is',
+ var_name,
+ )
+ return match.group()
Activity
klateefa commentedon May 15, 2026
@brucearctor Thanks for picking this up and opening a PR for it. The proposed approach of returning the original match instead of raising a
KeyErrormakes sense here, since instruction strings can legitimately contain literal patterns like${expression}in docs, schemas, or tool descriptions. It’s a minimal and backward-compatible fix that prevents unexpected crashes while preserving existing substitution behavior. Please try this approach and let me know if the issue still persists.surajksharma07 commentedon May 27, 2026
@brucearctor — just following up here. The root cause is confirmed: instructions_utils.py raises KeyError at line 122 for any valid identifier not found in session state rather than returning the match as-is the way invalid identifiers already do — so a literal ${expression} in documentation text hits this and crashes.
PR #5711 by @zhoufengen targets exactly the fix you proposed (returning match.group() instead of raising), though it hasn't merged yet.
Since we haven't heard back since klateefa's comment two weeks ago, going ahead and closing this out — feel free to reopen or track the fix via PR #5711!
fix: do not match dollar-brace or escaped patterns in instructions
fix(core): keep ${...} and escaped braces in instructions as literal …