Capítulo 21: Referencia Rápida y Cookbook
Capítulo 21: Referencia Rápida y Cookbook
Este capítulo es una referencia, no un tutorial. Está organizado para búsqueda rápida y copia de código. Si necesitas contexto o explicaciones profundas, los capítulos anteriores cubren cada tema en detalle.
1. Cheat Sheet: ClaudeCodeOptions
Opciones Completas
| Opción | Tipo | Default | Descripción |
|---|---|---|---|
max_turns | int | None | None | Máximo de turnos del agente. None = ilimitado |
system_prompt | str | None | None | Instrucciones del sistema adicionales |
cwd | str | Path | None | CWD actual | Directorio de trabajo del agente |
allowed_tools | list[str] | None | None (todos) | Lista blanca de herramientas |
disallowed_tools | list[str] | None | None | Lista negra de herramientas |
permission_mode | PermissionMode | None | "default" | Modo de permisos de herramientas |
model | str | None | None (modelo default) | Modelo de Claude a usar |
session_id | str | None | None | ID para continuar sesión previa |
mcp_servers | dict[str, MCPServer] | None | None | Servidores MCP adicionales |
output_format | str | None | None | Formato de salida ("json", "text") |
verbose | bool | False | Logging verbose del CLI |
executable_path | str | None | None | Path al binario claude |
args | list[str] | None | None | Argumentos extra al CLI |
Valores de permission_mode
| Valor | Descripción | Cuándo Usar |
|---|---|---|
"default" | Claude pregunta confirmación para operaciones destructivas | Uso general |
"acceptEdits" | Auto-acepta ediciones de archivos, pregunta resto | CI/CD con archivos conocidos |
"bypassPermissions" | Auto-acepta todo sin preguntar | Entornos sandbox, testing |
"plan" | Solo planifica, no ejecuta herramientas | Revisión antes de ejecutar |
Python — opciones más usadas
from claude_code_sdk import query, ClaudeCodeOptions
from pathlib import Path
opts = ClaudeCodeOptions(
max_turns=10,
system_prompt="Eres un asistente de código experto en Python.",
cwd=Path("/mi/proyecto"),
allowed_tools=["Read", "Write", "Bash"],
permission_mode="acceptEdits",
model="claude-opus-4-5",
)
async for msg in query(prompt="Analiza el proyecto", options=opts):
pass
TypeScript — opciones más usadas
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
const opts: ClaudeCodeOptions = {
maxTurns: 10,
systemPrompt: "Eres un asistente de código experto en TypeScript.",
cwd: "/mi/proyecto",
allowedTools: ["Read", "Write", "Bash"],
permissionMode: "acceptEdits",
model: "claude-opus-4-5",
};
for await (const msg of query({ prompt: "Analiza el proyecto", options: opts })) {
// procesar mensajes
}
Nombres Python vs TypeScript
| Python | TypeScript |
|---|---|
max_turns | maxTurns |
system_prompt | systemPrompt |
cwd | cwd |
allowed_tools | allowedTools |
disallowed_tools | disallowedTools |
permission_mode | permissionMode |
session_id | sessionId |
mcp_servers | mcpServers |
output_format | outputFormat |
executable_path | executablePath |
2. Cheat Sheet: Tipos de Mensajes
Tabla de Tipos
| Tipo | Cuándo Aparece | Propiedades Clave |
|---|---|---|
AssistantMessage | Claude genera texto o usa herramientas | content: list[ContentBlock] |
UserMessage | Input del usuario (raro en SDK) | content: list[ContentBlock] |
ResultMessage | Al final del stream, siempre | subtype, cost_usd, session_id, total_cost_usd |
SystemMessage | Mensajes del sistema del CLI | subtype, data |
Subtipos de ResultMessage
subtype | Significa |
|---|---|
"success" | El agente completó correctamente |
"error_during_execution" | Ocurrió un error durante la ejecución |
"max_turns_reached" | Se alcanzó el límite max_turns |
Content Blocks dentro de AssistantMessage
| Tipo de Block | Propiedades | Cuándo |
|---|---|---|
TextBlock | text: str | Claude genera texto |
ToolUseBlock | id, name, input | Claude llama una herramienta |
Snippets por tipo
Python — filtrar solo texto de AssistantMessage:
from claude_code_sdk import query, AssistantMessage, TextBlock
async for msg in query(prompt="Hola", options=opts):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(block.text, end="", flush=True)
TypeScript — filtrar solo texto:
import { query, AssistantMessage, TextBlock } from "@anthropic-ai/claude-code-sdk";
for await (const msg of query({ prompt: "Hola", options: opts })) {
if (msg.type === "assistant") {
for (const block of msg.content) {
if (block.type === "text") {
process.stdout.write(block.text);
}
}
}
}
Python — capturar ResultMessage:
from claude_code_sdk import query, ResultMessage
result = None
async for msg in query(prompt="...", options=opts):
if isinstance(msg, ResultMessage):
result = msg
if result:
print(f"Estado: {result.subtype}")
print(f"Costo: ${result.cost_usd:.4f}")
print(f"Costo total sesión: ${result.total_cost_usd:.4f}")
print(f"Session ID: {result.session_id}")
TypeScript — capturar ResultMessage:
import { query, ResultMessage } from "@anthropic-ai/claude-code-sdk";
let result: ResultMessage | null = null;
for await (const msg of query({ prompt: "...", options: opts })) {
if (msg.type === "result") {
result = msg;
}
}
if (result) {
console.log(`Estado: ${result.subtype}`);
console.log(`Costo: $${result.costUsd?.toFixed(4)}`);
console.log(`Session ID: ${result.sessionId}`);
}
Python — capturar uso de herramientas:
from claude_code_sdk import query, AssistantMessage, ToolUseBlock
async for msg in query(prompt="...", options=opts):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, ToolUseBlock):
print(f"Herramienta: {block.name}")
print(f"Input: {block.input}")
TypeScript — capturar uso de herramientas:
for await (const msg of query({ prompt: "...", options: opts })) {
if (msg.type === "assistant") {
for (const block of msg.content) {
if (block.type === "tool_use") {
console.log(`Herramienta: ${block.name}`);
console.log(`Input: ${JSON.stringify(block.input)}`);
}
}
}
}
3. Cheat Sheet: Herramientas
Herramientas Nativas del CLI
| Herramienta | Descripción | Parámetros Clave | permission_mode Requerido |
|---|---|---|---|
Read | Lee archivos | file_path | default |
Write | Crea/sobreescribe archivos | file_path, content | acceptEdits o bypassPermissions |
Edit | Edita secciones de archivos | file_path, old_string, new_string | acceptEdits o bypassPermissions |
MultiEdit | Múltiples ediciones en un archivo | file_path, edits: list | acceptEdits o bypassPermissions |
Bash | Ejecuta comandos de shell | command, timeout? | default (pregunta por destructivos) |
Glob | Busca archivos por patrón | pattern, path? | default |
Grep | Busca contenido en archivos | pattern, path?, glob? | default |
LS | Lista directorio | path | default |
Task | Lanza sub-agente | description, prompt | default |
TodoWrite | Escribe lista de tareas | todos: list | default |
TodoRead | Lee lista de tareas actual | — | default |
WebFetch | Descarga URL | url, prompt? | default |
WebSearch | Búsqueda web | query | default |
Cómo Restringir Herramientas
Python:
# Solo lectura
opts = ClaudeCodeOptions(
allowed_tools=["Read", "Glob", "Grep", "LS"],
permission_mode="default",
)
# Sin herramientas web
opts = ClaudeCodeOptions(
disallowed_tools=["WebFetch", "WebSearch"],
)
# Solo Bash (para scripts)
opts = ClaudeCodeOptions(
allowed_tools=["Bash"],
permission_mode="bypassPermissions",
)
TypeScript:
// Solo lectura
const opts: ClaudeCodeOptions = {
allowedTools: ["Read", "Glob", "Grep", "LS"],
permissionMode: "default",
};
// Sin herramientas web
const opts: ClaudeCodeOptions = {
disallowedTools: ["WebFetch", "WebSearch"],
};
4. Cheat Sheet: Hooks
Tipos de Eventos de Hook
| Evento | Cuándo se Dispara | Recibe | Puede Retornar |
|---|---|---|---|
PreToolUse | Antes de cada llamada a herramienta | tool_name, tool_input, session_id | {"action": "block", "reason": "..."} para bloquear, {"action": "approve"} para aprobar |
PostToolUse | Después de cada llamada a herramienta | tool_name, tool_input, tool_output | Ignorado (solo observación) |
Notification | Cuando Claude envía notificación | message, session_id | Ignorado |
Stop | Cuando el agente termina | session_id, result | Ignorado |
SubagentStop | Cuando un sub-agente termina | session_id, result | Ignorado |
Configuración de Hooks (settings.json)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|Bash",
"hooks": [
{
"type": "command",
"command": "python /hooks/auditar.py"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python /hooks/notificar.py"
}
]
}
]
}
}
Snippets de Hooks Mínimos
Python — hook PreToolUse (bloquear archivos .env):
#!/usr/bin/env python3
import json, sys
data = json.loads(sys.stdin.read())
tool = data.get("tool_name", "")
inp = data.get("tool_input", {})
if tool in ("Write", "Edit") and ".env" in inp.get("file_path", ""):
print(json.dumps({"action": "block", "reason": "No se permiten modificaciones a .env"}))
sys.exit(0)
print(json.dumps({"action": "approve"}))
Python — hook PostToolUse (log de auditoría):
#!/usr/bin/env python3
import json, sys
from datetime import datetime
data = json.loads(sys.stdin.read())
with open("/var/log/agente-audit.log", "a") as f:
f.write(json.dumps({
"ts": datetime.utcnow().isoformat(),
"tool": data.get("tool_name"),
"input": data.get("tool_input"),
}) + "\n")
Python — hook Stop (notificación):
#!/usr/bin/env python3
import json, sys, subprocess
data = json.loads(sys.stdin.read())
result = data.get("result", {})
subtype = result.get("subtype", "unknown")
subprocess.run(["notify-send", f"Agente terminó: {subtype}"])
5. Cookbook: Recetas de Uso Frecuente
Receta 1: Hello World Mínimo
Cuándo usar: Primer test de que el SDK funciona.
Python:
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions
async def main():
async for msg in query(
prompt="Di 'hola mundo' en español",
options=ClaudeCodeOptions(max_turns=1),
):
print(msg)
asyncio.run(main())
TypeScript:
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
async function main() {
for await (const msg of query({
prompt: "Di 'hola mundo' en español",
options: { maxTurns: 1 },
})) {
console.log(msg);
}
}
main();
Receta 2: Extraer Solo el Resultado Final
Cuándo usar: Solo necesitas el texto de respuesta de Claude, sin metadata.
Python:
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions, AssistantMessage, TextBlock
async def get_text(prompt: str) -> str:
partes = []
async for msg in query(
prompt=prompt,
options=ClaudeCodeOptions(max_turns=1),
):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
partes.append(block.text)
return "".join(partes)
resultado = asyncio.run(get_text("¿Cuánto es 2+2?"))
print(resultado)
TypeScript:
import { query, ClaudeCodeOptions, AssistantMessage } from "@anthropic-ai/claude-code-sdk";
async function getText(prompt: string): Promise<string> {
const parts: string[] = [];
for await (const msg of query({ prompt, options: { maxTurns: 1 } })) {
if (msg.type === "assistant") {
for (const block of msg.content) {
if (block.type === "text") {
parts.push(block.text);
}
}
}
}
return parts.join("");
}
const resultado = await getText("¿Cuánto es 2+2?");
console.log(resultado);
Receta 3: Streaming de Texto en Tiempo Real
Cuándo usar: Mostrar la respuesta mientras llega, sin esperar al final.
Python:
import asyncio
import sys
from claude_code_sdk import query, ClaudeCodeOptions, AssistantMessage, TextBlock
async def stream_respuesta(prompt: str):
async for msg in query(
prompt=prompt,
options=ClaudeCodeOptions(max_turns=1),
):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
sys.stdout.write(block.text)
sys.stdout.flush()
print() # newline final
asyncio.run(stream_respuesta("Explica qué es un closure en 3 párrafos"))
TypeScript:
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
async function streamRespuesta(prompt: string) {
for await (const msg of query({ prompt, options: { maxTurns: 1 } })) {
if (msg.type === "assistant") {
for (const block of msg.content) {
if (block.type === "text") {
process.stdout.write(block.text);
}
}
}
}
process.stdout.write("\n");
}
await streamRespuesta("Explica qué es un closure en 3 párrafos");
Receta 4: Agente que Analiza un Directorio
Cuándo usar: Obtener resumen o análisis de un proyecto existente.
Python:
import asyncio
from pathlib import Path
from claude_code_sdk import query, ClaudeCodeOptions, AssistantMessage, TextBlock
async def analizar_directorio(path: str) -> str:
resultado = []
opts = ClaudeCodeOptions(
cwd=Path(path),
allowed_tools=["Read", "Glob", "Grep", "LS"],
max_turns=5,
system_prompt="Analiza el proyecto y produce un resumen conciso.",
)
async for msg in query(
prompt="Lista los archivos principales y resume la arquitectura del proyecto.",
options=opts,
):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
resultado.append(block.text)
return "".join(resultado)
print(asyncio.run(analizar_directorio("/mi/proyecto")))
TypeScript:
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
async function analizarDirectorio(path: string): Promise<string> {
const parts: string[] = [];
const opts: ClaudeCodeOptions = {
cwd: path,
allowedTools: ["Read", "Glob", "Grep", "LS"],
maxTurns: 5,
systemPrompt: "Analiza el proyecto y produce un resumen conciso.",
};
for await (const msg of query({
prompt: "Lista los archivos principales y resume la arquitectura.",
options: opts,
})) {
if (msg.type === "assistant") {
for (const block of msg.content) {
if (block.type === "text") parts.push(block.text);
}
}
}
return parts.join("");
}
Receta 5: Agente que Crea un Archivo
Cuándo usar: Generar código o contenido y guardarlo en disco.
Python:
import asyncio
from pathlib import Path
from claude_code_sdk import query, ClaudeCodeOptions, ResultMessage
async def crear_archivo(descripcion: str, ruta_destino: str):
opts = ClaudeCodeOptions(
cwd=Path(ruta_destino).parent,
allowed_tools=["Write"],
permission_mode="bypassPermissions",
max_turns=3,
)
async for msg in query(
prompt=f"Crea el archivo {ruta_destino} con el siguiente contenido: {descripcion}",
options=opts,
):
if isinstance(msg, ResultMessage):
print(f"Estado: {msg.subtype}")
asyncio.run(crear_archivo("función Python que ordena una lista", "/tmp/ordenar.py"))
TypeScript:
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
import path from "path";
async function crearArchivo(descripcion: string, rutaDestino: string) {
const opts: ClaudeCodeOptions = {
cwd: path.dirname(rutaDestino),
allowedTools: ["Write"],
permissionMode: "bypassPermissions",
maxTurns: 3,
};
for await (const msg of query({
prompt: `Crea el archivo ${rutaDestino} con: ${descripcion}`,
options: opts,
})) {
if (msg.type === "result") {
console.log(`Estado: ${msg.subtype}`);
}
}
}
Receta 6: Agente que Ejecuta Tests
Cuándo usar: Correr la suite de tests y reportar resultado.
Python:
import asyncio
from pathlib import Path
from claude_code_sdk import query, ClaudeCodeOptions, AssistantMessage, TextBlock
async def ejecutar_tests(proyecto: str) -> dict:
salida = []
opts = ClaudeCodeOptions(
cwd=Path(proyecto),
allowed_tools=["Bash"],
permission_mode="bypassPermissions",
max_turns=3,
system_prompt="Ejecuta los tests y reporta: total, pasados, fallados.",
)
async for msg in query(
prompt="Ejecuta los tests del proyecto y dame el resumen.",
options=opts,
):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
salida.append(block.text)
return {"salida": "".join(salida)}
resultado = asyncio.run(ejecutar_tests("/mi/proyecto"))
print(resultado["salida"])
TypeScript:
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
async function ejecutarTests(proyecto: string): Promise<string> {
const salida: string[] = [];
const opts: ClaudeCodeOptions = {
cwd: proyecto,
allowedTools: ["Bash"],
permissionMode: "bypassPermissions",
maxTurns: 3,
systemPrompt: "Ejecuta los tests y reporta: total, pasados, fallados.",
};
for await (const msg of query({
prompt: "Ejecuta los tests del proyecto y dame el resumen.",
options: opts,
})) {
if (msg.type === "assistant") {
for (const block of msg.content) {
if (block.type === "text") salida.push(block.text);
}
}
}
return salida.join("");
}
Receta 7: Agente con Sistema de Logging (Hooks)
Cuándo usar: Auditar todas las acciones del agente para debugging o compliance.
Python — hook de auditoría (/hooks/audit.py):
#!/usr/bin/env python3
import json, sys
from datetime import datetime
from pathlib import Path
LOG_FILE = Path("/var/log/claude-audit.jsonl")
data = json.loads(sys.stdin.read())
entry = {
"ts": datetime.utcnow().isoformat(),
"event": data.get("hook_event_name"),
"tool": data.get("tool_name"),
"input": data.get("tool_input"),
"session": data.get("session_id"),
}
with LOG_FILE.open("a") as f:
f.write(json.dumps(entry) + "\n")
Python — leer log de auditoría:
import json
from pathlib import Path
def leer_audit_log(path: str = "/var/log/claude-audit.jsonl"):
lineas = Path(path).read_text().strip().split("\n")
return [json.loads(l) for l in lineas if l]
for entrada in leer_audit_log():
print(f"{entrada['ts']} | {entrada['tool']} | {entrada['input']}")
TypeScript — leer log de auditoría:
import { readFileSync } from "fs";
function leerAuditLog(path = "/var/log/claude-audit.jsonl") {
return readFileSync(path, "utf-8")
.trim()
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line));
}
for (const entrada of leerAuditLog()) {
console.log(`${entrada.ts} | ${entrada.tool} | ${JSON.stringify(entrada.input)}`);
}
Receta 8: Agente con Timeout
Cuándo usar: Evitar que el agente se cuelgue en tareas largas.
Python:
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions, ResultMessage
async def query_con_timeout(prompt: str, timeout_seg: int = 30) -> str | None:
salida = []
try:
async with asyncio.timeout(timeout_seg):
async for msg in query(
prompt=prompt,
options=ClaudeCodeOptions(max_turns=10),
):
if isinstance(msg, ResultMessage):
return msg.subtype
except asyncio.TimeoutError:
print(f"Timeout después de {timeout_seg}s")
return None
resultado = asyncio.run(query_con_timeout("Analiza este proyecto", timeout_seg=60))
TypeScript:
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
async function queryConTimeout(prompt: string, timeoutMs = 30_000): Promise<string | null> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
for await (const msg of query({ prompt, options: { maxTurns: 10 } })) {
if (msg.type === "result") {
clearTimeout(timer);
return msg.subtype;
}
}
} catch (err) {
if ((err as Error).name === "AbortError") {
console.log(`Timeout después de ${timeoutMs}ms`);
return null;
}
throw err;
} finally {
clearTimeout(timer);
}
return null;
}
Receta 9: Agente con Retry Automático
Cuándo usar: Resiliencia ante errores transitorios de red o API.
Python:
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions, ResultMessage
from claude_code_sdk.errors import CLIConnectionError
async def query_con_retry(
prompt: str,
max_intentos: int = 3,
espera_base: float = 2.0,
) -> str | None:
for intento in range(max_intentos):
try:
async for msg in query(
prompt=prompt,
options=ClaudeCodeOptions(max_turns=5),
):
if isinstance(msg, ResultMessage):
return msg.subtype
except CLIConnectionError as e:
if intento == max_intentos - 1:
raise
espera = espera_base * (2 ** intento)
print(f"Intento {intento+1} fallido: {e}. Esperando {espera}s...")
await asyncio.sleep(espera)
return None
TypeScript:
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
async function queryConRetry(
prompt: string,
maxIntentos = 3,
espera = 2000,
): Promise<string | null> {
for (let intento = 0; intento < maxIntentos; intento++) {
try {
for await (const msg of query({ prompt, options: { maxTurns: 5 } })) {
if (msg.type === "result") return msg.subtype;
}
} catch (err) {
if (intento === maxIntentos - 1) throw err;
const delay = espera * Math.pow(2, intento);
console.log(`Intento ${intento + 1} fallido. Esperando ${delay}ms...`);
await new Promise((r) => setTimeout(r, delay));
}
}
return null;
}
Receta 10: Reanudar Sesión Anterior
Cuándo usar: Continuar una conversación o tarea donde se dejó.
Python:
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions, ResultMessage
# Primera sesión — guardar el session_id
async def primera_sesion() -> str:
session_id = None
async for msg in query(
prompt="Empieza a analizar el archivo main.py",
options=ClaudeCodeOptions(max_turns=5, cwd="/mi/proyecto"),
):
if isinstance(msg, ResultMessage):
session_id = msg.session_id
return session_id
# Segunda sesión — continuar
async def continuar_sesion(session_id: str):
async for msg in query(
prompt="Continúa el análisis que empezaste",
options=ClaudeCodeOptions(
max_turns=5,
cwd="/mi/proyecto",
session_id=session_id,
),
):
pass
sid = asyncio.run(primera_sesion())
print(f"Session ID guardado: {sid}")
asyncio.run(continuar_sesion(sid))
TypeScript:
import { query, ClaudeCodeOptions } from "@anthropic-ic/claude-code-sdk";
async function primeraSesion(): Promise<string | undefined> {
let sessionId: string | undefined;
for await (const msg of query({
prompt: "Empieza a analizar el archivo main.ts",
options: { maxTurns: 5, cwd: "/mi/proyecto" },
})) {
if (msg.type === "result") sessionId = msg.sessionId;
}
return sessionId;
}
async function continuarSesion(sessionId: string) {
for await (const msg of query({
prompt: "Continúa el análisis que empezaste",
options: { maxTurns: 5, cwd: "/mi/proyecto", sessionId },
})) {
// procesar
}
}
Receta 11: Múltiples Agentes en Paralelo
Cuándo usar: Analizar varios archivos o módulos simultáneamente.
Python:
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions, AssistantMessage, TextBlock
async def analizar_modulo(modulo: str) -> tuple[str, str]:
partes = []
async for msg in query(
prompt=f"Resume en 2 oraciones qué hace el módulo {modulo}",
options=ClaudeCodeOptions(
cwd="/mi/proyecto",
allowed_tools=["Read", "Glob"],
max_turns=3,
),
):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
partes.append(block.text)
return modulo, "".join(partes)
async def analizar_todos():
modulos = ["auth", "payments", "notifications", "reports"]
tareas = [analizar_modulo(m) for m in modulos]
resultados = await asyncio.gather(*tareas)
for modulo, resumen in resultados:
print(f"\n## {modulo}\n{resumen}")
asyncio.run(analizar_todos())
TypeScript:
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
async function analizarModulo(modulo: string): Promise<[string, string]> {
const parts: string[] = [];
for await (const msg of query({
prompt: `Resume en 2 oraciones qué hace el módulo ${modulo}`,
options: { cwd: "/mi/proyecto", allowedTools: ["Read", "Glob"], maxTurns: 3 },
})) {
if (msg.type === "assistant") {
for (const block of msg.content) {
if (block.type === "text") parts.push(block.text);
}
}
}
return [modulo, parts.join("")];
}
async function analizarTodos() {
const modulos = ["auth", "payments", "notifications", "reports"];
const resultados = await Promise.all(modulos.map(analizarModulo));
for (const [modulo, resumen] of resultados) {
console.log(`\n## ${modulo}\n${resumen}`);
}
}
Receta 12: Agente con Herramienta Custom (MCP In-Process)
Cuándo usar: Exponer una función propia como herramienta al agente.
Python:
import asyncio
import json
from claude_code_sdk import query, ClaudeCodeOptions
# El MCP in-process se define como servidor MCP simple en un proceso separado
# Para uso básico, usa system_prompt para describir la herramienta como función Bash
opts = ClaudeCodeOptions(
allowed_tools=["Bash"],
permission_mode="bypassPermissions",
system_prompt="""Tienes acceso a una herramienta custom via Bash:
Para consultar la base de datos interna usa: python /tools/db_query.py '<sql>'
""",
max_turns=5,
)
async def main():
async for msg in query(
prompt="Cuántos usuarios se registraron hoy",
options=opts,
):
pass
asyncio.run(main())
TypeScript:
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
const opts: ClaudeCodeOptions = {
allowedTools: ["Bash"],
permissionMode: "bypassPermissions",
systemPrompt: `Tienes acceso a una herramienta custom via Bash:
Para consultar la BD interna usa: node /tools/db_query.js '<sql>'`,
maxTurns: 5,
};
for await (const msg of query({
prompt: "Cuántos usuarios se registraron hoy",
options: opts,
})) {
// procesar
}
Receta 13: Agente con Playwright (MCP Externo)
Cuándo usar: Automatizar tareas de navegador web.
Python:
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions
from claude_code_sdk.types import MCPStdioServer
opts = ClaudeCodeOptions(
mcp_servers={
"playwright": MCPStdioServer(
command="npx",
args=["@playwright/mcp"],
)
},
allowed_tools=["mcp__playwright__browser_navigate", "mcp__playwright__browser_take_screenshot"],
permission_mode="bypassPermissions",
max_turns=10,
)
async def main():
async for msg in query(
prompt="Navega a https://example.com y toma un screenshot",
options=opts,
):
pass
asyncio.run(main())
TypeScript:
import { query, ClaudeCodeOptions, MCPStdioServer } from "@anthropic-ai/claude-code-sdk";
const opts: ClaudeCodeOptions = {
mcpServers: {
playwright: {
command: "npx",
args: ["@playwright/mcp"],
} as MCPStdioServer,
},
allowedTools: ["mcp__playwright__browser_navigate", "mcp__playwright__browser_take_screenshot"],
permissionMode: "bypassPermissions",
maxTurns: 10,
};
for await (const msg of query({
prompt: "Navega a https://example.com y toma un screenshot",
options: opts,
})) {
// procesar
}
Receta 14: Agente con PostgreSQL (MCP para Base de Datos)
Cuándo usar: Consultar o modificar datos en PostgreSQL con lenguaje natural.
Python:
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions
from claude_code_sdk.types import MCPStdioServer
DATABASE_URL = "postgresql://user:pass@localhost:5432/midb"
opts = ClaudeCodeOptions(
mcp_servers={
"postgres": MCPStdioServer(
command="npx",
args=["-y", "@modelcontextprotocol/server-postgres", DATABASE_URL],
)
},
permission_mode="bypassPermissions",
max_turns=5,
system_prompt="Solo ejecuta consultas SELECT. Nunca modifiques datos.",
)
async def consulta_nl(pregunta: str) -> str:
partes = []
async for msg in query(prompt=pregunta, options=opts):
if hasattr(msg, "content"):
for block in msg.content:
if hasattr(block, "text"):
partes.append(block.text)
return "".join(partes)
print(asyncio.run(consulta_nl("¿Cuáles son los 5 productos más vendidos este mes?")))
TypeScript:
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
const DATABASE_URL = "postgresql://user:pass@localhost:5432/midb";
const opts: ClaudeCodeOptions = {
mcpServers: {
postgres: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-postgres", DATABASE_URL],
},
},
permissionMode: "bypassPermissions",
maxTurns: 5,
systemPrompt: "Solo ejecuta consultas SELECT. Nunca modifiques datos.",
};
async function consultaNL(pregunta: string): Promise<string> {
const parts: string[] = [];
for await (const msg of query({ prompt: pregunta, options: opts })) {
if (msg.type === "assistant") {
for (const block of msg.content) {
if (block.type === "text") parts.push(block.text);
}
}
}
return parts.join("");
}
Receta 15: Agente con Rate Limiting
Cuándo usar: No superar un número máximo de queries por minuto.
Python:
import asyncio
import time
from claude_code_sdk import query, ClaudeCodeOptions
class RateLimiter:
def __init__(self, max_por_minuto: int):
self.max = max_por_minuto
self.ventana = 60.0
self.timestamps: list[float] = []
async def esperar(self):
ahora = time.monotonic()
# Limpiar timestamps viejos
self.timestamps = [t for t in self.timestamps if ahora - t < self.ventana]
if len(self.timestamps) >= self.max:
mas_antiguo = self.timestamps[0]
espera = self.ventana - (ahora - mas_antiguo)
if espera > 0:
await asyncio.sleep(espera)
self.timestamps.append(time.monotonic())
limiter = RateLimiter(max_por_minuto=10)
async def query_limitado(prompt: str):
await limiter.esperar()
async for msg in query(
prompt=prompt,
options=ClaudeCodeOptions(max_turns=3),
):
pass
TypeScript:
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
class RateLimiter {
private timestamps: number[] = [];
constructor(private maxPorMinuto: number, private ventana = 60_000) {}
async esperar(): Promise<void> {
const ahora = Date.now();
this.timestamps = this.timestamps.filter((t) => ahora - t < this.ventana);
if (this.timestamps.length >= this.maxPorMinuto) {
const masAntiguo = this.timestamps[0];
const espera = this.ventana - (ahora - masAntiguo);
if (espera > 0) await new Promise((r) => setTimeout(r, espera));
}
this.timestamps.push(Date.now());
}
}
const limiter = new RateLimiter(10);
async function queryLimitado(prompt: string) {
await limiter.esperar();
for await (const msg of query({ prompt, options: { maxTurns: 3 } })) {
// procesar
}
}
Receta 16: Agente Seguro Solo Lectura
Cuándo usar: Análisis de código sin riesgo de modificar nada.
Python:
import asyncio
from pathlib import Path
from claude_code_sdk import query, ClaudeCodeOptions, AssistantMessage, TextBlock
SOLO_LECTURA = ClaudeCodeOptions(
allowed_tools=["Read", "Glob", "Grep", "LS"],
permission_mode="default",
system_prompt="Solo puedes leer archivos. Nunca sugieras crear, modificar ni eliminar nada.",
max_turns=10,
)
async def analisis_seguro(proyecto: str, tarea: str) -> str:
partes = []
opts = ClaudeCodeOptions(
**{**SOLO_LECTURA.__dict__, "cwd": Path(proyecto)}
)
async for msg in query(prompt=tarea, options=opts):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
partes.append(block.text)
return "".join(partes)
TypeScript:
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
const baseOpts: ClaudeCodeOptions = {
allowedTools: ["Read", "Glob", "Grep", "LS"],
permissionMode: "default",
systemPrompt: "Solo puedes leer archivos. Nunca sugieras crear, modificar ni eliminar nada.",
maxTurns: 10,
};
async function analisisSeguro(proyecto: string, tarea: string): Promise<string> {
const parts: string[] = [];
for await (const msg of query({
prompt: tarea,
options: { ...baseOpts, cwd: proyecto },
})) {
if (msg.type === "assistant") {
for (const block of msg.content) {
if (block.type === "text") parts.push(block.text);
}
}
}
return parts.join("");
}
Receta 17: Agente con Hook de Seguridad
Cuándo usar: Bloquear acceso a archivos o rutas sensibles.
Python — hook de seguridad (/hooks/security.py):
#!/usr/bin/env python3
import json, sys, re
RUTAS_BLOQUEADAS = [
r"\.env$", r"\.env\.", r"secrets\.", r"credentials\.",
r"id_rsa", r"\.pem$", r"\.key$", r"config/database\.yml",
]
data = json.loads(sys.stdin.read())
tool = data.get("tool_name", "")
inp = data.get("tool_input", {})
if tool in ("Read", "Write", "Edit"):
ruta = inp.get("file_path", "")
for patron in RUTAS_BLOQUEADAS:
if re.search(patron, ruta):
print(json.dumps({
"action": "block",
"reason": f"Acceso bloqueado a archivo sensible: {ruta}",
}))
sys.exit(0)
if tool == "Bash":
cmd = inp.get("command", "")
if "env" in cmd and "export" not in cmd:
print(json.dumps({
"action": "block",
"reason": "Comando que accede a variables de entorno bloqueado",
}))
sys.exit(0)
print(json.dumps({"action": "approve"}))
TypeScript — lector del resultado del hook:
import { execSync } from "child_process";
function verificarPermisoHook(toolName: string, toolInput: object): boolean {
const input = JSON.stringify({ tool_name: toolName, tool_input: toolInput });
const result = execSync("python /hooks/security.py", {
input,
encoding: "utf-8",
});
const response = JSON.parse(result);
return response.action === "approve";
}
Receta 18: Agente que Manda Notificación al Terminar
Cuándo usar: Tareas largas donde quieres ser notificado cuando terminan.
Python — hook Stop (/hooks/notificar.py):
#!/usr/bin/env python3
import json, sys, os, smtplib
from email.message import EmailMessage
data = json.loads(sys.stdin.read())
result = data.get("result", {})
subtype = result.get("subtype", "unknown")
session = data.get("session_id", "?")
# Opción 1: notificación del sistema
try:
os.system(f'notify-send "Agente Claude terminó" "Estado: {subtype} | Sesión: {session}"')
except Exception:
pass
# Opción 2: email (configurar SMTP)
# msg = EmailMessage()
# msg["Subject"] = f"Agente Claude: {subtype}"
# msg["From"] = "[email protected]"
# msg["To"] = "[email protected]"
# msg.set_content(f"Sesión {session} terminó con estado: {subtype}")
# with smtplib.SMTP("smtp.empresa.com", 587) as s:
# s.send_message(msg)
TypeScript — webhook al terminar:
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
async function queryConNotificacion(prompt: string, webhookUrl: string) {
let result = null;
for await (const msg of query({ prompt, options: { maxTurns: 20 } })) {
if (msg.type === "result") result = msg;
}
// Notificar via webhook
await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
estado: result?.subtype,
sesion: result?.sessionId,
costo: result?.totalCostUsd,
}),
});
}
Receta 19: Calcular Costo de una Query
Cuándo usar: Monitorear gasto de API, presupuesto por tarea.
Python:
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions, ResultMessage
async def query_con_costo(prompt: str) -> dict:
result_msg = None
async for msg in query(
prompt=prompt,
options=ClaudeCodeOptions(max_turns=10),
):
if isinstance(msg, ResultMessage):
result_msg = msg
return {
"estado": result_msg.subtype if result_msg else None,
"costo_query": result_msg.cost_usd if result_msg else 0,
"costo_sesion_total": result_msg.total_cost_usd if result_msg else 0,
"session_id": result_msg.session_id if result_msg else None,
}
info = asyncio.run(query_con_costo("Analiza la arquitectura del proyecto"))
print(f"Costo esta query: ${info['costo_query']:.4f}")
print(f"Costo total sesión: ${info['costo_sesion_total']:.4f}")
TypeScript:
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
interface CostoResult {
estado: string | undefined;
costoQuery: number;
costoSesionTotal: number;
sessionId: string | undefined;
}
async function queryConCosto(prompt: string): Promise<CostoResult> {
let result = null;
for await (const msg of query({ prompt, options: { maxTurns: 10 } })) {
if (msg.type === "result") result = msg;
}
return {
estado: result?.subtype,
costoQuery: result?.costUsd ?? 0,
costoSesionTotal: result?.totalCostUsd ?? 0,
sessionId: result?.sessionId,
};
}
const info = await queryConCosto("Analiza la arquitectura del proyecto");
console.log(`Costo esta query: $${info.costoQuery.toFixed(4)}`);
console.log(`Costo total sesión: $${info.costoSesionTotal.toFixed(4)}`);
Receta 20: Agente como Endpoint FastAPI
Cuándo usar: Exponer un agente como servicio HTTP REST básico.
Python:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions, AssistantMessage, TextBlock, ResultMessage
app = FastAPI(title="Agente Claude API")
class QueryRequest(BaseModel):
prompt: str
max_turns: int = 10
cwd: str = "/workspace"
class QueryResponse(BaseModel):
respuesta: str
estado: str
costo_usd: float
@app.post("/query", response_model=QueryResponse)
async def ejecutar_query(req: QueryRequest):
partes = []
estado = "unknown"
costo = 0.0
try:
opts = ClaudeCodeOptions(
max_turns=req.max_turns,
cwd=req.cwd,
allowed_tools=["Read", "Glob", "Grep", "LS"],
permission_mode="default",
)
async for msg in query(prompt=req.prompt, options=opts):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
partes.append(block.text)
elif isinstance(msg, ResultMessage):
estado = msg.subtype
costo = msg.cost_usd or 0.0
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
return QueryResponse(
respuesta="".join(partes),
estado=estado,
costo_usd=costo,
)
# uvicorn app:app --host 0.0.0.0 --port 8000
TypeScript (Express):
import express from "express";
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
const app = express();
app.use(express.json());
app.post("/query", async (req, res) => {
const { prompt, maxTurns = 10, cwd = "/workspace" } = req.body;
const parts: string[] = [];
let estado = "unknown";
let costo = 0;
try {
const opts: ClaudeCodeOptions = {
maxTurns,
cwd,
allowedTools: ["Read", "Glob", "Grep", "LS"],
permissionMode: "default",
};
for await (const msg of query({ prompt, options: opts })) {
if (msg.type === "assistant") {
for (const block of msg.content) {
if (block.type === "text") parts.push(block.text);
}
} else if (msg.type === "result") {
estado = msg.subtype;
costo = msg.costUsd ?? 0;
}
}
res.json({ respuesta: parts.join(""), estado, costoUsd: costo });
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
app.listen(8000, () => console.log("Servidor en http://localhost:8000"));
6. Troubleshooting Guide
Diagnóstico Rápido
flowchart TD
Error[Error al ejecutar agente] --> TipoCLI{¿CLINotFoundError?}
TipoCLI -->|Sí| InstalarCLI[Instalar Claude Code CLI:\nnpm install -g @anthropic-ai/claude-code]
TipoCLI -->|No| TipoConex{¿CLIConnectionError?}
TipoConex -->|Sí| VerAPIKey[Verificar ANTHROPIC_API_KEY\ny conectividad de red]
TipoConex -->|No| TipoProceso{¿ProcessError?}
TipoProceso -->|Sí| VerExitCode[Ver exit_code y stderr\npara diagnóstico específico]
TipoProceso -->|No| TipoTimeout{¿Stream se corta?}
TipoTimeout -->|Sí| AgregarTimeout[Agregar asyncio.timeout()\no AbortController]
TipoTimeout -->|No| OtroError[Revisar sección específica\nen esta guía]
Problema 1: CLINotFoundError — Claude Code CLI No Instalado
Síntoma: CLINotFoundError: claude CLI not found
Diagnóstico:
which claude
claude --version
Solución:
# Instalar globalmente con npm
npm install -g @anthropic-ai/claude-code
# Verificar PATH
echo $PATH
export PATH="$HOME/.npm-global/bin:$PATH"
Python — verificar en código:
import shutil
if not shutil.which("claude"):
raise RuntimeError("Claude Code CLI no está instalado. Ejecuta: npm install -g @anthropic-ai/claude-code")
Problema 2: CLIConnectionError — Fallo de Conexión
Síntoma: CLIConnectionError: Failed to connect to CLI process
Causas comunes:
- API key no configurada
- Proceso del CLI crasheó al iniciar
- Versión incompatible del CLI
Solución:
export ANTHROPIC_API_KEY="sk-ant-..."
claude --version # verificar que el CLI responde
Python:
import os
if not os.environ.get("ANTHROPIC_API_KEY"):
raise EnvironmentError("ANTHROPIC_API_KEY no está configurada")
Problema 3: ProcessError con exit_code 1
Síntoma: ProcessError: Process exited with code 1
Diagnóstico:
from claude_code_sdk.errors import ProcessError
try:
async for msg in query(prompt="...", options=opts):
pass
except ProcessError as e:
print(f"Exit code: {e.exit_code}")
print(f"Stderr: {e.stderr}")
Causas y soluciones:
| Exit Code | Causa Probable | Solución |
|---|---|---|
| 1 | Error genérico del CLI | Ver stderr para detalles |
| 2 | Argumento inválido | Verificar ClaudeCodeOptions |
| 126 | Permiso denegado al ejecutar | chmod +x $(which claude) |
| 127 | CLI no encontrado | Ver Problema 1 |
Problema 4: API Key Inválida o No Configurada
Síntoma: Error 401 en stderr, o mensaje de “authentication failed”
Solución:
# Configurar la key
export ANTHROPIC_API_KEY="sk-ant-api03-..."
# Verificar que es válida
curl -H "x-api-key: $ANTHROPIC_API_KEY" https://api.anthropic.com/v1/models
Python — configurar programáticamente:
import os
os.environ["ANTHROPIC_API_KEY"] = "sk-ant-..." # Antes de importar el SDK
Problema 5: Agente en Loop Infinito
Síntoma: El agente no termina, consume muchos tokens.
Diagnóstico: Contar turnos y herramientas usadas.
Solución:
from claude_code_sdk import query, ClaudeCodeOptions, ResultMessage, AssistantMessage, ToolUseBlock
async def query_con_limite(prompt: str):
conteo_herramientas = 0
MAX_HERRAMIENTAS = 50
opts = ClaudeCodeOptions(
max_turns=20, # SIEMPRE poner max_turns
system_prompt="Sé conciso. Si no puedes resolver algo en 5 pasos, detente y reporta.",
)
async for msg in query(prompt=prompt, options=opts):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, ToolUseBlock):
conteo_herramientas += 1
if conteo_herramientas > MAX_HERRAMIENTAS:
raise RuntimeError(f"Loop detectado: {conteo_herramientas} herramientas usadas")
Problema 6: Agente No Usa las Herramientas Esperadas
Síntoma: El agente escribe código en lugar de ejecutarlo, o no lee archivos.
Solución:
opts = ClaudeCodeOptions(
# 1. Especifica las herramientas explícitamente
allowed_tools=["Read", "Bash", "Glob"],
# 2. Guía en el system_prompt
system_prompt="SIEMPRE usa las herramientas disponibles. No generes código de memoria.",
# 3. Sé específico en el prompt
)
await query(prompt="USA la herramienta Read para leer /mi/archivo.py y analiza su contenido", options=opts)
Problema 7: Write/Edit Fallan por Permisos
Síntoma: Claude intenta escribir pero la operación es bloqueada o necesita confirmación.
Solución:
# Para entornos automatizados:
opts = ClaudeCodeOptions(
permission_mode="bypassPermissions", # O "acceptEdits" para solo archivos
)
# Para revisar qué pide permiso, usar "plan" primero:
opts_plan = ClaudeCodeOptions(permission_mode="plan")
Verificar que el proceso tiene permisos del OS:
ls -la /ruta/al/archivo
whoami
Problema 8: El Stream se Corta sin ResultMessage
Síntoma: El async for termina pero nunca llega un ResultMessage.
Diagnóstico:
result_recibido = False
async for msg in query(prompt="...", options=opts):
if isinstance(msg, ResultMessage):
result_recibido = True
if not result_recibido:
print("ADVERTENCIA: Stream terminó sin ResultMessage — el proceso fue terminado externamente")
Causas: timeout del sistema, OOM killer, señal SIGTERM.
Problema 9: MCP Server No Inicia
Síntoma: Error: MCP server 'nombre' failed to start
Diagnóstico:
# Probar el servidor MCP manualmente
npx @modelcontextprotocol/server-postgres postgresql://...
# Verificar que npx puede encontrar el paquete
npx --yes @modelcontextprotocol/server-postgres --help
Python — agregar logging al servidor MCP:
from claude_code_sdk.types import MCPStdioServer
opts = ClaudeCodeOptions(
mcp_servers={
"postgres": MCPStdioServer(
command="npx",
args=["-y", "@modelcontextprotocol/server-postgres", DATABASE_URL],
env={"DEBUG": "mcp:*"}, # logging de debug del servidor
)
},
verbose=True, # logging del SDK
)
Problema 10: Session Resume No Funciona
Síntoma: session_id ignorado o error “session not found”
Causas:
- El
session_idexpiró (las sesiones tienen TTL) - El
session_ides de una instancia diferente del CLI - Se usó
session_idsin esperar a que el primer stream termine
Solución:
# CORRECTO: Esperar a que el primer stream complete
async def flujo_correcto():
session_id = None
# Primera sesión — completar TODA la iteración
async for msg in query(prompt="Tarea 1", options=ClaudeCodeOptions()):
if isinstance(msg, ResultMessage):
session_id = msg.session_id # Guardar AQUÍ
# Segunda sesión — solo después
if session_id:
async for msg in query(
prompt="Tarea 2",
options=ClaudeCodeOptions(session_id=session_id),
):
pass
Problema 11: Costo Inesperadamente Alto
Síntoma: Facturas de API mucho mayores de lo esperado.
Diagnóstico:
from claude_code_sdk import query, ClaudeCodeOptions, ResultMessage, AssistantMessage, ToolUseBlock
herramientas_usadas = []
async for msg in query(prompt="...", options=opts):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, ToolUseBlock):
herramientas_usadas.append(block.name)
elif isinstance(msg, ResultMessage):
print(f"Costo: ${msg.cost_usd:.4f}")
print(f"Herramientas usadas: {len(herramientas_usadas)}")
print(f"Distribución: {set(herramientas_usadas)}")
Soluciones:
- Agregar
max_turnspara limitar iteraciones - Usar
allowed_toolspara restringir herramientas costosas - Detectar loops de herramientas (misma herramienta repetida 10+ veces)
Problema 12: Output de Herramienta Muy Grande
Síntoma: Error de contexto demasiado largo, o el agente se queda sin contexto.
Solución:
opts = ClaudeCodeOptions(
system_prompt="""
Cuando leas archivos grandes (>500 líneas), lee solo las secciones relevantes.
Cuando ejecutes comandos que producen mucho output, usa head/tail/grep para filtrar.
Ejemplo: en lugar de `cat archivo.log`, usa `tail -n 100 archivo.log | grep ERROR`.
""",
)
Problema 13: Agente No Lee Archivos Correctamente
Síntoma: El agente dice que no encuentra archivos que existen.
Causa: El cwd incorrecto hace que las rutas relativas no resuelvan.
Solución:
from pathlib import Path
proyecto = Path("/mi/proyecto").resolve()
opts = ClaudeCodeOptions(
cwd=proyecto, # SIEMPRE usar path absoluto resuelto
system_prompt=f"El directorio raíz del proyecto es {proyecto}. Usa rutas relativas a este directorio.",
)
Problema 14: Subagentes Fallan Silenciosamente
Síntoma: Los sub-agentes lanzados con Task fallan pero el agente padre no reporta error.
Solución:
opts = ClaudeCodeOptions(
system_prompt="""
Cuando uses la herramienta Task para lanzar sub-agentes:
1. Siempre verifica el resultado del sub-agente
2. Si el sub-agente falla, repórtalo explícitamente con el error
3. No continúes si un paso crítico falló
""",
)
Problema 15: Tests Flaky de Agentes
Síntoma: Tests que pasan a veces y fallan otras.
Causas y soluciones:
| Causa | Solución |
|---|---|
| No-determinismo del LLM | Usar system_prompt muy específico y max_turns=1 para tests |
| Dependencia de red | Mockear el SDK en tests unitarios |
| Estado del filesystem | Limpiar fixtures en setUp/tearDown |
| Timeouts variables | Usar asyncio.timeout() con margen generoso |
# Test con fixture limpio
import tempfile, asyncio
from pathlib import Path
async def test_crea_archivo():
with tempfile.TemporaryDirectory() as tmp:
opts = ClaudeCodeOptions(
cwd=Path(tmp),
allowed_tools=["Write"],
permission_mode="bypassPermissions",
max_turns=2,
system_prompt="Crea exactamente el archivo pedido. Nada más.",
)
async for msg in query(prompt="Crea test.txt con el contenido 'hola'", options=opts):
pass
assert (Path(tmp) / "test.txt").read_text() == "hola"
Problema 16: TypeScript — Type Errors con el SDK
Síntoma: TypeScript no puede inferir tipos en los mensajes del stream.
Solución con type guards:
import { query, AssistantMessage, ResultMessage } from "@anthropic-ai/claude-code-sdk";
function esAssistantMessage(msg: unknown): msg is AssistantMessage {
return typeof msg === "object" && msg !== null && (msg as any).type === "assistant";
}
function esResultMessage(msg: unknown): msg is ResultMessage {
return typeof msg === "object" && msg !== null && (msg as any).type === "result";
}
for await (const msg of query({ prompt: "...", options: opts })) {
if (esAssistantMessage(msg)) {
// msg es AssistantMessage aquí
} else if (esResultMessage(msg)) {
// msg es ResultMessage aquí
}
}
Problema 17: Docker — Claude CLI No Encontrado
Síntoma: El agente funciona local pero falla en contenedor Docker.
Dockerfile correcto:
FROM node:20-slim
# Instalar Claude Code CLI
RUN npm install -g @anthropic-ai/claude-code
# Instalar Python y dependencias
RUN apt-get update && apt-get install -y python3 python3-pip
RUN pip3 install claude-code-sdk
WORKDIR /app
COPY . .
ENV ANTHROPIC_API_KEY=""
CMD ["python3", "mi_agente.py"]
Verificar en el contenedor:
docker run --rm mi-imagen which claude
docker run --rm mi-imagen claude --version
Problema 18: Hooks No Se Ejecutan
Síntoma: El hook está configurado pero nunca se invoca.
Checklist de debugging:
# 1. Verificar que el archivo settings.json es válido JSON
python3 -m json.tool ~/.claude/settings.json
# 2. Verificar permisos del script del hook
chmod +x /hooks/mi_hook.py
# 3. Verificar que el matcher coincide con el evento
# PreToolUse con matcher "Write" se dispara solo para Write, no para Edit
# 4. Probar el hook manualmente
echo '{"tool_name": "Write", "tool_input": {"file_path": "/tmp/test.txt"}}' | python /hooks/mi_hook.py
# 5. Verificar que el hook imprime JSON válido a stdout
Problema 19: Rate Limit de API (429)
Síntoma: Error: 429 Too Many Requests en stderr del CLI.
Solución — backoff exponencial:
import asyncio
from claude_code_sdk.errors import ProcessError
async def query_con_backoff(prompt: str, max_intentos: int = 5):
for intento in range(max_intentos):
try:
async for msg in query(prompt=prompt, options=opts):
yield msg
return
except ProcessError as e:
if "429" in str(e.stderr) and intento < max_intentos - 1:
espera = (2 ** intento) * 10 # 10s, 20s, 40s, 80s...
print(f"Rate limit. Esperando {espera}s...")
await asyncio.sleep(espera)
else:
raise
Problema 20: Memory Leak en Agentes de Larga Duración
Síntoma: El proceso Python/Node.js crece en memoria con el tiempo.
Causas y soluciones:
# PROBLEMA: acumular todos los mensajes en memoria
todos_los_mensajes = []
async for msg in query(prompt="...", options=opts):
todos_los_mensajes.append(msg) # Crece indefinidamente
# SOLUCIÓN: procesar y descartar
async for msg in query(prompt="...", options=opts):
if isinstance(msg, AssistantMessage):
procesar_inmediatamente(msg) # No guardar
elif isinstance(msg, ResultMessage):
guardar_solo_metadata(msg.session_id, msg.cost_usd)
# Para agentes de larga duración: usar sesiones cortas
async def procesar_en_lotes(items: list[str]):
for chunk in chunks(items, size=10):
# Nueva sesión por lote — no acumula contexto
async for msg in query(
prompt=f"Procesa: {chunk}",
options=ClaudeCodeOptions(max_turns=5),
):
pass
await asyncio.sleep(1) # Dar tiempo al GC
7. Decision Trees
¿Qué Modelo Usar?
flowchart TD
Start[Elegir modelo] --> Velocidad{¿Prioridad?}
Velocidad -->|Máxima calidad| Calidad[claude-opus-4-5\nMejor razonamiento\nMás costoso]
Velocidad -->|Balance calidad/velocidad| Balance[claude-sonnet-4-5\nRecomendado para\nla mayoría de casos]
Velocidad -->|Máxima velocidad y bajo costo| Rapido[claude-haiku-3-5\nTareas simples\nAlto volumen]
Balance --> Agente{¿Es un agente\ncon herramientas?}
Agente -->|Sí, muchas herramientas| Opus[Considerar Opus\npara mejor planificación]
Agente -->|No, solo texto| Sonnet[Sonnet es suficiente]
¿Qué permission_mode Usar?
flowchart TD
Start[Elegir permission_mode] --> Humano{¿Hay un humano\nviendo la terminal?}
Humano -->|Sí| Default["default"\nClaude pide confirmación\npara operaciones destructivas]
Humano -->|No| Automatizado{¿Es entorno\nautomatizado/CI?}
Automatizado -->|Solo lee archivos| ReadOnly["default" con\nallowed_tools de solo lectura]
Automatizado -->|Edita archivos conocidos| AcceptEdits["acceptEdits"\nAuto-acepta ediciones]
Automatizado -->|Sandbox aislado| Bypass["bypassPermissions"\nTodo automático\nSolo en ambientes seguros]
Automatizado -->|Revisar antes de ejecutar| Plan["plan"\nSolo planifica, no ejecuta]
¿Agente Único o Multi-Agente?
flowchart TD
Start[¿Agente único o multi-agente?] --> Paralelizable{¿Las tareas son\nparalelizables?}
Paralelizable -->|Sí| Independientes{¿Son independientes\nentre sí?}
Independientes -->|Sí| MultiParalelo[Multi-agente paralelo\nasyncio.gather]
Independientes -->|No| MultiSecuencial[Multi-agente secuencial\ncon paso de contexto]
Paralelizable -->|No| Grande{¿La tarea es\nmuy grande?}
Grande -->|Sí| Descomponer[Descomponer en sub-tareas\ncon Task tool]
Grande -->|No| Unico[Agente único\nes suficiente]
¿Session Resume o Nueva Sesión?
flowchart TD
Start[¿Reanudar sesión?] --> TieneID{¿Tienes un\nsession_id guardado?}
TieneID -->|No| Nueva[Nueva sesión]
TieneID -->|Sí| Reciente{¿Es reciente?\nmenos de 24h}
Reciente -->|No| Nueva
Reciente -->|Sí| Continua{¿La nueva tarea\nusa el mismo contexto?}
Continua -->|Sí| Resume[Usar session_id\npara continuar]
Continua -->|No| Decision{¿Necesitas el contexto\nde la sesión anterior?}
Decision -->|Sí| Resume
Decision -->|No| Nueva
¿MCP In-Process o Externo?
flowchart TD
Start[¿Cómo integrar herramienta custom?] --> Existe{¿Existe un servidor\nMCP para esto?}
Existe -->|Sí, en npm/pip| Externo[MCP externo con\nMCPStdioServer]
Existe -->|No| Complejidad{¿Qué tan compleja\nes la herramienta?}
Complejidad -->|Simple, 1-2 funciones| Bash["Bash tool + script\nEjemplo: python /tools/mi_tool.py"]
Complejidad -->|Compleja, necesita estado| InProcess[Implementar servidor\nMCP in-process\ncon SDK de MCP]
Complejidad -->|Ya existe como API| HTTP[MCP con transport\nSSE o HTTP]
8. Anti-Patrones Rápidos
Anti-Patrón 1: No Limitar max_turns
Problema: El agente puede iterar indefinidamente consumiendo tokens.
Anti-patrón:
opts = ClaudeCodeOptions() # max_turns=None por defecto
Correcto:
opts = ClaudeCodeOptions(max_turns=20) # Siempre poner un límite razonable
Anti-Patrón 2: bypassPermissions en Producción con Datos Reales
Problema: El agente puede eliminar archivos, bases de datos, ejecutar comandos destructivos sin confirmación.
Anti-patrón:
opts = ClaudeCodeOptions(permission_mode="bypassPermissions") # En servidor de producción
Correcto:
# En producción: default o acceptEdits + restricción de herramientas
opts = ClaudeCodeOptions(
permission_mode="acceptEdits",
allowed_tools=["Read", "Write"], # Sin Bash en producción sensible
)
Anti-Patrón 3: Guardar Toda la Historia de Mensajes en Memoria
Problema: En agentes de larga duración, acumular todos los mensajes llena la RAM.
Anti-patrón:
historial = []
async for msg in query(prompt="...", options=opts):
historial.append(msg) # Nunca se libera
Correcto: Procesar y descartar cada mensaje al recibirlo.
Anti-Patrón 4: Ignorar ResultMessage
Problema: No sabes si el agente terminó con éxito o con error.
Anti-patrón:
async for msg in query(prompt="...", options=opts):
if isinstance(msg, AssistantMessage):
procesar(msg)
# Nunca verificas si hubo error
Correcto: Siempre capturar y verificar ResultMessage.subtype.
Anti-Patrón 5: System Prompt Demasiado Vago
Problema: El agente interpreta la tarea de forma impredecible.
Anti-patrón:
opts = ClaudeCodeOptions(system_prompt="Ayuda con el código.")
Correcto:
opts = ClaudeCodeOptions(
system_prompt="""
Eres un agente de análisis de código. Tu tarea es:
1. Leer los archivos indicados con la herramienta Read
2. Identificar problemas de seguridad OWASP Top 10
3. Reportar: archivo, línea, tipo de problema, severidad
NO modifiques ningún archivo.
""",
)
Anti-Patrón 6: Usar el SDK sin Manejar Excepciones
Problema: Errores del CLI o de red derrumban la aplicación entera.
Anti-patrón:
async for msg in query(prompt="...", options=opts):
procesar(msg)
Correcto:
from claude_code_sdk.errors import CLINotFoundError, CLIConnectionError, ProcessError
try:
async for msg in query(prompt="...", options=opts):
procesar(msg)
except CLINotFoundError:
logging.error("Claude Code CLI no instalado")
except CLIConnectionError as e:
logging.error(f"Error de conexión: {e}")
except ProcessError as e:
logging.error(f"Error del proceso (exit {e.exit_code}): {e.stderr}")
Anti-Patrón 7: Concatenar Prompts con String Formatting Inseguro
Problema: Inyección de prompt si los datos vienen de usuarios externos.
Anti-patrón:
prompt = f"Analiza el archivo {user_input}" # user_input puede ser malicioso
Correcto:
import re
def sanitizar_path(path: str) -> str:
# Solo permitir caracteres seguros en rutas
if not re.match(r'^[a-zA-Z0-9_\-./]+$', path):
raise ValueError(f"Ruta no permitida: {path}")
return path
prompt = f"Analiza el archivo {sanitizar_path(user_input)}"
Anti-Patrón 8: MCP Servers sin Timeout de Inicio
Problema: Si el servidor MCP tarda en iniciar, el agente espera indefinidamente.
Anti-patrón:
opts = ClaudeCodeOptions(mcp_servers={"db": MCPStdioServer(command="npx", args=[...])})
Correcto:
async with asyncio.timeout(30): # 30s max para que el MCP inicie
async for msg in query(prompt="...", options=opts):
pass
Anti-Patrón 9: Lanzar Muchos Agentes Paralelos sin Control
Problema: Superar rate limits de la API o agotar recursos del sistema.
Anti-patrón:
tareas = [analizar(f) for f in archivos_1000]
resultados = await asyncio.gather(*tareas) # 1000 agentes a la vez
Correcto:
import asyncio
semaforo = asyncio.Semaphore(5) # Máximo 5 agentes a la vez
async def analizar_limitado(archivo):
async with semaforo:
return await analizar(archivo)
tareas = [analizar_limitado(f) for f in archivos_1000]
resultados = await asyncio.gather(*tareas)
Anti-Patrón 10: No Testear con Datos de Producción Reales
Problema: Los agentes se comportan diferente con datos reales vs datos de prueba.
Anti-patrón: Solo testear con archivos ficticios simples.
Correcto:
# Usar fixtures realistas que representen casos edge
FIXTURES = {
"archivo_grande": "x" * 100_000, # 100KB
"unicode": "こんにちは 🎉 Ñoño",
"binario": b"\x00\x01\x02".decode("latin-1"),
"rutas_profundas": "a/b/c/d/e/f/g/archivo.txt",
"nombres_especiales": "archivo con espacios & símbolos!.txt",
}
9. Índice de Variables de Entorno
| Variable | Descripción | Ejemplo |
|---|---|---|
ANTHROPIC_API_KEY | API key de Anthropic. Requerida. | sk-ant-api03-... |
ANTHROPIC_BASE_URL | URL base de la API (para proxies o enterprise) | https://api.empresa.com |
CLAUDE_CODE_MAX_OUTPUT_TOKENS | Límite de tokens de output por respuesta | 4096 |
CLAUDE_CODE_DEFAULT_MODEL | Modelo por defecto cuando no se especifica en opciones | claude-sonnet-4-5 |
CLAUDE_CODE_DISABLE_TELEMETRY | Deshabilitar telemetría del CLI (1 = deshabilitar) | 1 |
CLAUDE_CODE_SETTINGS_PATH | Path alternativo al archivo settings.json | /etc/claude/settings.json |
DEBUG | Habilitar logging de debug del CLI | claude:* |
NO_COLOR | Deshabilitar colores en output del CLI | 1 |
HTTP_PROXY / HTTPS_PROXY | Proxy para conexiones HTTP/HTTPS | http://proxy:3128 |
NODE_EXTRA_CA_CERTS | Certificados CA adicionales (para redes corporativas) | /etc/ssl/corp-ca.pem |
CLAUDE_CODE_HOOKS_DIR | Directorio alternativo para scripts de hooks | /opt/hooks |
Configurar en Código (Python)
import os
# Configurar antes de usar el SDK
os.environ.setdefault("ANTHROPIC_API_KEY", "sk-ant-...")
os.environ.setdefault("CLAUDE_CODE_DEFAULT_MODEL", "claude-sonnet-4-5")
os.environ.setdefault("CLAUDE_CODE_DISABLE_TELEMETRY", "1")
from claude_code_sdk import query, ClaudeCodeOptions
Configurar en Código (TypeScript)
// Antes de importar el SDK
process.env.ANTHROPIC_API_KEY ??= "sk-ant-...";
process.env.CLAUDE_CODE_DEFAULT_MODEL ??= "claude-sonnet-4-5";
import { query } from "@anthropic-ai/claude-code-sdk";
10. Tabla de Compatibilidad
SDK Python
claude-code-sdk | Python | anthropic | Estado |
|---|---|---|---|
| 0.0.14+ | 3.11+ | ≥0.50 | Actual ✓ |
| 0.0.10–0.0.13 | 3.10+ | ≥0.45 | Mantenimiento |
| < 0.0.10 | 3.9+ | ≥0.40 | Obsoleto |
SDK TypeScript/JavaScript
@anthropic-ai/claude-code-sdk | Node.js | TypeScript | Estado |
|---|---|---|---|
| 0.0.14+ | 18+ | 5.0+ | Actual ✓ |
| 0.0.10–0.0.13 | 16+ | 4.9+ | Mantenimiento |
| < 0.0.10 | 14+ | 4.7+ | Obsoleto |
Claude Code CLI
| CLI Version | SDK Python mínimo | SDK TS mínimo | Notas |
|---|---|---|---|
| 1.x+ | 0.0.14 | 0.0.14 | Versión actual. Recomendada. |
| 0.9.x | 0.0.10 | 0.0.10 | Funcional con limitaciones |
| 0.8.x | 0.0.5 | 0.0.5 | Sin soporte para hooks avanzados |
| < 0.8 | No compatible | No compatible | — |
Verificar Compatibilidad
# Verificar versiones instaladas
claude --version
python3 -c "import claude_code_sdk; print(claude_code_sdk.__version__)"
node -e "const s = require('@anthropic-ai/claude-code-sdk'); console.log(s.version ?? 'ver no exportada')"
# Python: verificar dependencias
pip show claude-code-sdk
# Node: verificar
npm list @anthropic-ai/claude-code-sdk
Plataformas Soportadas
| Plataforma | Python SDK | TypeScript SDK | Claude CLI |
|---|---|---|---|
| Linux x86_64 | ✓ | ✓ | ✓ |
| Linux ARM64 | ✓ | ✓ | ✓ |
| macOS x86_64 | ✓ | ✓ | ✓ |
| macOS ARM64 (M1/M2) | ✓ | ✓ | ✓ |
| Windows x86_64 | ✓ | ✓ | ✓ (WSL recomendado) |
| Docker Alpine | ✓ | ✓ | ✓ (con Node.js) |
Volver al inicio: Índice del Tutorial