Skip to content

Subprocess Transport Only Returns Init Message in FastAPI Context #462

@zhourongyu

Description

@zhourongyu

Issue Summary

The claude_agent_sdk query() function works correctly in isolated tests but fails when called from within a FastAPI/uvicorn server. The SDK's subprocess transport only returns the initial SystemMessage and then terminates, never producing the expected AssistantMessage with response text.

Environment

  • claude_agent_sdk version: latest (bundled CLI)
  • Python: 3.12
  • FastAPI: latest
  • uvicorn: latest
  • anyio: latest
  • Platform: macOS

Reproduction Steps

Working Case (Isolated Test)

# This works correctly - returns 4 messages including text response
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def test():
    options = ClaudeAgentOptions(
        system_prompt="You are a helpful assistant.",
        allowed_tools=[],
        max_turns=1,
        permission_mode="bypassPermissions",
    )

    count = 0
    async for message in query(prompt="What is 2+2?", options=options):
        count += 1
        print(f"Message {count}: {type(message).__name__}")

    print(f"Total: {count} messages")

asyncio.run(test())
# Output: Message 1: SystemMessage, Message 2: AssistantMessage, Message 3: AssistantMessage, Message 4: ResultMessage

Working Case (Minimal FastAPI Server)

from fastapi import FastAPI
from claude_agent_sdk import query, ClaudeAgentOptions

app = FastAPI()

@app.get("/test")
async def test():
    options = ClaudeAgentOptions(
        system_prompt="You are a helpful assistant.",
        allowed_tools=[],
        max_turns=1,
        permission_mode="bypassPermissions",
    )

    messages = []
    async for message in query(prompt="What is 2+2?", options=options):
        messages.append(type(message).__name__)

    return {"messages": messages}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8001)
# Works: Returns ["SystemMessage", "AssistantMessage", "AssistantMessage", "ResultMessage"]

Failing Case (Reportify Codex Server)

# Same query() call fails when invoked from within the Reportify codebase
# Returns only: ["SystemMessage"]

# The server imports many modules including:
# - Sentry SDK
# - Loguru with logger.remove()
# - Custom middleware and exception handlers
# - Various database and business logic modules

Technical Details

Observed Behavior

  • The subprocess starts correctly (verified via ps aux)
  • Only the init SystemMessage (subtype=init) is received
  • Generator terminates after ~1 message instead of producing ~4 messages
  • The subprocess continues running but Python never receives more stdout

What Was Ruled Out

  1. uvloop vs asyncio: Both event loops work in isolated context
  2. Prompt complexity: Simple prompts fail the same way as complex ones
  3. Sentry SDK: Disabling Sentry initialization doesn't fix the issue
  4. StreamingResponse: Even simple GET endpoints with regular responses fail
  5. Environment variables: Same ANTHROPIC_* variables used in both contexts

Root Cause Analysis

The issue appears to be related to how anyio's TextReceiveStream interacts with subprocess stdout in the context of the main server's configuration. The subprocess transport in claude_agent_sdk/_internal/transport/subprocess_cli.py uses:

async for line in self._stdout_stream:
    # Process JSON lines...

This suggests the TextReceiveStream is not receiving all the data when the subprocess is spawned from within the Reportify codebase's context.

Server Configuration Differences

Main Server (Failing)

  • Uses uvicorn with hot-reload
  • Initializes Sentry SDK at module import
  • Configures loguru with logger.remove() at import time
  • Loads many business logic modules
  • Uses custom exception handlers and middleware

Minimal Server (Working)

  • Basic FastAPI setup
  • No Sentry, loguru, or complex middleware
  • Direct uvicorn.run() call

Expected vs Actual Behavior

Expected: query() should return 4 messages:

  1. SystemMessage (init)
  2. AssistantMessage (with planning text content)
  3. AssistantMessage (continuation)
  4. ResultMessage (with cost/usage)

Actual: query() returns only 1 message:

  1. SystemMessage (init)

Impact

This bug makes the claude_agent_sdk unusable in FastAPI/uvicorn applications with typical middleware and initialization patterns, which is a common deployment scenario.

Additional Notes

  • The issue is reproducible and consistent
  • The subprocess CLI itself works correctly when invoked directly
  • The problem appears to be in the Python-side subprocess communication layer
  • This suggests a potential compatibility issue with FastAPI's event loop or middleware stack

Request

Could you investigate if there's an interaction between the subprocess transport and typical FastAPI server configurations (Sentry, logging, middleware)? A potential solution might involve making the subprocess transport more resilient to different async contexts or providing an alternative transport implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    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