From 768456664f584caa9da7705f798060ec22fee8cf Mon Sep 17 00:00:00 2001 From: aarroyo Date: Tue, 1 Sep 2026 09:51:12 -0500 Subject: [PATCH 1/2] fix(agent-runtime): the OPA step fails at the command that fails, and says why MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The build still breaks in CI, and the reason I cannot yet name it is the defect this commit fixes: the step reports the wrong error, three commands late. CI shows `mv: can't rename '/tmp/opa': No such file or directory` and — this is the part that matters — **no output at all from curl or from node** in between, even with `-S`, which prints errors. Neither the download failing nor the `test -s` after it aborted the run. A `;` chain that should have stopped and did not turns every failure into the same misleading message. So the step is now an `&&` chain rather than `set -eu` with `;`. Any failure stops AT that command, with that command's own message. `set -x` traces each step, the pin and the URL are echoed, `test -n` refuses an empty version, and `ls -l` shows what was actually downloaded before anything is moved. This does NOT claim to fix the root cause. It cannot be reproduced here: the image builds clean locally on both arm64 and, with `--no-cache`, on linux/amd64 — the pin resolves to 1.19.0, curl fetches 60,526,763 bytes and `opa version` reports `Version: 1.19.0`. Whatever differs is in the runner, and until now the build was incapable of saying what. Now it will. Guessing at a cause I cannot observe would be the same mistake as shipping the first version with the image unverified. This makes the next CI run answer the question. Co-Authored-By: Claude Opus 5 Signed-off-by: aarroyo --- src/apps/agent-runtime-api/Dockerfile | 30 ++++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/apps/agent-runtime-api/Dockerfile b/src/apps/agent-runtime-api/Dockerfile index 85c0c7fc..0c9e8213 100644 --- a/src/apps/agent-runtime-api/Dockerfile +++ b/src/apps/agent-runtime-api/Dockerfile @@ -43,14 +43,28 @@ COPY .harness ./.harness # un `mv` que no encontraba nada. `-f` convierte un HTTP de error en salida no nula, # `-L` sigue la redireccion, y el `test -s` asegura que lo descargado no esta vacio. ARG TARGETARCH -RUN apk add --no-cache curl && \ - set -eu; \ - OPA_VERSION="$(node --input-type=module -e "import { OPA_VERSION } from 'file:///repo/.harness/scripts/opa-runtime.mjs'; process.stdout.write(OPA_VERSION);")"; \ - curl -fsSL -o /tmp/opa "https://openpolicyagent.org/downloads/v${OPA_VERSION}/opa_linux_${TARGETARCH:-amd64}_static"; \ - test -s /tmp/opa; \ - chmod 0755 /tmp/opa; \ - mv /tmp/opa .harness/bin/opa; \ - .harness/bin/opa version | grep -q "Version: ${OPA_VERSION}" +# Encadenado con `&&`, NO con `set -eu` y `;`. La version anterior fallaba en CI con +# `mv: can't rename '/tmp/opa'` — tres ordenes despues de la causa y sin nombrarla — +# porque ni el fallo de la descarga ni el `test -s` posterior abortaban. Un `;` que +# deberia haber cortado y no corta convierte cualquier error en el error equivocado. +# Con `&&` la cadena se detiene EN el comando que falla y con SU mensaje. +# +# Cada paso ademas se anuncia, porque el log de CI no mostro salida alguna de `curl` +# ni de `node`: sin saber cual de los dos corrio, el fallo no era diagnosticable. +RUN set -x \ + && apk add --no-cache curl \ + && OPA_VERSION="$(node --input-type=module -e "import { OPA_VERSION } from 'file:///repo/.harness/scripts/opa-runtime.mjs'; process.stdout.write(OPA_VERSION);")" \ + && echo "OPA pin resuelto: '${OPA_VERSION}'" \ + && test -n "${OPA_VERSION}" \ + && OPA_URL="https://openpolicyagent.org/downloads/v${OPA_VERSION}/opa_linux_${TARGETARCH:-amd64}_static" \ + && echo "Descargando ${OPA_URL}" \ + && curl -fsS --retry 3 --retry-connrefused -L -o /tmp/opa "${OPA_URL}" \ + && ls -l /tmp/opa \ + && test -s /tmp/opa \ + && chmod 0755 /tmp/opa \ + && mv /tmp/opa .harness/bin/opa \ + && .harness/bin/opa version \ + && .harness/bin/opa version | grep -q "Version: ${OPA_VERSION}" RUN npm ci --legacy-peer-deps From eee4c90a76ae17e9b1baa749be7b7c06f22c2e0e Mon Sep 17 00:00:00 2001 From: aarroyo Date: Tue, 1 Sep 2026 10:06:31 -0500 Subject: [PATCH 2/2] fix(agent-runtime): create the OPA destination directory, which does not exist on a clean checkout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause, at last, and it was never the download. `.gitignore:47` ignores `.harness/bin/` — roughly 400 MB of platform OPA binaries — so git tracks not one file there. A developer's machine has that directory because they downloaded into it. **A clean checkout does not have it at all.** So `mv /tmp/opa .harness/bin/opa` failed for want of a destination. Two things hid this for three attempts: * BusyBox `mv` names the SOURCE when the DESTINATION is missing — `can't rename '/tmp/opa': No such file or directory` reads exactly like a download that produced nothing, which is what I chased first with wget→curl. * Every local build passed, because it used files that exist only on my disk. That is the same class of error as the original one: verifying something adjacent to what CI runs instead of what CI runs. `mkdir -p .harness/bin` before the move. One line. **Two claims of mine are corrected in the process.** The Dockerfile comment, and the commit that introduced this step, said the build was shipping a Mach-O binary into a Linux image. It was not: those binaries are gitignored, so on CI there was no binary at all — which is what the original audit actually said ("absent from the alpine image"). I overstated it, and the corrected comments now say what is true. Verified the way it should have been from the start: in a clean `git worktree` — no `.harness/bin/`, exactly what the runner checks out — building `--platform linux/amd64 --no-cache`. `mkdir -p` runs, `mv` succeeds, `opa version` reports `Version: 1.19.0`. A note the chain earned: a transient DNS failure during one of these runs stopped the build AT `apk add` and said so. Under the previous `;` form that would have surfaced as the same misleading `mv` error. Co-Authored-By: Claude Opus 5 Signed-off-by: aarroyo --- src/apps/agent-runtime-api/Dockerfile | 30 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/apps/agent-runtime-api/Dockerfile b/src/apps/agent-runtime-api/Dockerfile index 0c9e8213..a05e158b 100644 --- a/src/apps/agent-runtime-api/Dockerfile +++ b/src/apps/agent-runtime-api/Dockerfile @@ -43,14 +43,18 @@ COPY .harness ./.harness # un `mv` que no encontraba nada. `-f` convierte un HTTP de error en salida no nula, # `-L` sigue la redireccion, y el `test -s` asegura que lo descargado no esta vacio. ARG TARGETARCH -# Encadenado con `&&`, NO con `set -eu` y `;`. La version anterior fallaba en CI con -# `mv: can't rename '/tmp/opa'` — tres ordenes despues de la causa y sin nombrarla — -# porque ni el fallo de la descarga ni el `test -s` posterior abortaban. Un `;` que -# deberia haber cortado y no corta convierte cualquier error en el error equivocado. -# Con `&&` la cadena se detiene EN el comando que falla y con SU mensaje. +# `mkdir -p .harness/bin` NO es defensivo: en CI ese directorio NO EXISTE. +# `.gitignore:47` ignora `.harness/bin/` —son ~400 MB de binarios OPA— asi que git no +# rastrea un solo fichero ahi. En una maquina de desarrollo el directorio existe con +# los binarios que uno descargo; en un checkout limpio no existe en absoluto. # -# Cada paso ademas se anuncia, porque el log de CI no mostro salida alguna de `curl` -# ni de `node`: sin saber cual de los dos corrio, el fallo no era diagnosticable. +# Ese es el fallo entero, y lo enmascaraban dos cosas: BusyBox `mv` nombra el ORIGEN +# cuando falta el DESTINO (`can't rename '/tmp/opa'`, que se lee como si la descarga +# hubiera fallado), y una build local pasa porque usa ficheros que solo existen en el +# disco de quien la lanza. +# +# La cadena va con `&&` y no con `set -eu` y `;` porque aquella version no abortaba en +# el comando que fallaba: el error aparecia tres ordenes despues y sin nombrar la causa. RUN set -x \ && apk add --no-cache curl \ && OPA_VERSION="$(node --input-type=module -e "import { OPA_VERSION } from 'file:///repo/.harness/scripts/opa-runtime.mjs'; process.stdout.write(OPA_VERSION);")" \ @@ -62,6 +66,7 @@ RUN set -x \ && ls -l /tmp/opa \ && test -s /tmp/opa \ && chmod 0755 /tmp/opa \ + && mkdir -p .harness/bin \ && mv /tmp/opa .harness/bin/opa \ && .harness/bin/opa version \ && .harness/bin/opa version | grep -q "Version: ${OPA_VERSION}" @@ -118,11 +123,12 @@ COPY --from=builder --chown=evolith:evolith /repo/src/packages/agent-runtime/pac COPY --from=builder --chown=evolith:evolith /repo/src/apps/agent-runtime-api/dist ./src/apps/agent-runtime-api/dist COPY --from=builder --chown=evolith:evolith /repo/src/apps/agent-runtime-api/package.json ./src/apps/agent-runtime-api/package.json -# The corpus the real .harness / OPA adapters run against. Copied from the -# builder stage, which is where the platform's OPA binary replaced the committed -# macOS one — copying `.harness` straight from the build context would ship a -# Mach-O binary into a Linux image, which is the bug this build step exists to -# prevent. +# The corpus the real .harness / OPA adapters run against. Copied from the builder +# stage, which is the only place the OPA binary exists: `.harness/bin/` is gitignored +# (~400 MB of platform binaries), so a clean checkout has no such directory and the +# build step above creates it and fetches the binary for THIS platform. Copying +# `.harness` straight from the build context would ship no `bin/` at all on CI, and +# whatever the developer happened to have locally everywhere else. COPY --from=builder --chown=evolith:evolith /repo/.harness ./corpus/.harness COPY --from=builder --chown=evolith:evolith /repo/src/rulesets ./corpus/rulesets