Skip to content

fix(ci): arangodb probe can never pass (401) + stress-test fake/impossible steps#640

Merged
hyperpolymath merged 6 commits into
mainfrom
fix/arangodb-healthcheck
Jul 21, 2026
Merged

fix(ci): arangodb probe can never pass (401) + stress-test fake/impossible steps#640
hyperpolymath merged 6 commits into
mainfrom
fix/arangodb-healthcheck

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Context

tests.yml was red on main with 4 failing jobs. Two of them — Integration Tests and
Haskell Integration Tests — fail with:

##[error]Failed to initialize container arangodb:3.11
##[error]One or more containers failed to start.

…immediately after ArangoDB itself logs ArangoDB (version 3.11.14 [linux]) is ready for business.
That paradox is the tell: the server is fine, the health probe is impossible.

Root cause — two independent faults, both measured

The probe was curl -f http://localhost:8529/_api/version || exit 1.

Fault Evidence (run against the exact arangodb:3.11 image)
1. curl is not in the image curl MISSING, python3 MISSING, openssl MISSING; wget, nc, arangosh present
2. localhost → IPv6, Arango binds IPv4 /etc/hosts maps both 127.0.0.1 localhost and ::1 localhost; probing localhostConnection refused, probing 127.0.0.1{"server":"arango","license":"community","version":"3.11.14"}

Either fault alone is fatal, so fixing only one would not have worked — and indeed a
wget + localhost variant was tried first and still went unhealthy after 10 retries.

Changes

.github/workflows/tests.yml — all three arangodb service definitions
(integration-tests, haskell-integration-tests, and the third stack job):

-  --health-cmd "curl -f http://localhost:8529/_api/version || exit 1"
+  --health-cmd "wget -q --spider http://127.0.0.1:8529/_api/version || exit 1"

The redis-cli ping probes on the dragonfly services are correct and untouched.

Verification — end-to-end against the real image, not by inspection

Probe Result
curl + localhost (old) command absent — can never run
wget + localhost unhealthy after 10 retries (Connection refused)
wget + 127.0.0.1 (shipped) healthy in 15s

YAML re-parsed clean: 11 jobs, 3/3 probes replaced, 0 curl+localhost occurrences left.

Scope note

An estate-wide sweep found only 2 repos using curl-on-localhost health checks: this one
(proven broken) and echidna/s4-loop.yml (ghcr.io/hyperpolymath/verisimdb-api:latest,
unverified — worth checking whether that image ships curl). Every other service probe in
the estate correctly uses pg_isready / redis-cli. This is therefore a small class, not
template rot.

Remaining red jobs (not addressed here — deliberately out of scope)

  • E2E — Elixir Scanner PipelinePASS=7 FAIL=1; the failing check is mix test
    (tests/e2e.sh:118). Separate, real test failure.
  • stress-test — not yet diagnosed.

Draft until CI confirms the two integration jobs go green on a real runner.

🤖 Generated with Claude Code

…calhost

`tests.yml` probed the arangodb:3.11 service with
`curl -f http://localhost:8529/_api/version`. That could never succeed, for
two independent reasons, both verified empirically against the exact image:

1. `curl` is NOT installed in arangodb:3.11.
   Present instead: wget, nc, arangosh. (curl/python3/openssl all MISSING.)
2. `localhost` inside the container resolves to `::1` first (/etc/hosts maps
   both `127.0.0.1 localhost` and `::1 localhost`), but ArangoDB binds IPv4
   only. Probing `localhost` returns "Connection refused"; probing
   `127.0.0.1` returns {"server":"arango","version":"3.11.14"}.

Net effect: the server logged "ArangoDB ... is ready for business" and was
then killed by Actions with "Failed to initialize container arangodb:3.11 /
One or more containers failed to start". This is why `Integration Tests` and
`Haskell Integration Tests` were red on main — not a code fault.

Fix: use wget (present in the image) against 127.0.0.1 (actually listening).
Applied to all three arangodb service definitions in this workflow.

Verified end-to-end locally with the real image, not by inspection:
  - old curl probe        -> command absent
  - wget + localhost      -> unhealthy after 10 retries (Connection refused)
  - wget + 127.0.0.1      -> healthy in 15s   <- shipped

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Self-inflicted regression from the previous commit, caught by CI.

I documented the health-check reasoning with `#` comments placed INSIDE the
`options: >-` block. In a YAML folded scalar `#` is not a comment — it is
literal text — so the prose was folded into the docker arguments:

    docker create ... # `curl` is NOT installed in arangodb:3.11 ... --health-cmd ...
    invalid reference format
    ##[error]Exit code 1 returned from process: file name '/usr/bin/docker'

The container never even got created, so `Integration Tests` and
`Haskell Integration Tests` failed earlier than before rather than passing.

Comments moved above the `options:` key, where they are real comments.

Verified by parsing the workflow and asserting on the FOLDED VALUE — the
check I should have run before pushing, rather than eyeballing the source:

  [integration-tests/arangodb]  --health-cmd "wget -q --spider
      http://127.0.0.1:8529/_api/version || exit 1" --health-interval 10s
      --health-timeout 5s --health-retries 10
  ... 5/5 service option strings start with a flag and contain no '#'.
  RESULT: CLEAN

The underlying fix (wget + 127.0.0.1, verified healthy in 15s against the
real image) is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hyperpolymath and others added 2 commits July 21, 2026 13:02
… start-up

Two further faults, both found by reading the real CI run of the previous
commit rather than assuming it was done.

1. `Integration Tests` died with **exit 127 — command not found**.
   The job installs the RUST toolchain (dtolnay/rust-toolchain), then runs
   `mix test --include integration`. There is no erlef/setup-beam anywhere in
   that job, so `mix` was simply not on PATH. Added setup-beam + `mix deps.get`
   before the Elixir step, pinned identically to the E2E job (OTP 27.0 /
   Elixir 1.17) so the two jobs cannot drift apart.

2. `Haskell Integration Tests` still reported "Failed to initialize container
   arangodb:3.11" **despite carrying the corrected health probe**. In the same
   run, `Integration Tests` reported "arangodb service is healthy" at 07:37:03,
   so the probe itself is right — the Haskell job's instance simply did not
   finish starting inside the retry budget while both jobs contended for the
   runner. ArangoDB's first boot is two-phase: it initialises, logs "server
   will now shut down due to upgrade, database initialization or admin
   restoration", exits, and only then restarts for real. Failures during that
   window were being counted against --health-retries.

   Added `--health-start-period 40s` to all three arangodb services, which is
   exactly what that flag is for: failures inside the start period do not count
   toward the retry budget.

Verified:
  * flag accepted by docker against the real image; healthy in 15s
  * folded-scalar assertion re-run — 5/5 service option strings start with a
    flag and contain no '#'; start-period present on all three arangodb
    services (this is the check that caught the comment-folding regression
    two commits ago, so it now runs every time)
  * step order asserted programmatically: setup-beam at index 9 precedes
    `mix test` at index 11

Evidence the previous commit DID work, for the record: that run reached
"arangodb service is healthy" and "dragonfly service is healthy", and ArangoDB
accepted writes ({"error":false,"code":201}). The container barrier is cleared;
these are the next two faults behind it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mpossible

Two independent CI faults, both diagnosed from real job logs and verified
against the real artefacts rather than inferred.

## 1. ArangoDB health probe: 401, always

`/_api/version` requires authentication whenever ARANGO_ROOT_PASSWORD is
set — and busybox wget (the only HTTP client in arangodb:3.11) has no
--user/--password option at all, so the probe *cannot* be authenticated.

Measured against the real image, polling once a second for 25s:

    /_api/version                exit 1 at EVERY timepoint (401)
    /_admin/server/availability  exit 1 for 3s, then exit 0 onwards

So the probe never succeeded, on any job, ever. What varied was whether
the runner's own health poll happened to read .State.Health.Status before
Docker had recorded any health state — reading empty and reporting
"healthy". That is why one job logged "arangodb service is healthy"
*before* the container logged "Initializing root user", while another job
in the same run went starting -> starting -> unhealthy and failed.

The earlier "verified healthy in CI" on this branch was therefore a won
race, not a working probe. Switching to /_admin/server/availability (the
unauthenticated liveness endpoint) removes the race entirely.

## 2. stress-test: one real step, two fakes, one impossible

Confirmed by matching ##[error] line numbers to ##[group] boundaries:

- "Concurrent operations stress test" globbed `./target/release/*`, which
  matches build directories (build/, deps/, examples/, incremental/). It
  printed "Permission denied" 100 times out of 100 and still reported
  SUCCESS, because a bare `wait` returns 0 regardless of its jobs.
- "Memory pressure test" ran `ulimit -v 512000; cargo test --release`.
  500MB of virtual address space cannot host rustc's LLD linker; it
  aborts with "Resource temporarily unavailable" / "ld terminated with
  signal 6". The same build scripts compiled fine in the immediately
  preceding step, which set no ulimit — that in-log A/B is the proof.
  This step failed on 6 of 6 recent runs. It could never pass.
- "Long-running scenario test" ran valgrind over the same directory glob
  and ended in `|| true`, so it neither ran nor could fail. Removed
  rather than left reporting success without testing anything.

Replaced with steps that resolve real bin targets from cargo metadata
(so they cannot rot when a binary is added or renamed), assert every
concurrent launch exits 0, and apply the memory cap to the BINARY rather
than to the compiler.

## Verified locally against the real binaries, not asserted

    cargo metadata resolved: cicd-fixer cii-registrar forge-adapter hyper
    all four --help                -> exit 0
    all four under a 1GiB VA cap   -> exit 0
    100 concurrent launches        -> 0 failures

plus a YAML check that every service `options` folds to a value starting
with a flag and containing no '#', and that no arangodb service still
references /_api/version.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@hyperpolymath

Copy link
Copy Markdown
Owner Author

Correction: a third fault, and my "verified in CI" claim was wrong

Two things in this PR as originally written need retracting.

1. There is a THIRD fault, and it is the one that actually mattered

The body above lists two faults (curl absent, localhost → IPv6). Both are real, but
fixing them was not sufficient, because /_api/version requires authentication
whenever ARANGO_ROOT_PASSWORD is set — which all three service blocks set. And busybox
wget, the only HTTP client in the image, has no --user/--password option at all, so
the probe cannot be authenticated:

$ wget --spider --user=root --password=testpassword http://127.0.0.1:8529/_api/version
wget: unrecognized option: password=testpassword

Measured against the real image, polling once per second for 25 seconds from container start:

endpoint result
/_api/version exit 1 at every single timepoint (401 Unauthorized)
/_admin/server/availability exit 1 for 3s, then exit 0 from t=3s onwards

So the probe never succeeded on any job, ever.

2. My earlier "verified healthy in CI" was a won race, not a working probe

I previously reported this fix as confirmed because a CI run logged
arangodb service is healthy. That was wrong, and the log itself shows why — the lines
appear in this order:

docker create ... --health-cmd "wget -q --spider .../_api/version" ... arangodb:3.11
arangodb service is healthy.          <-- declared healthy...
 Initializing root user...Hang on...  <-- ...before the DB had even initialised

The runner polled .State.Health.Status before Docker had recorded any health state, read
an empty string, and reported healthy. A job that polled a moment later saw starting,
waited, and eventually failed unhealthy — which is exactly the split seen in run
29828464533, where Integration Tests and Haskell Integration Tests had byte-identical
service config and opposite outcomes
.

Do not read arangodb service is healthy in these logs as evidence the probe works.

Fix

Switch all three arangodb services to /_admin/server/availability — the unauthenticated
liveness endpoint intended for exactly this — which removes the race rather than winning it.

Also in this push, a separate fault in the same workflow: stress-test consisted of one
real step, two steps that could not fail, and one that could not pass. Details in the
commit message for d6b241c.

Status: pushed, CI not yet green. I am not calling this verified until the run completes.

@hyperpolymath hyperpolymath changed the title fix(ci): arangodb health check can never pass — curl absent + IPv6 localhost fix(ci): arangodb probe can never pass (401) + stress-test fake/impossible steps Jul 21, 2026
`haskell-integration` ran `cabal build all` / `cabal test all` with
`defaults.run.working-directory: registry`, and cached on
`hashFiles('registry/hypatia.cabal')`.

`registry/` does not exist. It was deliberately deleted in f2fe2df
(2026-03-06), whose own commit message says:

    Remove registry/ (30 files - Haskell, superseded by Elixir)

The CI job was left behind. It has failed EVERY run since: 11 failures
and 1 cancellation across the last 12 runs, never once green. The repo
now contains 0 .cabal files, no cabal.project, no stack.yaml, and one
.hs file — test/soundness/fixtures/code_safety/unsafe_coerce.hs — which
is a scanner test fixture, not a Haskell project.

Until this branch it was masked: the job died at container startup
because the arangodb health probe could never pass. With that fixed the
container comes up healthy and the real error surfaces:

    ##[error]An error occurred trying to start process '/usr/bin/bash'
    with working directory '.../hypatia/registry'. No such file or directory

Removed the job and its two references in the `integration-status`
roll-up (`needs:` entry and the `needs.haskell-integration.result`
expression), which is what kept that gate red as well.

Each run had been installing GHC 9.6 + cabal 3.10 and standing up an
ArangoDB service container before failing at its first `run:` step, so
this also stops burning those runner minutes.

Verified: YAML parses; `haskell-integration` absent from jobs; zero
dangling `needs:` entries and zero dangling `needs.<job>.result`
expressions anywhere in the file; service-options and arango-probe
assertions still pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request Jul 21, 2026
…646)

## Context

`mise.toml` printed roughly **25 warning lines on every shell entry** in
this repo, while
installing none of the tools it warned about. It is developer-ergonomics
only — CI does not
consume it — but it made every command in this repo, human or bot,
arrive buried in noise.

## Three faults

**1. Thirteen tool names that do not exist in the mise registry.**
`denojs`, `pip`, `cargo`, `go-task`, `gofmt`, `isort`, `git`, `gnu-sed`,
`gnu-grep`,
`gnu-tar`, `vitest`, `pytest`, `jest`. Every one failed to resolve, so
every one was pure
noise. Removed, grouped by reason in the file itself:

| reason | names |
|---|---|
| already provided by a listed tool | `cargo` (rust), `gofmt` (go),
`pip` (python) |
| project deps, not system tools | `vitest`, `jest`, `pytest`, `isort` |
| base system binaries | `git`, `gnu-sed`, `gnu-grep`, `gnu-tar` |
| simply misnamed | `denojs` → `deno` |

`go-task` is **dropped, not renamed**. The bare name `task` does not
work either:
`mise registry` lists it as `aqua:go-task/task`, but `task@latest` still
fails to resolve.
That is a trap worth recording — *appearing in `mise registry` output is
not sufficient to
conclude a name resolves.* I initially "verified" all names against
registry listing and it
passed `task`; only running mise caught it.

**2. `erlang` and `elixir` were absent entirely** — despite Elixir being
the language of
`lib/` and all 71 test files. Now pinned to **erlang 27.0 / elixir
1.17**, matching what
`tests.yml` passes to `erlef/setup-beam`, so local `mix test` and CI run
the same toolchain.

**3. The `[alias]` block never did anything.**

```toml
[alias]
build = "cargo build --release || npm run build || go build"
test  = "cargo test || npm test || go test ./..."
```

mise's `[alias]` maps an alias to a **tool plugin** — it does not define
tasks, so none of
these ever ran (mise also warns it is deprecated in favour of
`[tool_alias]`). Deliberately
**not** re-added as `[tasks]`: the `Justfile` already defines `build`,
`test`, `fmt`,
`fmt-check`, `lint`, `doctor` and `heal` properly for the languages this
repo actually uses.
Two task runners sharing verb names — one of which silently falls
through `||` chains into a
different language's build — is worse than one.

## Verification

Controlled comparison, one machine, one shell, both files read (the
fixed copy in a
worktree, the unfixed copy in the parent checkout):

```
warnings citing the FIXED   file : 0
warnings citing the UNFIXED file : 14
```

Every retained name re-checked with `mise ls-remote` — resolution, not
registry listing.

## Expected remaining output

```
mise WARN  missing: erlang@27.0 elixir@1.17
```

until someone runs `mise install`. That warning is **correct and
actionable** — it reports a
genuine local/CI toolchain gap, unlike the previous unfixable typos.

## Acceptance criteria

- [x] `mise.toml` parses as valid TOML
- [x] Zero mise warnings attributable to this file
- [x] Every tool name resolves via `mise ls-remote`
- [x] No `[alias]` section
- [ ] Reviewer confirms dropping `go-task` is acceptable (nothing
in-repo invokes `task`)

## Model tier

**Haiku/Sonnet** — a single config file, no code paths, fully mechanical
once the registry
facts are established. Recorded here so the pattern can be reused:
several other estate
repos carry the same copied `mise.toml`.

## Related

- #640 — CI fixes in `tests.yml` (separate concern, separate branch)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@hyperpolymath

Copy link
Copy Markdown
Owner Author

Final state of this branch — measured, run 29831653210 (1dd31af)

job before after
stress-test failure (11 of last 12 runs) success — twice consecutively
Build Test Images success ✅ success
criterion + baseline gate success ✅ success
Aspect — Rule Module Coverage success ✅ success
E2E — Rust CLI Scan success ✅ success
Haskell Integration Tests failure (never once green) removed — orphan, see 1dd31af
E2E — Elixir Scanner Pipeline failure ❌ still failing — real Elixir test failures
Integration Tests failure (exit 127, no BEAM) ❌ still failing — but now for an honest reason
Integration Status failure ❌ roll-up of the above

ArangoDB now transitions properly in both remaining service jobs:
starting → healthy, rather than the instant pre-init "healthy" that a broken probe used
to produce by winning a race. Rust: 16 test suites ok, 0 failed.

The two remaining failures are not plumbing

Integration Tests previously died at exit 127 — the job installed the Rust toolchain
and then ran mix test with no BEAM on the runner at all. With erlef/setup-beam added it
now runs the suite and reports genuine failures. That is the intended outcome of this PR:
the CI was hiding the state of the test suite, and now it shows it.

Those failures are tracked separately and are not in scope here:

What this PR contains

  1. arangodb probe → /_admin/server/availability on all three services (the previous
    /_api/version probe 401s under ARANGO_ROOT_PASSWORD and busybox wget cannot
    authenticate, so it could never pass — see the correction comment above)
  2. erlef/setup-beam added to integration-tests (fixes exit 127)
  3. stress-test rewritten: two steps that could not fail and one that could not pass,
    replaced with assertions verified locally against the real binaries
  4. haskell-integration removed — its registry/ project was deleted in f2fe2df
    (2026-03-06, "superseded by Elixir") and the job has failed every run since

Ready for review. I have not marked it non-draft because the workflow still ends red, by
design — the red is now #645/#643, not this file.

hyperpolymath added a commit that referenced this pull request Jul 21, 2026
…en grant still required) (#641)

## Context

`verisimdb-data` is the estate fact-store — 302 repos of panic-attack
scan output that the
whole neurosymbolic pipeline (`PatternRegistry → TriangleRouter →
FleetDispatcher`) reasons
over. It **stopped updating on 2026-07-06** and was still stale on
**2026-07-21 (15 days)**.

Nothing alerted, because `estate-rescan.yml` was busy retrying the
failure.

## Root cause

The push step retried *any* push failure with backoff (2/4/8/16s), on
the reasonable
assumption of writer contention. But the actual failure was:

```
remote: Permission to hyperpolymath/verisimdb-data.git denied to hyperpolymath.
fatal: unable to access '...' The requested URL returned error: 403
```

`HYPATIA_DISPATCH_PAT` has **read** on `verisimdb-data` (the clone step
at line 141 succeeds)
but not **Contents:write**. A permission error can never clear by
retrying — so the loop
converted a hard, actionable auth failure into five rounds of
flaky-looking noise, and the
fact-store froze silently.

This is a monitoring failure as much as a permissions one: *the retry
hid the signal.*

## Changes

`.github/workflows/estate-rescan.yml` — narrow the retry rather than
remove it. Contention
retry is still correct and is preserved; auth/permission now fails fast
with a `::error`
naming the exact missing grant.

## Verification

Exercised with a stubbed `git` on `PATH` covering all four paths:

| Scenario | Attempts | Exit | vs before |
|---|---|---|---|
| 403 permission denied | **1** | **78** | was 5 attempts, exit 128 |
| Contention (`! [rejected]`) | 5 | 1 | unchanged |
| Immediate success | 1 | 0 | unchanged |
| Transient ×2 then success | 3 | 0 | unchanged — still self-heals |

`shellcheck` clean; workflow YAML re-parsed (`jobs: ['rescan']`).

## ⚠️ This makes the failure legible — it does not fix it

**Action still required:** grant `HYPATIA_DISPATCH_PAT` `Contents:
write` on
`hyperpolymath/verisimdb-data`. Until that lands, this workflow will now
fail *fast and
clearly* on the first attempt instead of silently wasting 30s and going
stale.

After the grant, verify recovery with:

```
gh workflow run estate-rescan.yml -R hyperpolymath/hypatia
gh api repos/hyperpolymath/verisimdb-data --jq .pushed_at   # must advance past 2026-07-06
```

## Related

Companion to #640 (arangodb health check). Both are Phase 0 foundation
repair: the estate
audit cannot be trusted while hypatia's own tests are red and its
fact-store is frozen.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@hyperpolymath
hyperpolymath marked this pull request as ready for review July 21, 2026 17:22
@hyperpolymath
hyperpolymath merged commit a3f2a8d into main Jul 21, 2026
47 of 50 checks passed
@hyperpolymath
hyperpolymath deleted the fix/arangodb-healthcheck branch July 21, 2026 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant