Skip to content

retrieve_customer_context raises IndexError on empty message content #543

Description

@dtaniwaki

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions