fix(ci): two runs stomped each other, and the logs blamed the tests
deploy / test (push) Failing after 8s
deploy / build (push) Skipped

Runs 490 and 491 both failed `test`. Neither failure was in the code.

Runs 490 and 491 started 16 minutes apart and a full suite takes longer
than that, so they overlapped. The first thing a run does is
`docker rm -fv cm-ci-pg` — a name every run shared — so the newer run
deleted the older run's database mid-suite. Both failed, and the failures
read as test failures.

Verified before changing anything: the exact CI command, on gw-04, against
the same warm cargo volumes and a Postgres started exactly as CI starts it,
passes on 128b423 — as do `npm ci`, `typecheck` and `vitest` on that host.
The code was never the problem.

  - `concurrency: deploy-${{ gitea.ref }}` with cancel-in-progress, so runs
    on a ref serialize. A superseded run tests a commit that is no longer
    the tip; finishing it costs 20 minutes to learn something that no longer
    matters.
  - the test Postgres is named per run, so overlap cannot corrupt a run even
    if the concurrency guard is later removed. Impossible rather than
    unlikely.
  - `--shm-size=1g` on it. Docker defaults /dev/shm to 64MB and cm-testkit
    creates a database per test; Postgres exhausts its parallel-query
    segments mid-run and reports `could not resize shared memory segment`
    DURING MIGRATIONS, which reads like a schema fault. Hit locally on
    2026-08-19; scripts/test-server.sh already carries the same flag.

The lesson is the session's own: I twice inferred a cause from a red build
without reading the failure — first node, then dash — and both were wrong.
The answer came from running the job on the runner's own host.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-20 07:00:10 -07:00
co-authored by Claude Opus 5
parent 128b423205
commit 72ba4ba523
2 changed files with 86 additions and 10 deletions
+33 -5
View File
@@ -21,6 +21,19 @@ on:
# Lets you re-run a deploy without an empty commit. # Lets you re-run a deploy without an empty commit.
workflow_dispatch: workflow_dispatch:
# Two pushes close together used to STOMP each other. Runs 490 and 491 started
# 16 minutes apart, a full suite takes longer than that, and the first thing a
# run does is `docker rm -fv` the shared test Postgres — so the newer run
# deleted the older run's database mid-suite and both failed. Nothing in the
# code was wrong; the logs blamed the tests.
#
# `cancel-in-progress` because a superseded run is testing a commit that is no
# longer the tip: finishing it costs 20 minutes to learn something that no
# longer matters.
concurrency:
group: deploy-${{ gitea.ref }}
cancel-in-progress: true
env: env:
REGISTRY: 100.94.185.103:5000 REGISTRY: 100.94.185.103:5000
NAMESPACE: clawmates NAMESPACE: clawmates
@@ -28,6 +41,9 @@ env:
jobs: jobs:
test: test:
runs-on: gw04 runs-on: gw04
env:
# Shared by the start and stop steps.
PG: cm-ci-pg-${{ gitea.run_id }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -37,15 +53,27 @@ jobs:
# only means "no database here". # only means "no database here".
- name: Start test Postgres - name: Start test Postgres
run: | run: |
docker rm -fv cm-ci-pg 2>/dev/null || true # Run-scoped name. `cm-ci-pg` was shared by every run, so a second
docker run -d --name cm-ci-pg \ # run removed the first one's database while it was still being used.
# The concurrency group above should prevent overlap; this makes the
# failure impossible rather than merely unlikely.
docker rm -fv "$PG" 2>/dev/null || true
# --shm-size: Docker defaults /dev/shm to 64MB. cm-testkit creates a
# database per test and the suite runs many at once, so Postgres
# exhausts its parallel-query segments mid-run. It surfaces as
# `could not resize shared memory segment ... No space left on device`
# during MIGRATIONS, which reads like a schema fault and is not one.
# Hit locally on 2026-08-19; scripts/test-server.sh carries the same
# flag for the same reason.
docker run -d --name "$PG" \
--shm-size=1g \
-e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres \
-p 127.0.0.1:55432:5432 postgres:16-alpine -p 127.0.0.1:55432:5432 postgres:16-alpine
for i in $(seq 1 30); do for i in $(seq 1 30); do
docker exec cm-ci-pg pg_isready -U postgres >/dev/null 2>&1 && break docker exec "$PG" pg_isready -U postgres >/dev/null 2>&1 && break
sleep 2 sleep 2
done done
docker exec cm-ci-pg pg_isready -U postgres docker exec "$PG" pg_isready -U postgres
# Rust lives in a container because gw-04 has no cargo. The named volumes # Rust lives in a container because gw-04 has no cargo. The named volumes
# are the whole reason this is not painfully slow: without them every run # are the whole reason this is not painfully slow: without them every run
@@ -105,7 +133,7 @@ jobs:
# 38 GB of leaked volumes before anyone noticed. # 38 GB of leaked volumes before anyone noticed.
- name: Stop test Postgres - name: Stop test Postgres
if: always() if: always()
run: docker rm -fv cm-ci-pg 2>/dev/null || true run: docker rm -fv "$PG" 2>/dev/null || true
# node 22 is on the host, so these run directly. # node 22 is on the host, so these run directly.
- name: Frontend checks - name: Frontend checks
+53 -5
View File
@@ -6,12 +6,60 @@ Read `CAPABILITY-REVIEW.md` for the system picture,
## State of the tree ## State of the tree
**11 unpushed commits on `main`** (`origin/main` is 11 behind). Nothing has been **Everything is pushed.** `main` is at `128b423`; the fork's
pushed and nothing is deployed to gw-04. Full suite green: 106 test binaries, `merge/upstream-v0.8.4` is at `db1c50966`. The local suite is green — 106 test
zero build errors, frontend builds. binaries, zero build errors, frontend builds.
**1 unpushed commit in the fork** at `/Users/quantum/projects/zeroclaw`, branch ## ⚠ CI IS RED — READ THIS FIRST
`merge/upstream-v0.8.4`: `db1c50966 feat(claude_cli): stream-json …`.
Pushing `main` auto-deploys (`.gitea/workflows/deploy.yml`: push → test → build
→ registry → gw-04's 60s timer). **The `test` job is failing, so the `build` job
has not run and NOTHING FROM THESE COMMITS IS DEPLOYED.** Production is still
serving the previous image. That is a safe state, not a broken one — but it
means the work is pushed and not live.
Two runs failed: 490 (`b653dbf`) and 491 (`128b423`).
**Known and fixed:** run 490 failed because `vm_tool_gate`'s shell tests execute
the generated hook, which parses its payload with `node`, and CI's
`rust:1.96-slim` has no node. `deploy.yml` now installs `nodejs`, and the gate
records an `inert` marker when node is missing rather than silently allowing.
Verified in a `rust:1.96-slim` container: without node the force-push payload
returns 0, with node it returns 2.
**Still unexplained:** run 491 carried that fix and failed anyway. The cause was
NOT determined. Do not guess at it — that is what produced two bad diagnoses
already this session.
To get the actual failure, reproduce the CI job locally (the Gitea logs API
returns 403 with the token in `deploy/compose/.env`, which lacks the actions
scope):
```
docker network create cm-ci-net
docker run -d --name cm-ci-pg-local --network cm-ci-net \
-e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres --shm-size=1g postgres:16-alpine
docker run --rm --network cm-ci-net -v "$PWD":/w -w /w \
-e SQLX_OFFLINE=true -e CARGO_NET_GIT_FETCH_WITH_CLI=true \
-e FORGE_TOKEN=<GITEA_TOKEN from deploy/compose/.env> \
-e CM_TEST_DATABASE_URL=postgres://postgres:postgres@cm-ci-pg-local:5432/postgres \
rust:1.96-slim sh -c 'apt-get update -qq &&
apt-get install -y -qq pkg-config libssl-dev cmake git nodejs &&
git config --global url."https://oauth2:[email protected]/".insteadOf "https://git.redclaw.dev/" &&
cargo test --workspace --no-fail-fast'
```
A run of exactly this was in flight when the session ended; its output was going
to `/tmp/cilocal.log`.
Candidates worth checking first, none confirmed:
- something in the new tests that is Linux- or dash-specific beyond the node
issue (the gate script itself IS verified under dash);
- `--shm-size` — the local test Postgres needed 1g because the suite exhausts
Docker's 64MB default mid-run, and CI's `cm-ci-pg` sets no `--shm-size`. This
surfaces as `could not resize shared memory segment … No space left on device`
**during migrations**, which reads like a schema fault and is not one. If that
is the cause, the fix is `--shm-size=1g` on CI's postgres, not on our code.
## The thing to do first ## The thing to do first