Describe the bug
AgentCoreMemorySessionManager.retrieve_customer_context (a MessageAddedEvent callback) indexes the first content block of the last message without checking that the content list is non-empty:
# src/bedrock_agentcore/memory/integrations/strands/session_manager.py
if not messages or messages[-1].get("role") != "user" or "text" not in messages[-1].get("content")[0]:
return None
When the last message has an empty content list ([]), messages[-1].get("content")[0] raises IndexError. As this runs inside a MessageAddedEvent hook, it propagates out of the event loop and aborts the agent invocation (a 500 on AgentCore Runtime). The guard covers not messages but not an empty content list.
The callback is registered for every MessageAddedEvent in sync mode regardless of retrieval_config, and the failing line runs before the retrieval_config check — so any attached AgentCoreMemorySessionManager is affected. It triggers when a user-role message with empty content is the most recent message. (An empty-content assistant message is safe: the role != "user" check short-circuits before the index access.)
To Reproduce
An empty-content user message is reachable through the public agent API: Strands' Agent._convert_prompt_to_messages appends a list[Message] prompt verbatim, so agent([{"role": "user", "content": []}]) fires a MessageAddedEvent whose messages[-1] is {"role": "user", "content": []}. (By contrast agent([]) and an empty list[ContentBlock] map to no message and are unaffected.)
Runnable end-to-end repro against a real strands.Agent (raises on main, clean with the suggested fix):
import asyncio
from unittest.mock import Mock, patch
from strands.agent.agent import Agent
from strands.hooks import MessageAddedEvent
from strands.models.model import Model
from strands.types.session import Session, SessionType
from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig, RetrievalConfig
from bedrock_agentcore.memory.integrations.strands.session_manager import AgentCoreMemorySessionManager
class FakeModel(Model): # the crash happens at MessageAddedEvent time, before the model is ever called
def get_config(self): return {}
def update_config(self, **k): pass
async def structured_output(self, *a, **k):
if False:
yield
async def stream(self, *a, **k):
yield {"messageStart": {"role": "assistant"}}
yield {"messageStop": {"stopReason": "end_turn"}}
def build_manager():
client = Mock()
client.retrieve_memories.return_value = []
cfg = AgentCoreMemoryConfig(
memory_id="m", session_id="s", actor_id="a",
retrieval_config={"prefs/{actorId}/": RetrievalConfig(top_k=5)},
)
with (
patch("bedrock_agentcore.memory.integrations.strands.session_manager.MemoryClient", return_value=client),
patch("boto3.Session"),
patch("strands.session.repository_session_manager.RepositorySessionManager.__init__", return_value=None),
):
m = AgentCoreMemorySessionManager(cfg)
m.session_id = "s"
m.session = Session(session_id="s", session_type=SessionType.AGENT)
return m
async def main():
manager = build_manager()
agent = Agent(model=FakeModel())
# exactly what register_hooks() wires up in sync mode (session_manager.py:941)
agent.hooks.add_callback(MessageAddedEvent, lambda e: manager.retrieve_customer_context(e))
await agent.invoke_async([{"role": "user", "content": []}]) # IndexError on main
asyncio.run(main())
Expected behavior
Return early when the last message has an empty content list, instead of raising.
Suggested fix
if not messages or messages[-1].get("role") != "user":
return None
content = messages[-1].get("content")
if not content or "text" not in content[0]:
return None
Environment
bedrock-agentcore 1.9.1 (line unchanged on main)
strands-agents 1.39.0
Describe the bug
AgentCoreMemorySessionManager.retrieve_customer_context(aMessageAddedEventcallback) indexes the first content block of the last message without checking that the content list is non-empty:When the last message has an empty
contentlist ([]),messages[-1].get("content")[0]raisesIndexError. As this runs inside aMessageAddedEventhook, it propagates out of the event loop and aborts the agent invocation (a500on AgentCore Runtime). The guard coversnot messagesbut not an emptycontentlist.The callback is registered for every
MessageAddedEventin sync mode regardless ofretrieval_config, and the failing line runs before theretrieval_configcheck — so any attachedAgentCoreMemorySessionManageris affected. It triggers when auser-role message with emptycontentis the most recent message. (An empty-contentassistantmessage is safe: therole != "user"check short-circuits before the index access.)To Reproduce
An empty-
contentuser message is reachable through the public agent API: Strands'Agent._convert_prompt_to_messagesappends alist[Message]prompt verbatim, soagent([{"role": "user", "content": []}])fires aMessageAddedEventwhosemessages[-1]is{"role": "user", "content": []}. (By contrastagent([])and an emptylist[ContentBlock]map to no message and are unaffected.)Runnable end-to-end repro against a real
strands.Agent(raises onmain, clean with the suggested fix):Expected behavior
Return early when the last message has an empty
contentlist, instead of raising.Suggested fix
Environment
bedrock-agentcore1.9.1 (line unchanged onmain)strands-agents1.39.0