Claude Codeに「APIレスポンスをパースして」と頼んだら、こういうコードが返ってきた。僕は最初、これで型安全になっていると勘違いしていた。
const data = response.data as any
const user = data.user as any
const name = (data.user as any).name as string
as any が3回。型情報が完全に捨てられている。これでは型チェックの意味がない。
TypeScriptのコードを書かせるとき、Claude Codeはデフォルトで「とりあえず動くコード」を優先する。型安全より実行できることを選ぶ。それを変えるには、型安全を明示的に要求する設定と指示が必要だ。
この記事を読めば次のことがわかる。
as anyが生成される根本原因と解決策tsconfig.jsonの strict 設定を CLAUDE.md で強制する方法- Zod スキーマ設計を Claude Code に任せる手順
- 型エラーを自動修正させるフィードバックループ
- 型安全なコード生成のための CLAUDE.md テンプレート
Claude Codeがanyを乱用するのはなぜか?
Claude Codeが any を使いたがる理由は主に3つある。
1. 型情報がない状態でコードを完成させようとする
APIレスポンスの型定義がない、ライブラリの型定義が不完全、など「型がわからない」状況でClaude Codeは any で埋めようとする。型エラーを出さずにコードを完成させることを優先するからだ。
2. 指示に型安全の要件が含まれていない
「パースして」「変換して」という指示に型の要件は含まれていない。Claude Codeは指示された機能を実現することに集中して、型の厳格さは後回しになる。
3. プロジェクトの型設定が伝わっていない
tsconfig.json の設定がClaude Codeに伝わっていないと、strictモードの存在を知らずに緩い型で書いてしまう。
これらを解決するアプローチは「プロジェクト設定を先に伝える」「型を最初から定義させる」「型チェックを必須にする」の3点だ。
tsconfig.jsonのstrict設定をCLAUDE.mdで強制するには?
CLAUDE.mdに以下の内容を書くと、Claude Codeがコードを生成するたびに型安全の要件を参照するようになる。
まずはtsconfig.jsonの設定。
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}
そしてCLAUDE.mdに書くコード生成のルール。
## TypeScript設定
### コード生成のルール
1. `as any` は禁止。どんな場合も使わない
2. `as Type` のキャストは型ガードか Zod バリデーション後のみ許可
3. 関数の引数・戻り値には必ず型注釈を付ける
4. `any` の代わりに `unknown` を使い、型ガードで絞り込む
5. 型推論に頼れる場面では明示的な型注釈は不要(過剰な注釈もNG)
### 検証コマンド
コード生成後は必ず `npx tsc --noEmit` を実行する。
型エラーが出たら修正してから提出すること。
この設定がCLAUDE.mdにあると、プロンプトごとに「anyを使わないで」と言わなくて済む。tsconfig.json の内容をCLAUDE.mdに転記しておくのが特に効く。
ZodスキーマをClaude Codeに設計させるにはどうすればいいか?
外部APIのレスポンスを扱う場合、Zodスキーマを先に定義してからパースコードを生成させる流れが安全だ。
Step 1: APIのレスポンス例を渡してZodスキーマを生成させる
以下のAPIレスポンス例からZodスキーマを生成してください。
APIレスポンス例:
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"role": "admin" | "user" | "guest",
"createdAt": "2026-01-01T00:00:00Z",
"profile": {
"bio": "Developer",
"avatarUrl": "https://example.com/avatar.jpg" | null
}
}
要件:
- すべてのフィールドに型を付ける
- role は union 型で定義する
- createdAt は z.coerce.date() でDate型に変換する
- nullable なフィールドは .nullable() で明示する
- スキーマから型を推論して export する
生成されたスキーマ。
import { z } from 'zod'
const ProfileSchema = z.object({
bio: z.string(),
avatarUrl: z.string().url().nullable(),
})
const UserRoleSchema = z.enum(['admin', 'user', 'guest'])
export const UserSchema = z.object({
id: z.number().int().positive(),
name: z.string().min(1),
email: z.string().email(),
role: UserRoleSchema,
createdAt: z.coerce.date(),
profile: ProfileSchema,
})
export type User = z.infer<typeof UserSchema>
export type UserRole = z.infer<typeof UserRoleSchema>
Step 2: スキーマを使ったパース関数を生成させる
上記の UserSchema を使って、fetch のレスポンスをパースする関数を書いてください。
要件:
- パース失敗時は ZodError をスローする
- unknown 型で受け取って Zod でバリデーションする
- `as any` は使わない
import { UserSchema, type User } from './schemas/user'
import { ZodError } from 'zod'
export async function fetchUser(userId: number): Promise<User> {
const response = await fetch(`/api/users/${userId}`)
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`)
}
const json: unknown = await response.json()
return UserSchema.parse(json)
}
response.json() の戻り値を unknown で受けて、ZodでバリデーションしてからTypedな値を返す。これが as any を使わずに外部データを扱う正しいパターンだ。
tsc --noEmit パスを必須条件にするにはどう書くか?
型エラーを生成コードに残させないために、CLAUDE.mdに次の一文を加える。
## コード提出前の必須チェック
コードを生成・修正したら、必ず `npx tsc --noEmit` を実行すること。
型エラーがゼロになるまでコードを修正してから回答すること。
型エラーが残ったままのコードは提出しない。
この「型エラーがゼロになるまで修正」という制約が重要だ。これがないと、Claude Codeは型エラーが出ていても「機能としては動く」という判断でコードを返してくることがある。
--noEmit フラグはJavaScriptファイルを出力せずに型チェックだけを実行するオプションだ。CIでの型チェックにも使える。
{
"scripts": {
"type-check": "tsc --noEmit",
"type-check:watch": "tsc --noEmit --watch"
}
}
型エラーをClaudeに自動修正させるループはどう回すか?
tsc --noEmit の出力をそのままClaudeに渡して修正させるループが効率的だ。
# 型エラーを取得
npx tsc --noEmit 2>&1
出力例。
src/utils/parseResponse.ts:12:5 - error TS2322: Type 'any' is not assignable to type 'User'.
src/utils/parseResponse.ts:18:3 - error TS7006: Parameter 'data' implicitly has an 'any' type.
Found 2 errors.
これをClaudeに渡す。
以下のTypeScriptエラーを修正してください。
エラー:
src/utils/parseResponse.ts:12:5 - error TS2322: Type 'any' is not assignable to type 'User'.
src/utils/parseResponse.ts:18:3 - error TS7006: Parameter 'data' implicitly has an 'any' type.
修正方針:
- `as any` を使わずに修正する
- unknown 型 + 型ガードか Zod を使う
- 修正後に `tsc --noEmit` でエラーがゼロになることを確認してから回答する
このループを回し続けると、最終的に型エラーがゼロのコードが出てくる。エラーが複数あるときは一度に渡してOKで、Claudeは全エラーをまとめて修正しようとする。
外部APIレスポンスの型付けはClaude Codeにどう任せるか?
SwaggerやOpenAPI仕様書がある場合は、それをClaudeに渡してZodスキーマに変換させるのが最速だ。
以下のOpenAPI仕様から、TypeScript + Zodのスキーマを生成してください。
仕様(抜粋):
---
components:
schemas:
Product:
type: object
required:
- id
- name
- price
properties:
id:
type: integer
name:
type: string
maxLength: 100
price:
type: number
minimum: 0
category:
type: string
enum: [electronics, clothing, food]
stock:
type: integer
nullable: true
---
要件:
- z.infer<typeof Schema> で型を export する
- バリデーション制約(maxLength・minimum)もZodに反映する
- nullable は .nullable() で表現する
OpenAPI仕様が複雑な場合は openapi-zod-client などのツールも選択肢になるが、Claudeに変換させる方が細かい調整がしやすい。
Prismaと連携して型推論を最大化するには?
PrismaはスキーマからTypeScriptの型を自動生成してくれる。Claude Codeにこの型を活用させる指示を書いておく。
## Prismaの型活用ルール(CLAUDE.mdに追記)
- Prismaが生成した型(`PrismaClient`・`Prisma.UserGetPayload` など)を積極的に使う
- `User` 型は `import type { User } from '@prisma/client'` から参照する
- クエリの戻り値には `Prisma.UserGetPayload<{include: {posts: true}}>` のような型を使う
- 独自の型定義でPrismaの型を上書きしない
Prismaの型を使ったコード例。
import type { Prisma } from '@prisma/client'
import { prisma } from '@/lib/prisma'
type UserWithPosts = Prisma.UserGetPayload<{
include: { posts: { include: { tags: true } } }
}>
export async function getUserWithPosts(userId: string): Promise<UserWithPosts | null> {
return prisma.user.findUnique({
where: { id: userId },
include: {
posts: {
include: { tags: true },
},
},
})
}
Prismaが生成する型を使えば、DBのスキーマ変更がTypeScriptの型エラーとして即座に検出できる。Claude Codeに「Prismaの型を使って」と一言添えるだけで、この恩恵を受けられる。
型安全なコード生成のためのCLAUDE.mdテンプレートはどう書くか?
ここまでの内容をまとめた CLAUDE.md テンプレートを公開する。このままプロジェクトに追加して使える。
## TypeScript型安全ポリシー
### 絶対禁止
- `as any` の使用(理由を問わず禁止)
- `@ts-ignore` の使用(`@ts-expect-error` は理由コメント付きで許可)
- 型注釈なしの関数定義(引数・戻り値の型は必須)
### 外部データの型付け
- 外部API・JSONは `unknown` で受けて Zod でバリデーション
- `response.json() as SomeType` のキャストは禁止
- Zodスキーマから `z.infer<typeof Schema>` で型を export する
### tsconfig.json準拠
strict: true / noImplicitAny: true / strictNullChecks: true / noUncheckedIndexedAccess: true
### 提出前チェック(必須)
コード生成・修正後は `npx tsc --noEmit` を実行し、エラーゼロを確認してから回答する。
### Zodの使い方
- スキーマ定義は `src/schemas/` ディレクトリに集約する
- `.parse()` はバリデーション失敗時にエラーをスロー(意図的な動作)
- `.safeParse()` はエラーハンドリングが必要な場合に使う
- スキーマファイルには型も一緒に export する
まとめ
Claude Codeに型安全なTypeScriptを生成させる鍵は「型安全を暗黙の了解にしない」ことだ。
- CLAUDE.mdにstrict設定を転記する — プロジェクト全体で一貫した型ポリシーが定着する
tsc --noEmitを必須チェックとして明記する — 型エラーが残ったコードが返ってこなくなる- 外部データはZodスキーマから始める —
as anyを使わない唯一の正道 - 型エラーログをそのまま渡す — フィードバックループで自動修正が効く
- Prismaの型を積極的に使わせる — DBスキーマとTypeScript型が自動で同期する
「anyを使わないで」と毎回言うのをやめて、CLAUDE.mdに型ポリシーを書いてしまうのが一番の近道だ。