Claude Media
TypeScriptでContentBlockのtextエラーが出る原因と直し方

TypeScriptでContentBlockのtextエラーが出る原因と直し方

TypeScript/Python SDKでmessage.content[0].textが型エラーになる原因と、typeで絞り込んで安全に読む書き方を解説します。

'text' does not exist on type 'ContentBlock' が出る理由

anthropic.messages.create()のレスポンスからmsg.content[0].textを直接読むと、TypeScriptがProperty 'text' does not exist on type 'ContentBlock'という型エラーを出すことがあります。原因はcontent配列の各要素が単一の型ではなく、複数のブロック型を束ねた共用体ContentBlockになっているためです。

実際のIssue #432の報告では、同じ1行に対してProperty 'text' does not exist on type 'ContentBlock'.Property 'text' does not exist on type 'ToolUseBlock'.の2種類のエラーが並んで表示されていました。TypeScriptは共用体を構成するメンバーごとに個別のエラーを出すため、ContentBlockにブロック型が増えるたびに、同じ1行から報告されるエラーの本数も増えていきます。

このエラーは2024年5月に報告されたIssue #432で確認されており、Anthropicのメンテナーからは「意図した変更」という回答が付いています。contentが共用体型である以上、typeで絞り込まずに.textへアクセスするコードは、SDKのどのバージョンでも型エラーになります。

この設計は一見不便ですが、意図は安全な側の挙動を選ぶことです。ツールを設定した呼び出しでcontent[0]が常にテキストだと決め打つと、モデルが実際にツールを呼んだ場合に.textundefinedのまま後続処理へ渡り、原因の分かりにくい実行時バグを生みます。TypeScriptの型エラーは、その事故をビルドの段階で検知するための警告です。

ContentBlockが持つメンバーは当初の2種類から増え続けています。TypeScript SDK v0.127.0のソースでは、TextBlockThinkingBlockRedactedThinkingBlockToolUseBlockServerToolUseBlockWebSearchToolResultBlockWebFetchToolResultBlockCodeExecutionToolResultBlockBashCodeExecutionToolResultBlockTextEditorCodeExecutionToolResultBlockToolSearchToolResultBlockContainerUploadBlockの12種類を束ねた共用体です。textプロパティを持つのは先頭のTextBlockだけなので、絞り込みなしのアクセスはどのバージョンのSDKでも型エラーになります。

いつからこの型エラーが起きるようになったのか

最初の報告は2024年5月31日です。SDKをv0.20.9からv0.22.0へ上げた直後に発生し、報告者はmsg.content[0].textを無条件に読んでいたコードがアップグレードだけで型エラーになったと説明していました。

メンテナーのbcherny氏は2024年6月19日、この変更がPull Request #429による意図的なものだと回答しています。それ以前のSDKはレスポンスが常にテキストブロックである前提だったため.textを無条件に読めましたが、ツール呼び出し機能の追加でレスポンスにToolUseBlockが混じるようになり、content配列の型を単一のTextBlockから複数ブロックの共用体へ広げる必要が生じました。

同じスレッドでは、create()の戻り値の型をツール設定の有無で出し分けるオーバーロードの提案も出ていました。ツールを使わない呼び出しではcontentが常にテキストブロックだけになる保証があるため、型上もTextBlockだけを返せばこのエラー自体を避けられるという案です。提案者のrattrayalex氏は2024年6月20日、bcherny氏の「意図した変更」という説明に同意したうえで、このオーバーロード案自体は気に入っているとコメントしています。提案は実装されないまま残り、2026年8月5日にメンテナーのdtmeadows-ant氏が絞り込みパターンを唯一サポートされる対処法としてIssueをクローズしました。

2026年5月16日のコメントでは、共用体の構成要素がさらに増えたことも報告されています。当初はTextBlockToolUseBlockの2種類だけでしたが、拡張思考機能の追加でThinkingBlockが加わり、報告者はエラーメッセージがToolUseBlockではなくThinkingBlockについて出るようになった点に戸惑ったと書いています。ブロックの種類が増えるたびに、絞り込みを書いていないコードは新しいエラーメッセージで同じ問題に再度つまずくことになります。

switch文とif文でブロック型を絞り込む

もっとも直接的な直し方は、typeフィールドでの分岐です。Issue内でAnthropicのメンテナーが最初に提示した書き方がswitch文でした。どのブロック型が来てもcaseが網羅されていれば処理が分岐し、想定外の型が来たときだけdefault側で気づけるという構造です。

const response = message.content[0];
switch (response.type) {
  case "text":
    console.log(response.text);
    break;
  case "tool_use":
    console.log(response.input);
    break;
  case "thinking":
    console.log(response.thinking);
    break;
}

1ブロックだけ判定できればよい場面ではif文でも十分です。

const block = message.content[0];
if (block.type === "text") {
  console.log(block.text);
}

型アサーション(as Anthropic.TextBlock)で済ませる方法もIssue内で紹介されていますが、実行時チェックを伴いません。たとえばツールを1つ追加した直後にモデルがそのツールを呼び出すと、content[0]ToolUseBlockになり.textプロパティ自体が存在しません。型アサーションはコンパイル時のチェックを通すだけなので、実行時にはundefinedを文字列として扱おうとして別のエラーを引き起こします。ツールを1つでも定義している構成では避けたほうが安全です。

型ガード関数とfilterでtextだけを取り出す

判定を複数箇所で使い回すなら、型ガード関数に切り出す方法がコメント欄でも紹介されています。この書き方はcarnivale7898氏が2025年4月5日に投稿したコメントが元になっています。

import type { ContentBlock, TextBlock } from "@anthropic-ai/sdk/resources/messages";
 
function isTextBlock(block: ContentBlock): block is TextBlock {
  return block.type === "text";
}
 
if (isTextBlock(message.content[0])) {
  console.log(message.content[0].text);
}

複数のテキストブロックをまとめて連結したいだけなら、filtermapで1行にまとめる書き方も実用的です。このgetTextヘルパーは、共用体のメンバーがさらに増えても呼び出し側のコードを変えずに済む点が型ガード関数単体より扱いやすいところです。

const getText = (blocks: ContentBlock[]) =>
  blocks
    .filter((b): b is TextBlock => b.type === "text")
    .map((b) => b.text)
    .join("");
 
const text = getText(message.content);

型のimport元には注意が必要です。TextBlockContentBlockはパッケージのルートからは公開されておらず、@anthropic-ai/sdk/resources/messagesからimportします。ルートからimport type { TextBlock } from "@anthropic-ai/sdk"と書くと、「no exported member」というエラーになります。この間違いはIssueのコメント欄でも報告されていて、絞り込みパターン自体は合っているのにimport文だけでビルドが止まるという紛らわしいケースです。

使い分け早見表 — どの絞り込み方法を選ぶか

判定の書き方は場面によって最適解が変わります。1回きりのアクセスに型ガード関数を持ち込むと大げさですし、逆に複数箇所で同じ判定を繰り返すと、ContentBlockに新しい型が加わったときの修正漏れの原因になります。

場面書き方おすすめ度
1ブロックだけ判定書き方if文おすすめ度◎ 最短で読める
複数のブロック型を分岐書き方switch文おすすめ度◎ 網羅漏れに気づきやすい
判定を複数箇所で使い回す書き方型ガード関数おすすめ度◎ 再利用性が高い
複数ブロックのテキストを連結書き方filter + mapおすすめ度◎ 1行で完結する
ツールを1つも使わない設計書き方型アサーションおすすめ度△ 将来ツールを足すと事故る

Pythonでも同じ設計 — content_block.typeで絞り込む

同じ問題はPython SDKでも起きます。anthropic-sdk-pythonの型定義を見ると、ContentBlockTextBlockThinkingBlockToolUseBlockを含む12種類のUnion型で、TypeScript側の共用体とメンバー構成が一致しています。PyPI上の現在の配布バージョンは1.7.0です。

Pylanceなど静的型チェッカーを使っている場合も、response.content[0].textへの直接アクセスは同様に警告になります。type属性で絞り込んでから読む書き方が共通の対処法です。

ただしTypeScriptとPythonでは事故り方が違います。TypeScriptはビルド自体が止まるため、絞り込みを忘れたコードは本番に届く前に気づけます。Pythonは型チェッカーを併用していない限りビルドという工程がなく、content_block.textが存在しないブロックへアクセスしても実行するまでエラーになりません。CIにmypypyrightを組み込んでいないプロジェクトでは、ツール呼び出しを含む応答が返ってきたときに初めてAttributeErrorとして表面化します。

content_block = response.content[0]
if content_block.type == "text":
    text = content_block.text
else:
    raise ValueError(f"Non-text response received: {content_block.type}")

よくあるつまずき

  • 型アサーションだけで済ませる: as Anthropic.TextBlockは実行時チェックを伴いません(理由は前述の型アサーションの節を参照)
  • importの階層を間違える: TextBlockContentBlockはパッケージルートでなく@anthropic-ai/sdk/resources/messagesからimportします
  • switch文にdefaultケースを書かない: ContentBlockは12種類の共用体まで増えており、今後も増える見込みです。未対応のブロック型をログへ残す分岐を用意しておくと、新しいブロック型の追加に気づけます
  • Message Batchesの結果ループでも絞り込みが必要: batches.results()で1件ずつ取り出すentry.result.message.contentも同じContentBlock型なので、Message Batches SDKの実装でも同じ型ガードが必要です
  • content[0]だけを決め打ちで読む: 拡張思考(extended thinking)を有効にすると、content[0]ThinkingBlockcontent[1]以降がTextBlockになることがあります。公式ドキュメントも、thinkingブロックが先にストリームされ、テキストブロックはそのあとに続くと説明しています。先頭要素だけを見る実装は、拡張思考を有効化した途端に空文字列やundefinedを返すようになります。配列全体をfilterしてから読む書き方にしておくと、この種の並び順の変化に影響されません

まとめ

Property 'text' does not exist on type 'ContentBlock'は、SDKのバグではなく、レスポンスのcontentがテキストとツール呼び出しなどを束ねた共用体になったことで生じる意図した型エラーです。2024年のPull Request #429でツール呼び出し対応のために導入され、戻り値の型を出し分けるオーバーロード案は実装されないまま、Anthropicのメンテナーは2026年8月5日、typeで絞り込むパターンを唯一サポートされる対処法としてIssueをクローズしました。1ブロックの判定ならif文、複数の分岐が必要ならswitch文、判定を使い回すなら型ガード関数と、場面で使い分ければ対応できます。TypeScriptならビルドが止まって気づけますが、Pythonは型チェッカーを併用しない限り実行時まで気づけない点も覚えておくと安全です。

より広くTypeScript SDKの使い方を確認したい場合はClaudeのTypeScript SDKで実装するStreaming・Tool・MCPヘルパー、実行時に発生するAPIエラーとSDK例外クラスの対応はClaude APIのエラー形式とSDK例外クラスの言語別対応表を参照してください。

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