anthropics / anthropics/claude-agent-sdk-python

subprocess CLI rejects list-form system_prompt (Anthropic API supports it)

未关闭
#899 3 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
bug enhancement good first issue
主要语言
Python
星标
8.1k
派生
1.3k
平均合并
2 天 31 分钟
30 天内合并 PR
1

描述

## Summary

`SubprocessCLITransport._build_command` at `src/claude_agent_sdk/_internal/transport/subprocess_cli.py:209-220` only accepts `system_prompt` as one of:

- `str`
- `None`
- `{"type": "file", "path": ...}` (`SystemPromptFile`)
- `{"type": "preset", "append": ...}` (`SystemPromptPreset`)

When a caller passes the **list-of-content-blocks** form (which is valid at the upstream Anthropic Messages API per the `system` parameter docs), the transport raises:

```
AttributeError: 'list' object has no attribute 'get'
File ".../subprocess_cli.py", line 215, in _build_command
if sp.get("type") == "file":
```

before any model call. The crash happens at command-build time; the user-facing `ClaudeAgentOptions(system_prompt=...)` doesn't validate the shape, so the failure is delayed and noisy.

## Use case

Anthropic's `prompt_caching` documentation specifically recommends passing `system` as a **list of content blocks** so `cache_control` can be attached to a trailing block and yield layer-independent caching of role vs. injected reference material. We use this pattern to layer (1) a stable role system prompt with (2) per-call retrieval-style skill content. The cache_proxy in our stack handles the list form correctly; only the SDK's bundled CLI transport blocks it.

Worked example we hit today (paid validation, $0.46 burned before crash):

```python
options = ClaudeAgentOptions(
system_prompt=[
{"type": "text", "text": role_system_prompt},
{"type": "text", "text": injected_skill_content},
],
...
)
async for msg in query(prompt=user_prompt, options=options):
...
# AttributeError at SubprocessCLITransport._build_command
```

## Suggested fix

`_build_command` could:

1. Detect `isinstance(sp, list)` and serialize via a new flag, e.g. `--system-prompt-json ` pointing at a temp file containing the JSON-encoded list. The bundled `claude` CLI would parse that and forward as-is to the API.

2. Alternatively, accept the list form on the existing `--system-prompt-file` path with content auto-detection (string vs. JSON array).

Either keeps the wire format Anthropic-compatible and avoids the silent client-side flatten that today's only workaround (concatenate to string) forces.

## Workaround we're using

We collapse the list to a single concatenated string at the runtime layer and document the cost (loss of layer-independent caching) in our ADR. This regresses option 4 ("structured 2-block") to option 3 ("concatenated string") in our chosen design. We'd revert the regression as soon as the SDK forwards list-form.

## Reproduction

`claude-agent-sdk` 0.1.66, bundled CLI 2.1.119:

```python
from claude_agent_sdk import ClaudeAgentOptions, query
options = ClaudeAgentOptions(
system_prompt=[{"type": "text", "text": "hello"}, {"type": "text", "text": "world"}],
model="claude-opus-4-7",
)
async for msg in query(prompt="hi", options=options):
print(msg)
# AttributeError: 'list' object has no attribute 'get'
```

## Related

- Upstream Anthropic API reference: `system` parameter accepts `string | content_block[]` per [Messages API docs](https://docs.claude.com/en/api/messages).
- Caching guidance: [prompt caching](https://docs.claude.com/en/build-with-claude/prompt-caching) recommends placing `cache_control` on the LAST block of each cacheable layer.

贡献指南

这个仓库没有索引到贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。