13 de septiembre de 2026

GitHub Actions en otoño de 2026: qué se rompe (y por qué node20 no es el problema)

Foto de Marco Orta Marco Orta | 18 min de lectura
Compartir
Portada tipográfica sobre fondo de terminal: las palabras GitHub Actions y debajo un diff de dos líneas, «- node20» en rojo y «+ node24» en verde
Tabla de Contenidos

    El 23 de septiembre de 2026 GitHub quita Node 20 de los runners de Actions, y casi todo el mundo lo está leyendo como «mis actions con using: node20 van a fallar». No van a fallar. Desde el 16 de junio el runner ya reescribe node20 a node24 antes de ejecutar, y lo mismo hace con node12 y node16. Lo que se quita el 23 es la salida de emergencia: la variable ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION deja de funcionar.

    Eso invierte quién está en riesgo. Si tu CI lleva verde desde junio sin esa variable, el 23 no te cambia nada en los runners de GitHub. Los que se rompen son justo los que ya se toparon con una action que no aguanta Node 24, pusieron la variable para seguir avanzando y dejaron el arreglo para después. Y alrededor de esa fecha hay otras cuatro con consecuencias reales: la versión mínima de los runners self-hosted (25 de septiembre), la retención de checks y ejecuciones (1 de octubre), los apagones de macos-14 (octubre) y su retiro (2 de noviembre).

    Lo verifiqué contra el código fuente del runner y contra el CI de este mismo sitio, que usa dos actions node20. Abajo va todo con su fuente.

    El calendario, por fecha

    FechaQué cambiaQué se rompeCómo lo detectas
    14, 16 y 18 sep (11:00-15:00 ET, 9:00-13:00 en CDMX)Apagones previos a la versión mínima de runners self-hostedLos runners desactualizados no se registran ni ejecutan jobs durante la ventanaAPI de deprecaciones del runner (abajo)
    17 sepubuntu-22.04 y ubuntu-22.04-arm entran en deprecaciónNada falla todavía; puede haber colas más largasgrep de la etiqueta
    21-30 sepwindows-11-arm pasa a la imagen con Visual Studio 2026Cambia la cadena de herramientas por debajogrep windows-11-arm
    23 sepNode 20 sale de los runnersACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION se ignora; runners Linux ARM32 sin soportegrep de la variable
    25 sepSe aplica la versión mínima de runners self-hostedRegistrar exige ≥ 2.329.0; un runner que no instala cada versión en 30 días deja de recibir jobsAPI de deprecaciones
    1 octLa retención de Actions pasa a cubrir checks, ejecuciones y statusesLo que pase tu periodo de retención (90 días por defecto) se borraAjuste de retención del repo/org
    5-31 octOcho apagones de macos-14Los jobs programados en la ventana fallangrep macos-14
    2 novmacos-14 retiradoLos jobs con esa etiqueta terminan con errorgrep macos-14
    17 abr 2027ubuntu-22.04 retirado (apagones antes, en marzo y abril)Los jobs terminan con errorgrep de la etiqueta

    1. Node 20: qué pasa de verdad el 23 de septiembre

    Lo que dice el aviso

    El changelog de GitHub del 19 de septiembre de 2025, con una nota del editor del 25 de agosto de 2026, fija dos fechas: desde el 16 de junio de 2026 los runners usan Node 24 por defecto, y la variable ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true para volver a Node 20 «solo funcionará hasta que actualicemos el runner y quitemos Node20 el 23 de septiembre de 2026». Añade dos límites de plataforma: Node 24 es incompatible con macOS 13.4 y anteriores, y no tiene soporte oficial para ARM32.

    Lo que el aviso no explica es qué le pasa a una action cuyo action.yml sigue diciendo using: node20. Eso está en el código.

    Lo que dice el código del runner

    En HandlerFactory.cs (igual en la etiqueta v2.337.0, la versión actual), el runner hace dos cosas antes de lanzar una action de JavaScript:

    1. Si la action declara node12 o node16, la sube a node20.
    2. Si queda en node20, le pregunta a NodeUtil.DetermineActionsNodeVersion qué versión usar.

    Y esa función tiene tres fases, controladas por feature flags del lado de GitHub:

    // Phase 3: Always use Node 24 regardless of environment variables
    if (requireNode24)
    {
        return (Constants.Runner.NodeMigration.Node24, null);
    }
    // ...
    // Phase 2: Node 24 is the default
    if (useNode24ByDefault)
    {
        if (allowUnsecureNode)
        {
            return (Constants.Runner.NodeMigration.Node20, null);
        }
        return (Constants.Runner.NodeMigration.Node24, null);
    }
    

    El PR #3948 que introdujo esto lo describe sin rodeos: en la fase 3, «todas las actions usan Node 24» y «no hay opciones de exclusión». Ninguna rama devuelve un error por declarar node20. La action se ejecuta con otro Node, que es muy distinto de no ejecutarse.

    Fíjate también en el orden: node12node20node24. Una action vieja con using: node16 (por ejemplo actions/checkout@v3) acaba igualmente en Node 24.

    La prueba: el CI de este sitio

    El workflow de CI de este blog usa actions/checkout@v4 y actions/setup-node@v4. Los action.yml oficiales de esas etiquetas declaran using: node20; los de v5 en adelante declaran node24 (lo comprobé en las etiquetas v4 a v7 de las dos). La ejecución del 11 de septiembre, en el runner 2.337.0 sobre la imagen ubuntu-24.04, terminó en verde con esta anotación:

    Node.js 20 is deprecated. The following actions target Node.js 20 but are being
    forced to run on Node.js 24: actions/checkout@v4, actions/setup-node@v4.
    

    Y en el log de los pasos de esas dos actions, esta línea:

    Node 20 is being deprecated. This workflow is running with Node 24 by default.
    If you need to temporarily use Node 20, you can set the
    ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable.
    

    La misma anotación sale desde la primera ejecución de ese workflow, el 23 de agosto. Es decir: mis dos actions node20 nunca han corrido en Node 20 en este CI, y yo no toqué nada para eso. El 23 de septiembre no les cambia el runtime, porque el cambio ya ocurrió en junio. Actualizarlas a v5 o superior quita la anotación, y conviene hacerlo, pero no hay reloj.

    Cómo se ve cuando sí se rompe

    Hay tres formas reales de fallar, y ninguna es un mensaje de GitHub que diga «Node 20 ya no existe».

    Tenías la variable puesta. Alguna action tuya tropezó con Node 24 después del 16 de junio, pusiste ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true y siguió en Node 20. En la fase 3 el runner ignora la variable sin avisar: la rama de requireNode24 devuelve null como mensaje. La action vuelve a Node 24 y el error que ves es el de su propio código, el mismo que viste la primera vez. Una pista de que la fase 3 ya está activa: con el código actual del runner, la línea «If you need to temporarily use Node 20…» deja de salir en el log, porque solo se imprime cuando Node 24 es el valor por defecto pero todavía no es obligatorio.

    La action usa algo que Node 24 eliminó. Según la tabla de deprecaciones de Node 24 y las notas de la 24.0.0, llegaron a fin de vida en esa versión:

    APIEstado en Node 24Cómo falla
    tls.createSecurePair()Eliminada (DEP0064)TypeError: ... is not a function al llamarla
    fs.truncate() con un descriptor de archivoEliminado (DEP0081)Lanza ERR_INVALID_ARG_TYPE: espera una ruta. Usa fs.ftruncate()
    dirent.pathEliminada (DEP0178)No lanza por sí sola: la propiedad ya no existe y lees undefined. Usa dirent.parentPath
    OutgoingMessage.prototype._headers / _headerNamesEliminadas (DEP0066)Lees undefined. Usa getHeaders()

    El caso de dirent.path es el más traicionero de la lista, porque leerla no lanza nada: el fallo aparece más adelante, donde se use esa ruta, o nunca, si solo se concatena en un texto y sale undefined/archivo. Si mantienes una action que recorre directorios, busca esa propiedad antes de fiarte del check verde.

    Tu runner self-hosted no puede ejecutar Node 24. En Linux ARM32, el mismo NodeUtil.cs tiene un interruptor de apagado que hace fallar el paso con:

    Linux ARM32 runners are no longer supported. Please migrate to a supported platform.
    

    Antes de que se active, el runner mantiene esas actions en Node 20 con un aviso que usa como fecha por defecto la de la retirada de Node 20: «Linux ARM32 runners are deprecated and will no longer be supported after September 23rd, 2026». GitHub no ha publicado el día exacto en que se activa; el changelog dice que ARM32 deja de tener soporte tras la deprecación de Node 20. En macOS, el BUILDING.md de Node 24 exige macOS 13.5 o superior.

    2. La auditoría: qué buscar y dónde

    En un repositorio

    Corre esto desde la raíz:

    # 1. El interruptor que deja de funcionar el 23 de septiembre
    grep -rn 'ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION' .github/
    
    # 2. Etiquetas de imagen con fecha. Sin "runs-on:" a propósito:
    #    así también aparecen las matrices (os: [ubuntu-22.04, ...])
    grep -rnE 'ubuntu-22\.04|macos-14|windows-11-arm' .github/
    
    # 3. Actions propias o locales que declaran un Node viejo
    grep -rnE --include=action.yml --include=action.yaml \
      "using:[[:space:]]*['\"]?node(12|16|20)" .
    

    La búsqueda 2 tiene un punto ciego: runs-on: ${{ vars.RUNNER }} o una etiqueta que llega como input a un workflow reutilizable no aparece en ningún grep. Para esos casos, la fuente de verdad es el log. El paso «Set up job» imprime la imagen real:

    gh run view <run-id> --log | grep -m1 'Image:'
    # Image: ubuntu-24.04
    

    Si la matriz es larga o viene anidada, pasarla a JSON y filtrarla con jq suele ser más rápido que leer el YAML a ojo:

    Para saber qué runtime declara cada action de terceros que usas, incluidas las fijadas por SHA, hay que leer su action.yml en esa referencia exacta. Este script lo hace con gh:

    grep -rhoE "uses:[[:space:]]*['\"]?[^[:space:]'\"#]+@[^[:space:]'\"#]+" .github/ \
      | sed -E "s/uses:[[:space:]]*['\"]?//" | sort -u \
      | while read -r ref; do
          spec=${ref%@*}; ver=${ref#*@}
          case "$spec" in ./*|docker://*) continue ;; esac
          repo=$(cut -d/ -f1-2 <<<"$spec"); sub=$(cut -d/ -f3- <<<"$spec")
          for f in action.yml action.yaml; do
            rt=$(gh api "repos/$repo/contents/${sub:+$sub/}$f?ref=$ver" --jq .content 2>/dev/null \
                 | base64 -d | grep -E '^[[:space:]]*using:' | tr -d " '\"" | cut -d: -f2)
            [ -n "$rt" ] && { echo "$rt  $ref"; break; }
          done
        done
    

    En este repo imprime:

    node20  actions/checkout@v4
    node20  actions/setup-node@v4
    

    Soporta subrutas (github/codeql-action/init@v3) y SHAs. Un SHA que apunta a una versión node20 no falla el 23 por lo mismo que no falla @v4: el runner la fuerza a Node 24. Lo que cambia con un SHA es cómo lo actualizas: no recibe nada solo, tiene que moverlo Dependabot o una persona.

    Un aviso antes de subir de major: lee las notas de versión. actions/setup-node v5 activó caché automática cuando package.json trae el campo packageManager, y la v6 la limitó a npm. Si ya pasas cache: npm explícito, como en mi CI, no te afecta.

    En una organización

    La búsqueda de código de GitHub llega a todos los repos de una vez:

    gh search code --owner TU_ORG 'ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION'
    gh search code --owner TU_ORG 'ubuntu-22.04 path:.github/workflows'
    gh search code --owner TU_ORG 'macos-14 path:.github/workflows'
    gh search code --owner TU_ORG node20 --filename action.yml
    

    Dos límites de la API de búsqueda de código que te van a morder: solo busca en la rama por defecto (un workflow en una rama de release no sale) y permite 10 peticiones por minuto. Preparando este post me topé con HTTP 403: API rate limit exceeded en cuanto encadené unas cuantas búsquedas; mete un sleep entre ellas si lo vas a automatizar.

    3. Runners self-hosted: dos fechas en tres días

    Si solo usas runners de GitHub, salta esta sección. Si tienes los tuyos, el 25 de septiembre pesa más que el 23.

    El changelog del 12 de junio retoma la aplicación de dos reglas en github.com:

    • Para registrar (o volver a registrar) un runner hace falta la versión 2.329.0 o superior.
    • Para seguir ejecutando jobs, el runner tiene que instalar cada versión nueva en menos de 30 días desde su publicación. «Un runner fijado en 2.329.0 que nunca se actualiza no recibirá jobs».

    La aplicación completa empieza el 25 de septiembre de 2026 (la tabla del aviso rotula esa fecha como GitHub Enterprise Cloud, y el mismo aviso aclara que el cambio aplica a github.com), con apagones antes: los días 14, 16 y 18 de septiembre, de 11:00 a 15:00 hora del Este (9:00 a 13:00 en la Ciudad de México), los runners fuera de versión ni se registran ni ejecutan. Lo que ves, según el aviso, es que el workflow «puede quedarse en cola o fallar».

    La forma de saber cuándo caduca tu versión es un endpoint que GitHub añadió el 3 de septiembre. Lo consulté hoy:

    gh api repos/OWNER/REPO/actions/runners/deprecations/2.335.1
    # {"runner_version":"2.335.1","runtime_deprecates_at":"2026-09-24T15:30:55Z"}
    
    gh api repos/OWNER/REPO/actions/runners/deprecations/2.337.0
    # {"runner_version":"2.337.0","runtime_deprecates_at":null}
    

    Un runner en 2.335.1 queda fuera de plazo el 24 de septiembre, así que cuando empiece la aplicación completa, el 25, ya no recibe jobs. La versión instalada la sacas en la propia máquina con ./config.sh --version.

    El checklist para self-hosted, en orden:

    1. Actualiza el runner a la versión actual y deja activada la actualización automática. Si la tienes apagada, necesitas una cadencia manual de menos de 30 días.
    2. Rehaz las imágenes, plantillas de VM y contenedores con los que creas runners. El changelog pide rehacer los que salen de imágenes o plantillas viejas: si la imagen trae un runner anterior a 2.329.0, no se registra.
    3. Linux ARM32: migra a arm64. Node 24 no tiene soporte oficial ahí, el paquete del runner para linux-arm solo trae el binario de Node 20 (externals.sh) y el código que hace fallar el paso ya está en el runner.
    4. macOS 13.4 o anterior: actualiza el sistema operativo. GitHub declara Node 24 incompatible con esas versiones.
    5. Las actions que ya declaran node24 (como actions/checkout@v5) piden como mínimo el runner v2.327.1. Si cumples el punto 1, esto ya lo tienes.

    4. Retención, 1 de octubre: el historial se borra

    Este cambio no rompe ningún build, pero borra datos. Según el changelog del 27 de agosto, desde el 1 de octubre de 2026 los checks, las ejecuciones de workflows y los statuses siguen el mismo ajuste de retención que ya controlaba los artefactos y los logs, 90 días por defecto. Hasta ahora se guardaban «400+ días» sin importar lo que configuraras.

    • En repositorios públicos el máximo es 90 días.
    • El cambio no es retroactivo: subir la retención después no recupera nada.
    • Los metadatos de checks y ejecuciones no se cobran como almacenamiento; los artefactos y logs asociados sí.

    Lo que se rompe es todo lo que lea ese historial más allá de tu periodo: métricas de despliegue calculadas desde gh run list, auditorías que enlazan a una ejecución concreta o reportes trimestrales. Exporta lo que necesites antes del 1 de octubre.

    Lo que el changelog no dice: qué pasa con un pull request abierto más de 90 días cuyo check requerido caduca. No lo encontré documentado. Si tienes PRs de larga vida con protección de rama, revísalos después del 1 de octubre antes de dar nada por hecho.

    5. Imágenes: macos-14 sí tiene fecha, ubuntu-22.04 todavía no

    macos-14 (actions/runner-images#13518): la deprecación empezó el 6 de julio y el soporte termina el 2 de noviembre de 2026. Antes hay ocho apagones en los que los jobs fallan: 5, 12, 16, 19, 23, 26, 29 y 30 de octubre, cada uno de 14:00 UTC a 00:00 UTC del día siguiente. Afecta a macos-14, macos-14-large y macos-14-xlarge. El reemplazo es macos-15, macos-26 o macos-latest, que con la migración iniciada el 15 de junio pasó a apuntar a macOS 26.

    ubuntu-22.04 (actions/runner-images#14254): el 17 de septiembre solo empieza la deprecación. El issue avisa de colas más largas en horas pico, no de fallos. El retiro es el 17 de abril de 2027, con apagones previos que el issue fecha en marzo y abril (sin año, pero caen antes del retiro). Afecta también a ubuntu-22.04-arm. El reemplazo es ubuntu-24.04, ubuntu-26.04 o ubuntu-latest.

    Si alguien te dice que ubuntu-22.04 se rompe esta semana, no es cierto. Migra con calma, pero no lo dejes para marzo.

    Lo que sale en el changelog y no rompe nada

    • cache-mode (10 de septiembre): una clave nueva para limitar el acceso a la caché por workflow o job. Es opcional: los workflows que no la declaran «siguen usando los valores seguros existentes».
    • La ruta separada de Code Quality (20 de agosto): solo te afecta si tienes reportes que filtran por dynamic/github-code-scanning/codeql o por el actor github-advanced-security.
    • actions/checkout y pull_request_target (18 de junio): esto sí rompe, pero ya pasó. Desde el 20 de julio las etiquetas flotantes, @v4 incluida, rechazan hacer checkout del código de un fork en pull_request_target. Si tu workflow dependía de eso, ya está en rojo. Es la misma familia de riesgo que conté en el gusano que se coló por el trusted publishing de npm.

    Qué hago con mi CI

    Nada urgente. actions/checkout@v4 y actions/setup-node@v4 corren en Node 24 desde junio, el workflow no usa la variable de exclusión, corre en ubuntu-latest (hoy ubuntu-24.04) y en runners de GitHub. El 23 de septiembre no le cambia nada.

    Sí voy a subir las dos actions a su major actual, por dos razones que no tienen que ver con la fecha: la anotación amarilla entrena a ignorar las anotaciones, y una action que su autor ya no prueba en el runtime donde de verdad se ejecuta es deuda. Si además estás pensando en el Node de tu propio proyecto, no el de las actions, lo tengo contado en qué se rompe al actualizar a Node 26.

    Preguntas frecuentes

    ¿Qué pasa con GitHub Actions el 23 de septiembre de 2026?

    GitHub quita Node 20 de los runners. Desde el 16 de junio de 2026 los runners ya ejecutaban las actions con Node 24 por defecto, y la variable ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true permitía volver a Node 20. A partir del 23 de septiembre esa variable deja de funcionar y todas las actions de JavaScript corren en Node 24. Los runners self-hosted en Linux ARM32 pierden soporte, porque Node 24 no tiene soporte oficial para esa plataforma.

    ¿Falla una action que declara using: node20 después del 23 de septiembre?

    No por declararlo. El código del runner (HandlerFactory.cs y NodeUtil.cs en actions/runner) reescribe node12 y node16 a node20, y node20 a node24; en la fase final ignora las variables de entorno y siempre elige Node 24, sin devolver error. La action falla si su código usa algo que Node 24 eliminó, como tls.createSecurePair o fs.truncate con un descriptor, o si corre en un runner self-hosted Linux ARM32 o con macOS 13.4 o anterior. En los logs aparece la anotación: The following actions target Node.js 20 but are being forced to run on Node.js 24.

    ¿Tengo que actualizar actions/checkout@v4 y actions/setup-node@v4?

    Conviene, pero no hay fecha que lo obligue. Las etiquetas v4 de las dos declaran node20 y las v5 en adelante declaran node24. En runners de GitHub, las v4 ya corren en Node 24 desde junio y siguen funcionando. Al subir setup-node, revisa las notas de versión: la v5 activó caché automática cuando package.json tiene packageManager y la v6 la limitó a npm.

    ¿Cómo encuentro en mi organización los workflows afectados?

    Con la búsqueda de código de GitHub: gh search code --owner TU_ORG con ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION, con ubuntu-22.04 path:.github/workflows, con macos-14 path:.github/workflows, y con node20 --filename action.yml para tus actions propias. La API solo busca en la rama por defecto y permite 10 peticiones por minuto. Las etiquetas que llegan por variables o expresiones no aparecen en ninguna búsqueda: para esas, revisa la línea Image: del paso Set up job en el log.

    ¿Se rompe ubuntu-22.04 el 17 de septiembre de 2026?

    No. El 17 de septiembre solo empieza la deprecación y el aviso habla de colas más largas en horas pico. Los jobs empiezan a fallar en los apagones que el issue 14254 de actions/runner-images programa para marzo y abril, antes de que la imagen se retire el 17 de abril de 2027. La que sí tiene fecha este otoño es macos-14: apagones en octubre y retiro el 2 de noviembre de 2026.

    ¿Qué tienen que hacer los runners self-hosted antes del 25 de septiembre?

    Actualizar el runner. Desde el 25 de septiembre de 2026 GitHub exige la versión 2.329.0 para registrar un runner y que se instale cada versión nueva en menos de 30 días para seguir recibiendo jobs. Hay apagones previos el 14, 16 y 18 de septiembre, de 11:00 a 15:00 hora del Este. El endpoint GET /repos/OWNER/REPO/actions/runners/deprecations/VERSION devuelve cuándo deja de ejecutar tu versión. Rehaz también las imágenes con las que creas runners.

    ¿Qué cambia con la retención de GitHub Actions el 1 de octubre de 2026?

    Los checks, las ejecuciones de workflows y los statuses pasan a seguir el ajuste de retención de artefactos y logs, que por defecto es de 90 días; antes se guardaban más de 400 días. En repositorios públicos el máximo es 90 días y el cambio no es retroactivo. Si tienes métricas o auditorías que leen ejecuciones antiguas, exporta los datos antes de esa fecha.

    Compartir

    Buscar

    Etiquetas

    Migración IA PHP JavaScript Laravel Tutorial Desarrollo Web Upgrade Buenas Prácticas Seguridad SEO Backend Claude Laravel 13 TypeScript