0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Agent SDK(Python)で Claude Code をスクリプトから動かす実装手順 — CLI が見つからない・許可待ちで止まる・CLAUDE.md が読まれない、3つのハマりどころ【2026】

0
Posted at

はじめに / 対象と前提

Claude Code をターミナルで使い慣れてきて、次は「自分の Python スクリプトやバッチ処理から、同じエージェントを呼び出したい」と思った人向け。Claude API(anthropic パッケージ)を直接叩くのではなく、Claude Code のツール実行・許可制御・コンテキスト管理をそのまま借りるのが Claude Agent SDK の位置づけだ。

自分が動作確認した環境は以下。

  • macOS 15 / Python 3.13 / Node.js 22.x
  • Claude Code v2.x(claude --version で確認)
  • claude-agent-sdk 0.1.x(pip)
  • 認証は環境変数 ANTHROPIC_API_KEY

SDK はバージョンアップでデフォルト挙動が変わっている(後述の setting_sources が典型)。読んでいる時点のバージョンを必ず確認してほしい。

TL;DR

  • pip install claude-agent-sdk だけでは動かない。SDK は Claude Code CLI を子プロセスで起動する薄いラッパーなので、claude コマンドが解決できる必要がある
  • 一発実行は query()、会話を続けるなら ClaudeSDKClient。どちらも ClaudeAgentOptions に cwd / allowed_tools / permission_mode を渡す
  • ハマりどころは 3 つ。CLINotFoundError、非対話なのに許可待ちで止まる(またはツールが黙って拒否される)、CLAUDE.md が読み込まれない

手順 / 動かし方

1. インストールと疎通確認

npm install -g @anthropic-ai/claude-code
claude --version          # 2.x.x が出ればOK

python3 -m venv .venv && source .venv/bin/activate
pip install claude-agent-sdk
python -c "import claude_agent_sdk; print('ok')"

2. 最小コード:読み取り専用でリポジトリを調査させる

import anyio
from claude_agent_sdk import (
    query, ClaudeAgentOptions,
    AssistantMessage, ResultMessage, TextBlock, ToolUseBlock,
)

async def main():
    options = ClaudeAgentOptions(
        cwd="/path/to/your-repo",
        allowed_tools=["Read", "Grep", "Glob"],   # 読み取り系だけ許可
        permission_mode="default",
        max_turns=10,
    )
    async for msg in query(
        prompt="このリポジトリのテスト実行コマンドと、テストが置かれているディレクトリを調べて報告して",
        options=options,
    ):
        if isinstance(msg, AssistantMessage):
            for block in msg.content:
                if isinstance(block, ToolUseBlock):
                    print(f"[tool] {block.name} {block.input}")
                elif isinstance(block, TextBlock):
                    print(block.text)
        elif isinstance(msg, ResultMessage):
            print(f"--- turns={msg.num_turns} cost=${msg.total_cost_usd:.4f}")

anyio.run(main)

実行結果(抜粋):

[tool] Glob {'pattern': '**/pytest.ini'}
[tool] Read {'file_path': '/path/to/your-repo/pyproject.toml'}
テストは `pytest` で実行します。設定は pyproject.toml の [tool.pytest.ini_options] にあり、
テストファイルは tests/ 配下に 14 本あります。
--- turns=4 cost=$0.0312

query() は async generator で、CLI が吐くメッセージを順に流してくる。ResultMessage が最後に 1 回だけ来て、ターン数とコストが取れる。バッチで回すならここをログに残すのが基本。

3. 編集を伴うタスクにする

ファイルを書かせるなら Edit / Write / Bash を許可し、permission_mode を acceptEdits にする。

options = ClaudeAgentOptions(
    cwd="/path/to/your-repo",
    allowed_tools=["Read", "Grep", "Glob", "Edit", "Write", "Bash"],
    permission_mode="acceptEdits",   # cwd 配下のファイル編集を自動承認
    max_turns=30,
)

4. 会話を続ける:ClaudeSDKClient

「まず調査 → 結果を見てから直す」のように、前のターンの文脈を引き継ぎたいときは ClaudeSDKClient を使う。query() は毎回新規セッションになる。

from claude_agent_sdk import ClaudeSDKClient

async def main():
    async with ClaudeSDKClient(options=options) as client:
        await client.query("失敗しているテストを列挙して。まだ直さないで")
        async for msg in client.receive_response():
            ...  # 上と同じ出力処理

        await client.query("1 件目だけ直して、pytest でそのテストが通ることを確認して")
        async for msg in client.receive_response():
            ...

ハマりどころ

1. CLINotFoundError: Claude Code not found

claude_agent_sdk._errors.CLINotFoundError: Claude Code not found.
Install with: npm install -g @anthropic-ai/claude-code

原因:SDK は claude 実行ファイルを PATH から探して子プロセス起動する。ターミナルでは動くのに、launchd / cron / systemd / Docker から起動すると PATH に npm のグローバル bin(/opt/homebrew/bin や ~/.npm-global/bin)が入っておらず見つからない。

回避策:which claude で絶対パスを調べて cli_path に渡す。

options = ClaudeAgentOptions(
    cli_path="/opt/homebrew/bin/claude",
    ...
)

自分はこれをスケジューラ経由で踏んだ。対話シェルで再現しないので原因に気づくまで時間を溶かした。

2. 非対話なのに許可待ちで止まる / ツールが黙って拒否される

症状 A:permission_mode="default" で allowed_tools に入れていないツール(例:Bash)を Claude が使おうとすると、対話 UI が無いので承認できず、Claude が「権限がないため実行できませんでした」とテキストで返して普通に終了する。ResultMessage.is_error は False。ログを見ないと失敗に気づけない。

症状 B:can_use_tool コールバックで動的に判定しようとして query() に渡すと、コールバックはストリーミング入力モードでしか使えないというエラーになる。

回避策:

  1. 使わせたいツールは allowed_tools に明示する。Bash を許可すると全コマンドが通るので、必要なら disallowed_tools と併用する
  2. 動的判定が要るなら ClaudeSDKClient + can_use_tool にする
from claude_agent_sdk import PermissionResultAllow, PermissionResultDeny

async def gate(tool_name, input_data, context):
    if tool_name == "Bash":
        cmd = input_data.get("command", "")
        if "rm -rf" in cmd or "git push" in cmd:
            return PermissionResultDeny(message=f"blocked: {cmd}")
    return PermissionResultAllow()

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Edit", "Bash"],
    can_use_tool=gate,
    ...
)
  1. 「返答テキストに『権限』『permission』が含まれたら失敗扱い」のような後段チェックを自分で入れる。SDK は仕事が完遂されたかどうかまでは判定してくれない

permission_mode="bypassPermissions" は全部通すので手っ取り早いが、コンテナなど壊れてもいい環境以外では使わないほうがいい。

3. CLAUDE.md も .claude/settings.json も読まれない

症状:ターミナルの Claude Code では守られていたプロジェクト規約(CLAUDE.md)を SDK 経由だと無視する。カスタムスラッシュコマンドや Skills も認識しない。

原因:SDK v0.1.0 以降、setting_sources のデフォルトが 「何も読まない」 に変わった。SDK アプリの挙動がユーザーのローカル設定に左右されないようにするための仕様変更だが、旧バージョンの記事を見て書くと必ず踏む。

回避策:読ませたいソースを明示する。

options = ClaudeAgentOptions(
    setting_sources=["project"],        # CLAUDE.md / .claude/ を読む
    system_prompt={
        "type": "preset",
        "preset": "claude_code",         # Claude Code 標準のシステムプロンプト
        "append": "回答は日本語で。変更前に必ず既存テストを実行すること。",
    },
    ...
)

system_prompt も同様で、未指定だと Claude Code のデフォルトシステムプロンプトは使われない。ターミナルと同じ振る舞いを期待するなら preset: claude_code を指定する。

背景・補足

なぜ CLI 依存の問題がそのまま出るのかは、構造を見ると腑に落ちる。

ツール実行も許可判定も設定ファイルの読み込みも、全部 CLI 側で起きている。だから PATH・環境変数・設定ソースといった CLI の実行環境の問題が SDK の問題として顔を出す。逆に言えば、CLI で再現テストをすれば切り分けが早い。

まとめ

  • claude-agent-sdk は CLI のラッパー。pip と npm の両方が必要で、非対話環境では cli_path を絶対パスで渡す
  • 使わせるツールは allowed_tools に明示し、編集させるなら permission_mode="acceptEdits"。拒否されても is_error は立たないので後段チェックを入れる
  • setting_sources と system_prompt はデフォルトで空。ターミナルと同じ挙動が欲しければ ["project"] と preset: claude_code を明示する
  • 動的な許可判定は ClaudeSDKClient + can_use_tool。query() では使えない
  • ResultMessage のコストとターン数は必ずログに残す
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?