@@ -38,15 +38,88 @@ async def test_agent_definition():
3838 async for message in client .receive_response ():
3939 if isinstance (message , SystemMessage ) and message .subtype == "init" :
4040 agents = message .data .get ("agents" , [])
41- assert isinstance (
42- agents , list
43- ), f"agents should be a list of strings, got: { type ( agents ) } "
44- assert (
45- "test-agent" in agents
46- ), f"test-agent should be available, got: { agents } "
41+ assert isinstance (agents , list ), (
42+ f" agents should be a list of strings, got: { type ( agents ) } "
43+ )
44+ assert "test-agent" in agents , (
45+ f "test-agent should be available, got: { agents } "
46+ )
4747 break
4848
4949
50+ @pytest .mark .e2e
51+ @pytest .mark .asyncio
52+ async def test_filesystem_agent_loading ():
53+ """Test that filesystem-based agents load via setting_sources and produce full response.
54+
55+ This is the core test for issue #406. It verifies that when using
56+ setting_sources=["project"] with a .claude/agents/ directory containing
57+ agent definitions, the SDK:
58+ 1. Loads the agents (they appear in init message)
59+ 2. Produces a full response with AssistantMessage
60+ 3. Completes with a ResultMessage
61+
62+ The bug in #406 causes the iterator to complete after only the
63+ init SystemMessage, never yielding AssistantMessage or ResultMessage.
64+ """
65+ with tempfile .TemporaryDirectory () as tmpdir :
66+ # Create a temporary project with a filesystem agent
67+ project_dir = Path (tmpdir )
68+ agents_dir = project_dir / ".claude" / "agents"
69+ agents_dir .mkdir (parents = True )
70+
71+ # Create a test agent file
72+ agent_file = agents_dir / "fs-test-agent.md"
73+ agent_file .write_text (
74+ """---
75+ name: fs-test-agent
76+ description: A filesystem test agent for SDK testing
77+ tools: Read
78+ ---
79+
80+ # Filesystem Test Agent
81+
82+ You are a simple test agent. When asked a question, provide a brief, helpful answer.
83+ """
84+ )
85+
86+ options = ClaudeAgentOptions (
87+ setting_sources = ["project" ],
88+ cwd = project_dir ,
89+ max_turns = 1 ,
90+ )
91+
92+ messages = []
93+ async with ClaudeSDKClient (options = options ) as client :
94+ await client .query ("Say hello in exactly 3 words" )
95+ async for msg in client .receive_response ():
96+ messages .append (msg )
97+
98+ # Must have at least init, assistant, result
99+ message_types = [type (m ).__name__ for m in messages ]
100+
101+ assert "SystemMessage" in message_types , "Missing SystemMessage (init)"
102+ assert "AssistantMessage" in message_types , (
103+ f"Missing AssistantMessage - got only: { message_types } . "
104+ "This may indicate issue #406 (silent failure with filesystem agents)."
105+ )
106+ assert "ResultMessage" in message_types , "Missing ResultMessage"
107+
108+ # Find the init message and check for the filesystem agent
109+ for msg in messages :
110+ if isinstance (msg , SystemMessage ) and msg .subtype == "init" :
111+ agents = msg .data .get ("agents" , [])
112+ # Agents are returned as strings (just names)
113+ assert "fs-test-agent" in agents , (
114+ f"fs-test-agent not loaded from filesystem. Found: { agents } "
115+ )
116+ break
117+
118+ # On Windows, wait for file handles to be released before cleanup
119+ if sys .platform == "win32" :
120+ await asyncio .sleep (0.5 )
121+
122+
50123@pytest .mark .e2e
51124@pytest .mark .asyncio
52125async def test_setting_sources_default ():
@@ -74,12 +147,12 @@ async def test_setting_sources_default():
74147 async for message in client .receive_response ():
75148 if isinstance (message , SystemMessage ) and message .subtype == "init" :
76149 output_style = message .data .get ("output_style" )
77- assert (
78- output_style != " local-test-style "
79- ), f"outputStyle should NOT be from local settings (default is no settings), got: { output_style } "
80- assert (
81- output_style == " default"
82- ), f"outputStyle should be 'default', got: { output_style } "
150+ assert output_style != "local-test-style" , (
151+ f"outputStyle should NOT be from local settings (default is no settings), got: { output_style } "
152+ )
153+ assert output_style == "default" , (
154+ f"outputStyle should be ' default', got: { output_style } "
155+ )
83156 break
84157
85158 # On Windows, wait for file handles to be released before cleanup
@@ -121,9 +194,9 @@ async def test_setting_sources_user_only():
121194 async for message in client .receive_response ():
122195 if isinstance (message , SystemMessage ) and message .subtype == "init" :
123196 commands = message .data .get ("slash_commands" , [])
124- assert (
125- "testcmd" not in commands
126- ), f"testcmd should NOT be available with user-only sources, got: { commands } "
197+ assert "testcmd" not in commands , (
198+ f "testcmd should NOT be available with user-only sources, got: { commands } "
199+ )
127200 break
128201
129202 # On Windows, wait for file handles to be released before cleanup
@@ -159,11 +232,11 @@ async def test_setting_sources_project_included():
159232 async for message in client .receive_response ():
160233 if isinstance (message , SystemMessage ) and message .subtype == "init" :
161234 output_style = message .data .get ("output_style" )
162- assert (
163- output_style == " local-test-style "
164- ), f"outputStyle should be from local settings, got: { output_style } "
235+ assert output_style == "local-test-style" , (
236+ f"outputStyle should be from local settings, got: { output_style } "
237+ )
165238 break
166239
167240 # On Windows, wait for file handles to be released before cleanup
168241 if sys .platform == "win32" :
169- await asyncio .sleep (0.5 )
242+ await asyncio .sleep (0.5 )
0 commit comments