Codex contra PowerShell: qué resuelve el Skill powershell-safe-invocation

A partir de una captura donde Codex corrige repetidamente un script en PowerShell, este artículo resume reglas prácticas para invocar PowerShell, escribir archivos y pasar argumentos de forma más segura en Windows.

Hace poco vi una captura con mucho efecto cómico: Codex intentaba corregir un archivo Markdown en PowerShell. Primero el here-string no se ejecutaba, luego fallaba la coincidencia CRLF/LF, después los backticks de Markdown eran interpretados por PowerShell, las barras invertidas de las rutas se comportaban de forma inestable y la sintaxis del argumento -split volvía a causar problemas. El modelo seguía diagnosticándose a sí mismo y parcheando el script. Al final, en los comentarios le pusieron un título muy acertado: el clásico GPT 5.5 contra PowerShell.

Captura de Codex corrigiendo repetidamente un script en PowerShell

La cadena de correcciones de la captura tiene gracia, pero los problemas de fondo no son raros.

Funciona como broma porque se parece mucho a una sesión real de desarrollo. PowerShell no es simplemente “Bash para Windows”. Tiene sus propias reglas de cadenas, reglas de enlace de parámetros, modelo de errores de cmdlets y forma de pasar argumentos a comandos nativos. Cuando un agente lleva hábitos de Bash a PowerShell, tareas aparentemente normales como escribir archivos, reemplazar texto o invocar programas externos pueden convertirse en una secuencia de pequeños fallos.

El skill powershell-safe-invocation de Misaka-Mikoto-Tech reúne estos tropiezos en un conjunto compacto de reglas. No se trata de memorizar más sintaxis de PowerShell. Se trata de decirle al agente que, al ejecutar comandos en Windows, elija primero formas de invocación con menos probabilidades de fallar.

El problema principal no es que PowerShell sea difícil, sino que los agentes lo tratan como Bash

La captura muestra varios problemas típicos.

La primera categoría son las cadenas y los saltos de línea. Markdown usa backticks, y PowerShell también trata los backticks como caracteres de escape. Un documento puede usar CRLF mientras el script intenta coincidir exactamente con LF. Si el inicio o el cierre de un here-string tiene mal la indentación o las comillas, puede que ni siquiera genere el texto esperado.

La segunda categoría son las rutas y los argumentos. Las rutas de Windows contienen barras invertidas por naturaleza, y los directorios suelen incluir espacios, corchetes o caracteres chinos. Si se construye un comando como una sola cadena enorme antes de ejecutarlo, un argumento puede dividirse de forma inesperada, o un fragmento de ruta puede interpretarse como escape, comodín, expresión regular o sintaxis de otro shell.

La tercera categoría es la gestión de errores. Los programas externos deben comprobarse con $LASTEXITCODE; los cmdlets de PowerShell deberían depender de errores terminantes y de $ErrorActionPreference = 'Stop'. Si un agente mezcla estos dos modelos, puede acabar creyendo que un comando tuvo éxito cuando no fue así.

Nada de esto es un fallo enorme por sí solo, pero consume atención. Los agentes también pueden seguir reparando una suposición equivocada simplemente porque “parece que el comando ya se ejecutó”, y el bucle se vuelve cada vez más enredado.

La regla central de este skill: no construir una cadena de comando gigante

La regla más importante de powershell-safe-invocation es no concatenar el ejecutable, las rutas, los argumentos y las comillas en una cadena de comando gigante.

Al invocar un programa nativo, cada argumento debe ser un elemento independiente del array:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$exe = 'C:\Path With Spaces\tool.exe'
$args = @(
    '--input'
    'C:\Data Folder\input.json'
    '--flag'
)

& $exe @args

$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
    throw "$exe failed with exit code $exitCode"
}

Esto parece más largo que un comando de una línea, pero separa los riesgos: una ruta es una ruta, un argumento es un argumento y la invocación es la invocación. El agente ya no tiene que adivinar si una capa de comillas pertenece a PowerShell, a cmd o al programa de destino.

Para cmdlets de PowerShell, suele encajar mejor el splatting con hashtable:

1
2
3
4
5
6
7
8
$params = @{
    LiteralPath = 'C:\Data[1]\input.txt'
    Destination = 'C:\Output'
    Force       = $true
    ErrorAction = 'Stop'
}

Copy-Item @params

El punto clave aquí es -LiteralPath. Las rutas reales deberían tratarse como literales por defecto. Usa parámetros que expanden comodines solo cuando esa expansión sea intencional. Para un agente, esto es mucho más estable que intentar decidir sobre la marcha si una ruta contiene [, ], * o ?.

No metas comandos complejos dentro de -Command

Muchos problemas de automatización en Windows empiezan con un comando como este:

1
cmd.exe /c pwsh.exe -Command "..."

En cuanto aparecen JSON, XML, expresiones regulares, pipelines, redirecciones, texto multilínea, rutas no ASCII o backticks de Markdown, las capas de escape se vuelven difíciles de controlar. Es complicado de leer para una persona y todavía más fácil de malinterpretar para un agente.

La recomendación del skill es simple: cuando el comando sea aunque sea un poco complejo, escribe un archivo temporal .ps1 y ejecútalo con pwsh.exe -NoLogo -NoProfile -NonInteractive -File script.ps1.

Esta regla es especialmente adecuada para agentes. Después de generar el archivo de script, el agente puede revisar el contenido, ejecutarlo y verificar las diferencias de archivos paso a paso. Si todo se mete en un único -Command, cuando algo falla solo queda una maraña de comillas y escapes para depurar.

powershell.exe no es pwsh.exe

Otro punto fácil de pasar por alto es la versión.

Instalar PowerShell 7 no hace que powershell.exe se convierta automáticamente en PowerShell 7. Normalmente:

  • powershell.exe es Windows PowerShell 5.1
  • pwsh.exe es PowerShell 7

Ambos difieren en el paso de argumentos a comandos nativos, valores predeterminados de codificación y otros detalles de comportamiento. Si un agente no sabe en qué shell se está ejecutando, debería comprobarlo primero:

1
2
$PSVersionTable.PSVersion
$PSNativeCommandArgumentPassing

No es un ritual. Muchos problemas de “en mi máquina funciona, pero cuando lo ejecuta el agente falla” vienen de diferencias en la versión del shell y las reglas de paso de argumentos.

Al escribir archivos, la codificación y los saltos de línea son asuntos de primer orden

La parte más interesante de la captura es cómo el agente da vueltas alrededor de reemplazos fallidos en un archivo Markdown. Ahí hay al menos tres supuestos ocultos:

  • cuál es la codificación real del archivo
  • si el archivo usa CRLF o LF
  • si el objetivo de reemplazo fue alterado por la sintaxis de cadenas de PowerShell

Cuando se mezclan un artículo en chino, bloques de código Markdown, front matter, shortcodes y backticks, es mejor no escribir el cuerpo con un here-string improvisado de PowerShell. O bien se usan APIs de archivo con codificación explícita, o bien el script maneja solo lógica de control ASCII y el texto no ASCII se escribe por una ruta más fiable.

Para flujos de trabajo con agentes, hay una costumbre adicional: verificar inmediatamente después de escribir. Como mínimo, comprobar que el archivo se puede leer como UTF-8, que el front matter está cerrado, que los enlaces Markdown no se rompieron y que el cuerpo no contiene signos de interrogación repetidos ni mojibake evidente. Los problemas de codificación son más baratos cuanto antes se encuentran.

Un orden de decisión más estable

La parte más práctica de este skill es que le da al agente un orden de decisión:

  1. Si basta con un cmdlet de PowerShell, usa el cmdlet.
  2. Al invocar un programa externo, usa & $exe @args.
  3. Cuando el comando sea complejo, escribe un archivo .ps1 y ejecútalo con pwsh.exe -File.
  4. Cuando sea necesario controlar con precisión los argumentos del proceso, usa ProcessStartInfo.ArgumentList.
  5. Usa Start-Process solo para comportamientos especiales como una ventana nueva, elevación o ejecución desacoplada.
  6. Usa cmd.exe /c solo cuando realmente se necesite la semántica de cmd.
  7. Deja Invoke-Expression como último recurso muy controlado.

Este orden convierte “se ejecuta” en “se puede explicar, comprobar y reproducir”. Eso importa para desarrolladores humanos, y todavía más para agentes.

Consejos prácticos para Codex

Si le pido a Codex que edite archivos en un repositorio de Windows, le daría algunas restricciones claras:

  • No usar escapes de estilo Bash como \" en PowerShell.
  • No concatenar rutas y argumentos en una gran cadena.
  • No usar Start-Process -ArgumentList para argumentos complejos.
  • No envolver llamadas normales con cmd.exe /c.
  • Preferir -LiteralPath para rutas reales de archivos.
  • Para scripts complejos, preferir escribir un .ps1 y ejecutarlo con pwsh.exe -File.
  • Después de escribir contenido en chino, japonés u otro texto no ASCII, comprobar la codificación y posibles señales de corrupción.

PowerShell no es el enemigo de los agentes. El verdadero problema es que los agentes caen demasiado fácilmente en el viejo hábito de “concatenar una cadena y dejar que el shell adivine”. Una vez que los límites de invocación están claros, PowerShell puede ofrecer una capacidad estructurada bastante fuerte.

Así que la conclusión de esta historia de “Codex contra PowerShell” no es que los agentes deban evitar Windows. Es que necesitan un manual táctico: apostar menos por las comillas y usar más estructura; meter menos cosas en un comando de una línea y conservar más estados intermedios que se puedan inspeccionar.

La broma está en la captura. La lección está en las reglas.

记录并分享
Creado con Hugo
Tema Stack diseñado por Jimmy