5.5 KiB
CI Pipeline Postmortem - Release 4.2.46 (2026-01-29)
Summary
Release 4.2.46 triggered the full 42-language test matrix. 31 of 43 test jobs failed (72% failure rate). Root cause analysis revealed three distinct failure categories, none of which indicate actual SDK bugs.
Impact
- 12 languages passed: python, ruby, bash, cpp, fortran, d, nim, v, crystal, fsharp, haskell, prolog
- 31 languages failed: All others
- Pipeline status reported "success" because test jobs have
allow_failure: true
Root Causes
1. QR Test Missing Dependencies (23 failures)
What: QR code generation tests (qr_generate) require language-specific libraries (qrcode npm, python qrcode, etc.) that are not installed in the sandbox environment.
Why it failed: The test treated missing dependencies as a test FAIL rather than a SKIP. The QR test files (e.g., test/qr.js) do require('qrcode') which fails with Cannot find module 'qrcode' in the sandbox.
Why python passed: The qrcode pip package happens to be pre-installed in the sandbox image.
Fix in 4.2.48: QR test now detects missing dependency errors and reports SKIP instead of FAIL. This is honest - we can't test what we can't run.
2. Service Operations 5xx Errors (13 failures)
What: service_destroy and service_exec tests failed with HTTP 5xx errors from the API, even after retry exhaustion.
Root cause - pool imbalance: The cluster has two pool hosts:
- Cammy (
cammy-foxhop-net): 32 vCPUs, load 37.96 (118% utilization), 21 services - AI (
ai-foxhop-net): 32 vCPUs, load 9.26 (29% utilization), 0 services
All 21 services were placed on Cammy while AI sat idle. The API's service placement algorithm appears to always select the first available pool rather than load-balancing across pools.
Why service_destroy was especially bad: A 5xx on destroy could mean the service was actually destroyed but the response was lost. The next retry would get "Service not found" which didn't match the success pattern destroy|deleted|success, and also didn't match any transient error pattern, so it fell into the "no-match" retry path (only 3 attempts, 2s delay).
Fixes in 4.2.48:
service_destroynow accepts "not found" as success (service was already destroyed)- Backoff cap increased from 10s to 30s
- No-match retry increased from 3 to 5 attempts with 3s delay
- Added nginx error messages to 5xx detection (bad gateway, service unavailable, gateway timeout)
Remaining: The pool imbalance is an API-side issue that needs a load-balancing fix in api.unsandbox.com.
3. Inline Code Format Issues (5 failures)
What: exec_basic failed for erlang, clojure, kotlin, objc, and deno. These languages can't run their "hello world" code via build/un -s LANG 'code'.
Why: The hello code format in test-sdk.sh doesn't match what the sandbox expects:
- Erlang: Module-based syntax (
-module(main).) doesn't work for inline execution via escript - Kotlin:
fun main() { println("test-ok") }needs a.ktfile, not inline - Deno: "deno" isn't a valid language name for the API's
-sflag - Clojure: Inline format incompatible with sandbox's Clojure runner
- Obj-C: Requires compilation, can't run inline
Status: Not fixed in 4.2.48. These need per-language hello code adjustments or acceptance that inline execution isn't supported for all languages.
Failure Output Improvement
Problem: When tests failed, the output showed only head -5 of the output file, which often missed the actual error. HTTP status codes (500, 502, 503) were not displayed.
Fix in 4.2.48: Failure output now shows:
- Matched HTTP status codes and error messages (via grep)
- First 5 lines of raw output
- Clear section markers for readability
Change Detection Bug
Problem: The version bump for 4.2.46 changed 14 files across 8 language directories, but the main branch pipeline only ran 8 SDK tests instead of the full matrix. A version bump is a release-level change that should trigger full testing.
Root cause: detect-changes.sh line 140 checked for tests?/|scripts/|\.gitlab-ci\.yml|clients/\{.*\}/ but did NOT check for VERSION or Makefile changes. The clients/\{.*\}/ pattern with literal braces was also a bug - it would only match paths like clients/{template}/ which don't exist.
Fix in 4.2.48: Changed to ^(VERSION|Makefile|tests?/|scripts/|\.gitlab-ci\.yml) - VERSION and Makefile changes now trigger full matrix.
Timeline
| Time | Event |
|---|---|
| 20:20 | 4.2.46 tag pushed, pipelines start |
| 20:25 | Child pipeline 13574 starts full 42-language matrix |
| 20:28 | Python passes 20/20 (first to complete) |
| 20:35 | First failures appear (service 5xx, QR deps) |
| 20:45 | Second wave failures as Cammy becomes more overloaded |
| 21:45 | Pipeline 13574 completes: 12 pass, 31 fail |
| 21:50 | Root cause analysis complete |
| 22:00 | Fixes committed, 4.2.48 tagged |
Lessons Learned
- QR tests assumed dependencies exist - sandbox doesn't have every language's QR library. Tests should detect and SKIP, not FAIL.
- Service placement needs load balancing - a single overloaded host causes cascading failures across all test languages.
- Retry logic needs to understand operation semantics - "not found" after a destroy attempt is success, not failure.
- Core file changes should trigger full matrix - VERSION bumps are release events.
- Failure output must include HTTP status codes - without them, diagnosing API issues requires manual log reading.