Tu agente ejecuta otro Python: diagnostica su shell
Respuesta directa: si un comando funciona en tu terminal pero falla cuando lo ejecuta tu agente, lo más probable es que el agente no esté usando tu sesión. Cada llamada a su herramienta de shell suele arrancar un proceso nuevo, sin tu venv activado, sin el nvm use que hiciste antes y con un PATH distinto al tuyo.
El síntoma: en tu terminal pasa, en la del agente no
El patrón típico es este: le pides al agente que ejecute los tests y obtienes ModuleNotFoundError, command not found: pnpm o un error de sintaxis que solo tiene sentido con una versión antigua de Node. Tú lanzas exactamente el mismo comando y todo va bien.
El peligro para un junior no es el error en sí, sino lo que hace el agente después. Ante un ModuleNotFoundError, un agente con permisos de escritura puede intentar arreglarlo instalando el paquete con el pip global, cambiando imports o rebajando sintaxis para que encaje con un Node viejo. Todo eso "funciona" en su shell y rompe el proyecto en el tuyo o en CI.
Por eso, antes de aceptar cualquier cambio motivado por un error de entorno, confirma qué intérprete está usando el agente. Es un diagnóstico de 30 segundos.
La causa: el agente no hereda tu sesión interactiva
Lo que activas a mano vive en tu proceso de shell, no en el sistema. Cuando el agente lanza un comando, no escribe en tu terminal: crea un proceso hijo con su propio entorno.
- Claude Code: según la referencia de herramientas, las variables que exportas con
exporten un comando Bash no persisten al siguiente; para propagar estado indica usarCLAUDE_ENV_FILEo un hookSessionStart. Unsource .venv/bin/activateen una llamada no afecta a la siguiente. Ojo: la documentación genérica de la tool Bash de la API describe una sesión persistente, y esa contradicción está recogida en un issue del repositorio de Claude Code, cerrado sin cambios. Para Claude Code CLI, quédate con la referencia de Claude Code. - Codex CLI: el entorno que reciben los comandos lo controla
shell_environment_policy. Según la referencia de configuración,inheritadmiteall,coreonone, y hay filtros que pueden quitar variables. Si tu config usacoreonone, es normal que falten cosas que tú das por hechas. - nvm: su README lo define como un gestor "instalado por usuario e invocado por shell". El
nvm useque hiciste en tu pestaña no existe para otro proceso.
Además, muchos de estos ajustes se cargan desde .bashrc o .zshrc, que solo se leen en shells interactivos. Cómo inicializa cada herramienta su shell varía entre versiones y sistemas operativos, así que no lo supongas: compruébalo.
Confírmalo con un comando de diagnóstico
La forma fiable de saberlo es comparar dos salidas: la de tu terminal y la del agente. Este bloque imprime qué shell corre, si es interactivo, qué binarios encuentra y qué hay al principio del PATH.
echo "shell: $0 | flags: $-"
command -v python python3 pip node npm pnpm
python3 -c "import sys; print(sys.executable)"
node --version 2>/dev/null
echo "VIRTUAL_ENV=${VIRTUAL_ENV:-<vacío>}"
echo "$PATH" | tr ':' '\n' | head -n 8Cómo usarlo:
- Ejecútalo en tu terminal, con tu entorno activado como siempre, y guarda la salida.
- Pídele al agente: "Ejecuta exactamente este bloque y pégame la salida sin interpretarla".
- Compara línea a línea. Si en
$-aparece unai, el shell es interactivo; si no aparece, no esperes que lea tu.bashrc.
Lo que buscas son tres diferencias concretas: sys.executable fuera de .venv, VIRTUAL_ENV vacío o una versión de Node distinta a la de tu .nvmrc. Si las dos salidas coinciden, el problema no es el entorno y toca buscar en otra parte.
Tabla de diagnóstico: síntoma, causa y arreglo
Con la salida del diagnóstico delante, esta tabla te lleva al arreglo adecuado. Está pensada para proyectos Python y Node, que son los casos más frecuentes.
| Lo que ves en la salida del agente | Causa probable | Arreglo recomendado |
|---|---|---|
sys.executable apunta a /usr/bin/python3 y VIRTUAL_ENV está vacío | El venv no está activo en su shell | Usa uv run o .venv/bin/python explícito |
node --version no coincide con .nvmrc | nvm no se ha cargado en ese proceso | nvm exec o fijar el entorno al inicio de sesión |
command -v pnpm no devuelve nada | El binario está en una ruta que solo añade tu .zshrc | Script de proyecto (npm run) o hook de inicio |
Falta una variable como DATABASE_URL | Filtrado de variables (en Codex, shell_environment_policy) | Revisa inherit y filtros; no metas secretos en el repo |
| Todo coincide con tu terminal | No es el entorno | Reproduce el error tú y depura el código |
Arreglo 1: comandos que no dependen de activar nada
La solución más estable es que el comando no necesite estado previo. Así da igual cuántos procesos lance el agente o qué herramienta uses.
- Python con uv: según la documentación de uv,
uv runasegura que el entorno del proyecto está al día antes de ejecutar el comando, sin activación manual. - Python sin uv: llama al intérprete del venv por ruta:
.venv/bin/python -m pytest. - Node con nvm:
nvm execynvm runejecutan el comando con la versión indicada y, según el README de nvm, respetan el.nvmrc. Requiere que nvm esté cargado en ese shell, cosa que no siempre ocurre. - Herramientas del proyecto: mejor
npm run testonpxque depender de binarios globales.
Luego díselo al agente en su fichero de instrucciones (CLAUDE.md, AGENTS.md o las rules de tu IDE). Esta plantilla es copiable; cambia los comandos por los de tu proyecto:
## Entorno de ejecución
- Python: ejecuta todo con `uv run` (ej.: `uv run pytest`). Nunca `pip install` global.
- Node: la versión está en `.nvmrc`. Usa `npm run <script>`, no binarios globales.
- Si un comando falla con ModuleNotFoundError o command not found:
PARA, no instales nada ni cambies imports. Muéstrame `command -v` y la versión.La última regla es la importante: convierte un error de entorno en una pregunta para ti, en vez de en un parche del agente.
Arreglo 2: fija el entorno al arrancar la sesión
Cuando no puedes evitar la activación (nvm, conda, toolchains con scripts de entorno), prepárala una vez al inicio de cada sesión del agente.
En Claude Code, los hooks SessionStart tienen acceso a CLAUDE_ENV_FILE, una ruta donde escribes export que se aplican a los comandos Bash posteriores, según la documentación de hooks. Este script, adaptado del ejemplo oficial, captura lo que cambian nvm y el venv:
#!/bin/bash
cd "$CLAUDE_PROJECT_DIR" || exit 0
ENV_BEFORE=$(export -p | sort)
source "$HOME/.nvm/nvm.sh"
nvm use >/dev/null # lee .nvmrc
[ -f .venv/bin/activate ] && source .venv/bin/activate
if [ -n "$CLAUDE_ENV_FILE" ]; then
ENV_AFTER=$(export -p | sort)
comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi
exit 0Guárdalo como .claude/hooks/env.sh, dale permisos de ejecución y regístralo en .claude/settings.json:
{
"hooks": {
"SessionStart": [
{ "hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/env.sh" }
] }
]
}
}Después, abre una sesión nueva y vuelve a pasar el comando de diagnóstico. No des el arreglo por bueno hasta que la salida del agente coincida con la tuya.
Y en Codex: revisa la política de entorno
En Codex CLI el punto de control es la tabla [shell_environment_policy] de config.toml. La documentación de configuración avanzada explica el orden: primero exclusiones automáticas, luego las tuyas, después los valores de set y por último la lista de inclusión.
[shell_environment_policy]
inherit = "all" # "core" o "none" recortan el entorno
set = { UV_PROJECT_ENVIRONMENT = ".venv" }
[shell_environment_policy.filters]
"AWS_*" = "exclude"Si el diagnóstico muestra variables que faltan, mira primero inherit y los filtros. La referencia también lista experimental_use_profile para cargar tu perfil de shell, pero está marcada como experimental: úsala solo si los comandos explícitos del arreglo 1 no te bastan.
Cuándo no aplicar estos arreglos
Fijar el entorno es útil en local, pero no siempre es la decisión correcta.
- Si el agente corre en un contenedor o sandbox remoto, no le copies tu entorno local: define las dependencias en la imagen o en el script de setup del proyecto. Si no, lo que funcione allí dependerá de tu máquina.
- Si el fallo también aparece en CI, el agente quizá tenga razón y el roto sea tu entorno local (un paquete instalado a mano que no está en el lockfile). Revisa tus dependencias antes de "arreglar" el shell del agente.
- Si quieres heredar todo con
inherit = "all"o volcando tu entorno completo, recuerda que también le pasas tokens y credenciales. Con agentes que ejecutan comandos sin pedir permiso, hereda solo lo necesario. - Si el diagnóstico coincide en ambos lados, no toques nada de esto: el problema está en el código o en los datos.
Como referencia práctica, no como dato: cuando aparezca un error de entorno, el diagnóstico va antes que cualquier diff. Si el agente ya ha cambiado imports o versiones para esquivar ese error, descarta esos cambios y empieza por comparar las dos salidas.
Preguntas frecuentes
¿Basta con arrancar el agente desde una terminal con el venv activado?
A menudo ayuda, porque el proceso del agente hereda las variables de esa terminal en el momento de lanzarlo. Pero depende de cómo la herramienta construya el entorno de cada comando, y en Codex lo filtra shell_environment_policy. Compruébalo con el diagnóstico en vez de darlo por hecho.
¿Puedo poner el PATH del venv en el bloque env de settings.json?
Claude Code acepta variables en la clave env de sus settings, pero un PATH con rutas absolutas no se puede compartir con tu equipo y te arriesgas a sustituir el PATH heredado en vez de ampliarlo. Si lo haces, usa .claude/settings.local.json y verifica con el diagnóstico. Para la mayoría de los proyectos, uv run o la ruta explícita al intérprete es más simple.
¿Y si uso conda en vez de venv?
El problema es el mismo: la activación vive en el shell. La alternativa sin estado es conda run -n tu_entorno comando, o capturar la activación en el hook de inicio igual que con nvm.