Claude Media
コード実行ツールでファイルが消える理由 — $OUTPUT_DIRの罠と対処法

コード実行ツールでファイルが消える理由 — $OUTPUT_DIRの罠と対処法

code executionツールが作ったファイルは、$OUTPUT_DIR直下に置いた分しか返りません。消える原因の切り分け方と、確実に回収する書き方、取りこぼしの救い方を示します。

code executionツールにグラフやレポートを作らせたのに、レスポンスにfile_idが入っていない。Claudeのbashコマンドは成功しているのに、ファイルだけが消える。原因の大半は$OUTPUT_DIRの仕様で、置き場所とコピーの手順を直せば解消します。

$OUTPUT_DIR直下に置いたファイルだけが回収される

code executionツールのbash_code_executionは、呼び出しごとに新しい空のディレクトリを用意します。コマンドからは$OUTPUT_DIRとして参照できます。コマンドが終わると、このディレクトリの最上位にあるファイルだけがキャプチャされ、結果のcontentリストにfile_id付きで返ります。

それ以外の場所に書いたファイルは、コンテナには残りますがAPIレスポンスには現れません。

くらべる

回収されるファイルと、されないファイル

file_id が付く

返ってくる

$OUTPUT_DIR/report.pdfのように、$OUTPUT_DIRの直下に置いたファイル。

content が空になる

返ってこない

$OUTPUT_DIR/charts/output.pngのようなサブディレクトリの中身。/tmp/report.pdfなど$OUTPUT_DIRの外に書いたファイル。

見落としやすいのが、$OUTPUT_DIRが呼び出しごとに作り直される点です。ある呼び出しで置いたファイルは、その呼び出しの結果としてキャプチャされます。次の呼び出しの$OUTPUT_DIRは別の空ディレクトリです。

1回のリクエストでbash_code_executionが複数回呼ばれる場合、データ取得、集計、グラフ描画を別コマンドに分ける組み方があります。このとき中間ファイルを$OUTPUT_DIRに置いても、後続のコマンドからは見えません。

中間ファイルは/tmpなど通常の作業ディレクトリに置き、返したい最終成果物だけを最後のコマンドで$OUTPUT_DIRにコピーします。この役割分担が仕様に沿った設計です。

$OUTPUT_DIRは出力専用の仕組みで、入力ファイルとは別の経路です。CSVやExcelをClaudeに解析させたいときは、Files APIでアップロードしたfile_idをcontainer_uploadコンテンツブロックとしてメッセージに含めます。ファイルがコンテナ内に置かれ、コードから読み込めるようになります。

「入力を渡したのに出力が返らない」ときは、まず入力と出力のどちらの経路の話かを分けて考えてください。

症状から原因を切り分ける

消え方によって、直す場所が変わります。

起きていること原因直し方
return_codeは0、contentが空原因生成物が$OUTPUT_DIRの外に残っている直し方最終コマンドで$OUTPUT_DIR直下へコピーする
contentが空で、保存先にサブフォルダを切っている原因キャプチャ対象は最上位のファイルだけ直し方os.makedirsやmkdirをやめ、直下に置く
text_editor_code_executionで作ったのにfile_idが無い原因作成結果にfile_idのフィールドが無い直し方bashで$OUTPUT_DIRへコピーする
アップロードしたファイルの取得が400で失敗する原因アップロード分はダウンロード不可直し方返したいファイルを$OUTPUT_DIR経由で生成させる

1行目と2行目はどちらも「コマンドは成功しているのに何も返らない」症状です。Claudeがmatplotlib.pyplot.savefig("/tmp/chart.png")とパスを直書きしたコードを走らせた場合を考えます。return_codeは0のままで、呼び出し元には1つもファイルが渡りません。保存先を決めるのはClaudeの書くコードなので、$OUTPUT_DIRを指示しない限り、どこに書かれるかは保証されません。

成果物を作るライブラリはコンテナに入っている

コンテナはインターネットに接続できないため、Claudeは実行時にパッケージを追加できません。使えるのは入っているライブラリだけです。Python 3.11の環境には、次のような成果物用のライブラリが入っています。

  • グラフ: matplotlib、seaborn
  • Excel: openpyxl、xlsxwriter
  • PowerPoint・Word: python-pptx、python-docx
  • PDF: reportlab、pypdf、pdfplumber

これらで作った成果物が回収の対象になります。依頼文で「pip installして」と書いても実行できません。成果物の形式に合うライブラリの名前を指定する選択肢があります。

text_editorで作ったファイルはfile_idを持たない

code executionツールはClaudeに2つのサブツールを渡します。シェルコマンドを実行するbash_code_executionと、ファイルの閲覧・作成・編集を行うtext_editor_code_executionです。

createコマンドの結果はtext_editor_code_execution_create_resultで返ります。持つフィールドはis_file_updateだけで、既存ファイルを上書きしたかどうかを示します。contentリストもfile_idも含まれません。

file_idが載るのは、bash_code_execution_resultのcontentフィールドだけです。つまり、text_editorで作ったファイルを受け取るには、bashコマンドで$OUTPUT_DIRの直下にコピーする工程が要ります。$OUTPUT_DIRは呼び出しごとに新規作成されるので、text_editorの操作が前にあるからといって、bashの$OUTPUT_DIRにそのファイルが入っているわけではありません。

Claudeはキャプチャの成否を自分では確認できない

ここが本質的な理由です。code executionツールの説明文は、Claudeにファイルを$OUTPUT_DIRへコピーして共有するよう指示しています。一方でClaudeは、キャプチャされたファイルの一覧であるcontentリストを見ません。

そのため、ファイルを必ず受け取りたいアプリケーションでは、$OUTPUT_DIRへのコピーとlsによる確認を同じコマンドに含めるよう、Claudeに指示します。lsの出力がstdoutに乗るので、Claudeはコピーが成功したかを自分で判断できます。

python /tmp/make_report.py && cp /tmp/report.pdf "$OUTPUT_DIR/" && ls "$OUTPUT_DIR"

1コマンドの中で、レポートの生成、$OUTPUT_DIR直下へのコピー、lsによる中身の表示が順に走ります。

システムプロンプトに書いておく1文

リクエストのたびに「$OUTPUT_DIRにコピーしてlsで確認して」と書くのは現実的ではありません。ファイル生成が絡むアプリケーションでは、システムプロンプトに恒常的なルールとして置きます。

ファイルを生成するタスクでは、最終的にユーザーへ返すファイルを必ず$OUTPUT_DIRの直下(サブディレクトリを作らない)にコピーし、コピーしたコマンドの中でlsを実行して結果を確認すること。中間ファイルは/tmp等の作業ディレクトリに置き、$OUTPUT_DIRには最終成果物だけを置く。

この1文には、直下に置くこと、lsで確認すること、中間ファイルを/tmpに逃がすことの3点が入っています。Agent SDKでエージェントを組む場合も、システムプロンプトを直す箇所は1つで済みます。

生成したファイルはFiles APIで取得する

$OUTPUT_DIR直下でキャプチャされたファイルは、レスポンスのcontentリストにfile_idとして現れます。このfile_idをFiles API経由でダウンロードします(PDFの取得手順も同じAPIの流れです)。

def extract_file_ids(response):
    file_ids = []
    for item in response.content:
        if item.type == "bash_code_execution_tool_result":
            content_item = item.content
            if content_item.type == "bash_code_execution_result":
                for output_block in content_item.content:
                    file_ids.append(output_block.file_id)
    return file_ids
 
for file_id in extract_file_ids(response):
    file_metadata = client.files.retrieve_metadata(file_id)
    file_content = client.files.download(file_id)
    file_content.write_to_file(file_metadata.filename)

ダウンロードできるのは、スキルやcode executionツールが作ったファイルだけです。自分でアップロードしたファイルをダウンロードしようとすると、400エラーが返ります。メタデータのdownloadableがtrueかどうかで見分けられ、アップロード分はfalseです。

Files APIのファイルは、expires_in_secondsを指定せずにアップロードすればexpires_atがnullのまま、削除するまで残ります。code executionが生成したファイルも、Files APIで取得できる間は明示的に削除するまで保持されます。キャプチャされなかったファイルにはfile_idが付きませんが、コンテナの期限(作成から30日)内なら、コンテナを再利用して$OUTPUT_DIRへコピーさせれば取り出せます。

ダウンロードしたファイルが、コンテナ内のサイズやチェックサムと一致しないことがあります。これはバグではありません。Claude APIでは、Claudeが作った対応形式の画像・動画・音声に署名メタデータが埋め込まれて届きます。C2PA Content Credentialsと呼ばれ、発行者はAnthropicです。対応形式にはPNG・JPEG・GIF・WebP・MP4・MP3・WAVなどが含まれ、署名で数KB増えますが、見た目の内容は変わりません。

テキスト、PDF、Officeドキュメントは署名の対象外です。再エンコードや形式変換、スクリーンショット、メタデータを消すツールを通すと署名は失われます。検証にはc2patoolなどのC2PA対応ツールを使います。

$OUTPUT_DIRへのコピーを忘れてもコンテナ再利用で救える場合がある

回収し損ねたファイルは、生成したbash_code_executionと同じコンテナに残っています。レスポンスのcontainer.idを次のリクエストのcontainerパラメーターに渡せば、そのファイルは元の場所で見つかります。あとはClaudeに「$OUTPUT_DIRにコピーして」と頼むだけです。

手順

取りこぼしたファイルを拾い直す流れ

  1. 1

    1回目のレスポンスから container.id を控える

    ファイルが/tmpに残ったまま終わった場合でも、レスポンスのcontainerオブジェクトにidが入っています。

  2. 2

    同じ container を指定して再リクエストする

    リクエストのcontainerパラメーターにそのIDを渡します。コンテナ内のファイルシステムはリクエストをまたいで保たれます。

  3. 3

    コピーと ls を同じコマンドで頼む

    「/tmp/report.pdfを$OUTPUT_DIRにコピーしてlsで確認して」と指示します。今度のコマンドの結果にfile_idが載ります。

# 1回目のリクエストでファイルが/tmpに残ったまま終わったとする
response1 = client.messages.create(
    model="claude-opus-5",
    max_tokens=4096,
    messages=[{"role": "user", "content": "レポートを作って/tmp/report.pdfに保存して"}],
    tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
 
# container.idを渡して同じコンテナを再利用し、今度は$OUTPUT_DIRへのコピーを明示する
response2 = client.messages.create(
    container=response1.container.id,
    model="claude-opus-5",
    max_tokens=4096,
    messages=[
        {
            "role": "user",
            "content": "/tmp/report.pdfを$OUTPUT_DIRにコピーしてlsで確認して",
        }
    ],
    tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)

コンテナの寿命と制約は、次の数字で押さえておくと判断しやすくなります。

数字

コンテナの寿命と容量

  • 期限

    作成から30日

    期限切れのコンテナは再利用できない

  • 自動保存

    約5分

    無操作でチェックポイントされ、30日以内なら復元される

  • ディスク

    5 GiB

    ワークスペースの保存領域。ネットワークは使えない

期限切れのコンテナIDを渡すと、復元ではなくエラーが返ります。その場合はcontainerパラメーターを外して新しいコンテナで実行し直します。

レスポンスのcontainer.expires_atは、30日の期限を表す値ではありません。公式ドキュメントでは、より短い時間で動く値として説明されています。期限の見積もりには使えないので、作成日から30日で数えます。

コンテナはFiles APIと同様に、リクエストのワークスペース単位でスコープされます。別のワークスペースで作ったコンテナのIDを使い回す構成は避けてください。

まとめ

ファイルが返らないときは、まずコマンドが$OUTPUT_DIRの直下にコピーしているか、lsで確かめているかを見ます。text_editorで作った場合はbashでのコピーを挟み、取りこぼしたらコンテナIDを渡して拾い直します。

エージェントを組むなら、MCPをコードとして呼び出す設計やClaude Agent SDK入門も参考になります。ファイル生成を伴うステップの前に、この仕様をシステムプロンプトへ織り込んでおきます。

この記事を共有:XはてブLinkedIn