Last active
January 23, 2026 02:42
-
-
Save miguelrios/1c15b907c39a3721a8dea838c572ba4b to your computer and use it in GitHub Desktop.
Claude CLI bug: --agents flag doesn't support @filepath syntax (affects SDK with large agent configs)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Minimal reproducible example: Claude Agent SDK @filepath bug for --agents flag | |
| BUG SUMMARY: | |
| When custom agents are passed to ClaudeAgentOptions and the resulting command line | |
| exceeds 100K characters, the SDK writes agents to a temp file and passes `@filepath` | |
| to the CLI. However, the CLI doesn't support this syntax for --agents, causing | |
| custom agents to be silently ignored. | |
| STEPS TO REPRODUCE: | |
| 1. pip install claude-agent-sdk | |
| 2. Set ANTHROPIC_API_KEY environment variable | |
| 3. Run: python sdk-agents-filepath-bug-v2.py | |
| EXPECTED: Custom agents appear in the init message's agents list | |
| ACTUAL: Custom agents are missing when prompt size triggers @filepath fallback | |
| Environment: | |
| - Claude CLI version: 2.1.12 | |
| - claude-agent-sdk version: 0.1.x | |
| - OS: Linux (Ubuntu 24.04) | |
| """ | |
| import asyncio | |
| import json | |
| import subprocess | |
| import tempfile | |
| import os | |
| # ============================================================================= | |
| # TEST 1: SDK with small agents config (direct JSON) - WORKS | |
| # ============================================================================= | |
| async def test_sdk_small_agents(): | |
| """Test SDK with small agent config - agents passed as direct JSON.""" | |
| print("=" * 70) | |
| print("TEST 1: SDK with small agents config (<100K chars) - Expected: WORKS") | |
| print("=" * 70) | |
| from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, AgentDefinition | |
| from claude_agent_sdk.types import SystemMessage, AssistantMessage | |
| # Small agent config - will be passed as direct JSON | |
| agents = { | |
| "my-test-agent": AgentDefinition( | |
| description="A custom test agent for bug reproduction", | |
| prompt="You are a helpful test agent.", | |
| ) | |
| } | |
| options = ClaudeAgentOptions( | |
| agents=agents, | |
| max_turns=1, | |
| permission_mode="bypassPermissions", | |
| ) | |
| print(f"Agent config size estimate: ~100 chars (well under 100K limit)") | |
| print("SDK will pass agents as direct JSON on command line") | |
| found_agent = False | |
| client = ClaudeSDKClient(options=options) | |
| try: | |
| await client.connect() | |
| await client.query("Say 'hello' and nothing else.") | |
| async for message in client.receive_messages(): | |
| # Check init message for agents list | |
| if isinstance(message, SystemMessage): | |
| data = getattr(message, 'data', {}) or {} | |
| subtype = getattr(message, 'subtype', None) | |
| if subtype == "init": | |
| agents_list = data.get("agents", []) | |
| print(f"\nInit message agents: {agents_list}") | |
| found_agent = "my-test-agent" in agents_list | |
| break | |
| # Also check for assistant response to know we're done | |
| if isinstance(message, AssistantMessage): | |
| break | |
| finally: | |
| await client.disconnect() | |
| if found_agent: | |
| print("✅ PASS: 'my-test-agent' found in init message") | |
| else: | |
| print("❌ FAIL: 'my-test-agent' NOT found in init message") | |
| return found_agent | |
| # ============================================================================= | |
| # TEST 2: SDK with large agents config (@filepath fallback) - FAILS (BUG) | |
| # ============================================================================= | |
| async def test_sdk_large_agents(): | |
| """Test SDK with large agent config - triggers @filepath fallback.""" | |
| print("\n" + "=" * 70) | |
| print("TEST 2: SDK with large agents config (>100K chars) - Expected: WORKS, Actual: FAILS") | |
| print("=" * 70) | |
| from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, AgentDefinition | |
| from claude_agent_sdk.types import SystemMessage, AssistantMessage | |
| from dataclasses import asdict | |
| # Large agent config - will trigger @filepath fallback | |
| # SDK's _CMD_LENGTH_LIMIT is 100,000 chars | |
| large_prompt = "You are a helpful assistant. " * 2000 # ~60K chars per agent | |
| agents = { | |
| "my-large-agent-1": AgentDefinition( | |
| description="First large test agent", | |
| prompt=large_prompt, | |
| ), | |
| "my-large-agent-2": AgentDefinition( | |
| description="Second large test agent", | |
| prompt=large_prompt, | |
| ), | |
| } | |
| # Estimate JSON size | |
| agents_json = json.dumps({ | |
| name: {k: v for k, v in asdict(agent).items() if v is not None} | |
| for name, agent in agents.items() | |
| }) | |
| print(f"Agent config size: {len(agents_json):,} chars") | |
| print(f"SDK _CMD_LENGTH_LIMIT: 100,000 chars") | |
| print(f"Will trigger @filepath fallback: {len(agents_json) > 100000}") | |
| print("\nWatch for SDK log: 'Command line length (X) exceeds limit (100000). Using temp file for --agents'") | |
| options = ClaudeAgentOptions( | |
| agents=agents, | |
| max_turns=1, | |
| permission_mode="bypassPermissions", | |
| ) | |
| found_agent = False | |
| client = ClaudeSDKClient(options=options) | |
| try: | |
| await client.connect() | |
| await client.query("Say 'hello' and nothing else.") | |
| async for message in client.receive_messages(): | |
| # Check init message for agents list | |
| if isinstance(message, SystemMessage): | |
| data = getattr(message, 'data', {}) or {} | |
| subtype = getattr(message, 'subtype', None) | |
| if subtype == "init": | |
| agents_list = data.get("agents", []) | |
| print(f"\nInit message agents: {agents_list}") | |
| found_agent = "my-large-agent-1" in agents_list or "my-large-agent-2" in agents_list | |
| break | |
| if isinstance(message, AssistantMessage): | |
| break | |
| finally: | |
| await client.disconnect() | |
| if found_agent: | |
| print("✅ PASS: Large agents found in init message") | |
| else: | |
| print("❌ FAIL: Large agents NOT found in init message (BUG!)") | |
| print("\n🐛 The SDK wrote agents to a temp file and passed @filepath to CLI,") | |
| print(" but the CLI doesn't support @filepath syntax for --agents flag.") | |
| return found_agent | |
| # ============================================================================= | |
| # SUPPLEMENTARY: Direct CLI test showing @filepath doesn't work | |
| # ============================================================================= | |
| def test_cli_filepath_direct(): | |
| """Supplementary test: CLI @filepath syntax directly.""" | |
| print("\n" + "=" * 70) | |
| print("SUPPLEMENTARY: Direct CLI test - @filepath vs direct JSON") | |
| print("=" * 70) | |
| agents_dict = { | |
| "cli-test-agent": { | |
| "description": "CLI test agent", | |
| "prompt": "You are a test agent." | |
| } | |
| } | |
| # Test 1: Direct JSON (works) | |
| print("\n--- Direct JSON ---") | |
| agents_json = json.dumps(agents_dict) | |
| cmd1 = ["claude", "--agents", agents_json, "--print", "--", "List agent names only"] | |
| result1 = subprocess.run(cmd1, capture_output=True, text=True, timeout=60) | |
| direct_works = "cli-test-agent" in result1.stdout.lower() | |
| print(f"Command: claude --agents '<json>' --print -- '...'") | |
| print(f"Result: {'✅ Agent found' if direct_works else '❌ Agent NOT found'}") | |
| # Test 2: @filepath (fails) | |
| print("\n--- @filepath syntax ---") | |
| with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: | |
| json.dump(agents_dict, f) | |
| temp_path = f.name | |
| cmd2 = ["claude", "--agents", f"@{temp_path}", "--print", "--", "List agent names only"] | |
| result2 = subprocess.run(cmd2, capture_output=True, text=True, timeout=60) | |
| filepath_works = "cli-test-agent" in result2.stdout.lower() | |
| print(f"Command: claude --agents '@{temp_path}' --print -- '...'") | |
| print(f"Result: {'✅ Agent found' if filepath_works else '❌ Agent NOT found (BUG)'}") | |
| os.unlink(temp_path) | |
| return direct_works, filepath_works | |
| # ============================================================================= | |
| # MAIN | |
| # ============================================================================= | |
| async def main(): | |
| print("Claude Agent SDK @filepath Bug - Minimal Reproducible Example") | |
| print("=" * 70) | |
| # Check versions | |
| result = subprocess.run(["claude", "--version"], capture_output=True, text=True) | |
| print(f"Claude CLI version: {result.stdout.strip()}") | |
| try: | |
| import claude_agent_sdk | |
| print(f"claude-agent-sdk: installed") | |
| except ImportError: | |
| print("ERROR: claude-agent-sdk not installed. Run: pip install claude-agent-sdk") | |
| return 1 | |
| print() | |
| # Run SDK tests | |
| test1_passed = await test_sdk_small_agents() | |
| test2_passed = await test_sdk_large_agents() | |
| # Run supplementary CLI test | |
| cli_direct, cli_filepath = test_cli_filepath_direct() | |
| # Summary | |
| print("\n" + "=" * 70) | |
| print("SUMMARY") | |
| print("=" * 70) | |
| print(f"SDK Test 1 (small config, direct JSON): {'✅ PASS' if test1_passed else '❌ FAIL'}") | |
| print(f"SDK Test 2 (large config, @filepath): {'✅ PASS' if test2_passed else '❌ FAIL (BUG)'}") | |
| print(f"CLI Test (direct JSON): {'✅ PASS' if cli_direct else '❌ FAIL'}") | |
| print(f"CLI Test (@filepath): {'✅ PASS' if cli_filepath else '❌ FAIL (BUG)'}") | |
| if test1_passed and not test2_passed: | |
| print("\n" + "=" * 70) | |
| print("🐛 BUG CONFIRMED") | |
| print("=" * 70) | |
| print(""" | |
| The Claude CLI does not support @filepath syntax for the --agents flag. | |
| When the SDK's command line exceeds 100K characters (due to large agent prompts), | |
| it writes agents JSON to a temp file and passes `@filepath` to the CLI: | |
| # From claude_agent_sdk/_internal/transport/subprocess_cli.py (lines 336-357) | |
| if len(cmd_str) > _CMD_LENGTH_LIMIT and self._options.agents: | |
| temp_file = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) | |
| temp_file.write(agents_json_value) | |
| temp_file.close() | |
| cmd[agents_idx + 1] = f"@{temp_file.name}" # CLI doesn't understand this! | |
| The CLI interprets `@/tmp/xxx.json` as a literal string instead of reading from the file, | |
| causing custom agents to be silently ignored. | |
| IMPACT: | |
| - Users with large agent configurations (detailed prompts, multiple agents) are affected | |
| - Custom agents silently fail to register - no error is raised | |
| - Difficult to debug because everything appears to work except agents are missing | |
| WORKAROUND: | |
| import claude_agent_sdk._internal.transport.subprocess_cli as cli | |
| cli._CMD_LENGTH_LIMIT = 500000 # Increase from 100K to 500K | |
| SUGGESTED FIX: | |
| Either: | |
| 1. Add @filepath support to CLI for --agents flag (consistent with other flags) | |
| 2. Use stdin or environment variable for large agent configs in SDK | |
| 3. Document the 100K limit and recommend shorter prompts | |
| """) | |
| return 1 | |
| return 0 | |
| if __name__ == "__main__": | |
| exit(asyncio.run(main())) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Minimal reproducible example: Claude CLI doesn't support @filepath for --agents flag | |
| BUG SUMMARY: | |
| The Claude Agent SDK writes agents JSON to a temp file when command line exceeds 100K chars, | |
| passing `@filepath` to the CLI. However, the CLI interprets `@filepath` as a literal string | |
| instead of reading from the file, causing custom agents to be silently ignored. | |
| STEPS TO REPRODUCE: | |
| 1. Run this script: python sdk-agents-filepath-bug.py | |
| 2. Observe that with direct JSON, custom agent appears in available agents list | |
| 3. Observe that with @filepath syntax, custom agent is MISSING | |
| EXPECTED: Both methods should register the custom agent | |
| ACTUAL: @filepath method silently fails to register the agent | |
| Environment: | |
| - Claude CLI version: 2.1.12 | |
| - claude-agent-sdk version: 0.1.x | |
| - OS: Linux (Ubuntu 24.04) | |
| """ | |
| import subprocess | |
| import json | |
| import tempfile | |
| import sys | |
| def test_agents_direct_json(): | |
| """Test 1: Agents passed as direct JSON string - WORKS""" | |
| print("=" * 70) | |
| print("TEST 1: Direct JSON (small payload) - Expected: WORKS") | |
| print("=" * 70) | |
| agents_json = json.dumps({ | |
| "my-custom-agent": { | |
| "description": "A custom test agent", | |
| "prompt": "You are a helpful test agent." | |
| } | |
| }) | |
| cmd = [ | |
| "claude", | |
| "--agents", agents_json, | |
| "--print", | |
| "--", "List all available agents. Just list agent names, nothing else." | |
| ] | |
| print(f"Command: claude --agents '<json>' --print -- 'List agents'") | |
| print(f"Agents JSON length: {len(agents_json)} chars") | |
| result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) | |
| output = result.stdout | |
| if "my-custom-agent" in output.lower(): | |
| print("✅ PASS: 'my-custom-agent' found in output") | |
| else: | |
| print("❌ FAIL: 'my-custom-agent' NOT found in output") | |
| print(f"\nOutput preview:\n{output[:500]}...") | |
| return "my-custom-agent" in output.lower() | |
| def test_agents_filepath(): | |
| """Test 2: Agents passed via @filepath - FAILS (BUG)""" | |
| print("\n" + "=" * 70) | |
| print("TEST 2: @filepath syntax - Expected: WORKS, Actual: FAILS (BUG)") | |
| print("=" * 70) | |
| agents_dict = { | |
| "my-custom-agent": { | |
| "description": "A custom test agent", | |
| "prompt": "You are a helpful test agent." | |
| } | |
| } | |
| # Write to temp file (same as SDK does) | |
| with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: | |
| json.dump(agents_dict, f) | |
| temp_path = f.name | |
| print(f"Temp file: {temp_path}") | |
| print(f"File contents: {json.dumps(agents_dict)}") | |
| # Use @filepath syntax (same as SDK does when command line > 100K chars) | |
| cmd = [ | |
| "claude", | |
| "--agents", f"@{temp_path}", | |
| "--print", | |
| "--", "List all available agents. Just list agent names, nothing else." | |
| ] | |
| print(f"Command: claude --agents '@{temp_path}' --print -- 'List agents'") | |
| result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) | |
| output = result.stdout | |
| if "my-custom-agent" in output.lower(): | |
| print("✅ PASS: 'my-custom-agent' found in output") | |
| else: | |
| print("❌ FAIL: 'my-custom-agent' NOT found in output (BUG!)") | |
| print(f"\nOutput preview:\n{output[:500]}...") | |
| # Cleanup | |
| import os | |
| os.unlink(temp_path) | |
| return "my-custom-agent" in output.lower() | |
| def test_sdk_triggers_filepath(): | |
| """Test 3: Show that SDK uses @filepath when JSON > 100K chars""" | |
| print("\n" + "=" * 70) | |
| print("TEST 3: SDK behavior with large agents config") | |
| print("=" * 70) | |
| # Create agent config that exceeds 100K chars (SDK's _CMD_LENGTH_LIMIT) | |
| large_prompt = "A" * 50000 # 50K chars per agent | |
| agents_dict = { | |
| f"agent-{i}": { | |
| "description": f"Test agent {i}", | |
| "prompt": large_prompt | |
| } | |
| for i in range(3) # 3 agents * ~50K = ~150K chars | |
| } | |
| agents_json = json.dumps(agents_dict) | |
| print(f"Agents JSON size: {len(agents_json):,} chars") | |
| print(f"SDK _CMD_LENGTH_LIMIT: 100,000 chars") | |
| print(f"Would trigger @filepath fallback: {len(agents_json) > 100000}") | |
| print("\nSDK code that triggers this (subprocess_cli.py lines 336-357):") | |
| print(""" | |
| if len(cmd_str) > _CMD_LENGTH_LIMIT and self._options.agents: | |
| # Command is too long - use temp file for agents | |
| temp_file = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) | |
| temp_file.write(agents_json_value) | |
| temp_file.close() | |
| cmd[agents_idx + 1] = f"@{temp_file.name}" # <-- CLI doesn't understand this! | |
| """) | |
| def main(): | |
| print("Claude CLI @filepath Bug - Minimal Reproducible Example") | |
| print("=" * 70) | |
| print() | |
| # Check Claude CLI version | |
| result = subprocess.run(["claude", "--version"], capture_output=True, text=True) | |
| print(f"Claude CLI version: {result.stdout.strip()}") | |
| print() | |
| # Run tests | |
| test1_passed = test_agents_direct_json() | |
| test2_passed = test_agents_filepath() | |
| test_sdk_triggers_filepath() | |
| # Summary | |
| print("\n" + "=" * 70) | |
| print("SUMMARY") | |
| print("=" * 70) | |
| print(f"Test 1 (direct JSON): {'✅ PASS' if test1_passed else '❌ FAIL'}") | |
| print(f"Test 2 (@filepath): {'✅ PASS' if test2_passed else '❌ FAIL (BUG)'}") | |
| if test1_passed and not test2_passed: | |
| print("\n🐛 BUG CONFIRMED: CLI accepts direct JSON but ignores @filepath syntax") | |
| print("\nIMPACT: When using Claude Agent SDK with large agent configs (>100K chars),") | |
| print("custom agents are silently ignored because the SDK falls back to @filepath.") | |
| print("\nWORKAROUND: Monkey-patch SDK to increase _CMD_LENGTH_LIMIT:") | |
| print(""" | |
| import claude_agent_sdk._internal.transport.subprocess_cli as cli | |
| cli._CMD_LENGTH_LIMIT = 500000 # Increase from 100K to 500K | |
| """) | |
| return 1 | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment