Handoff: ContainerNursery merge-queue flake fixes + production OOM fix + buildtest lease/rendering — awaiting user approval to merge & deploy
Written 2026-07-12 ~17:00 UTC
⚠️ RE-VERIFY BEFORE ACTING. Every state claim below is a write-time snapshot and WILL have drifted (this effort already saw ~12h of drift between sessions — two PRs that were "parked red" became green on their own). Before doing anything, re-check:
- PRs:
gh pr view <n> --repo CodexCoder21Organization/<repo> --json state,mergeStateStatus,statusCheckRollup,mergedAt
- buildtest run ground truth:
ssh -p 23 root@198.199.106.165 'grep -o "\"status\": *\"[A-Z]*\"" /root/buildtest-data/runs/<runId>/run.json; tail -3 /root/buildtest-data/runs/<runId>/build.log'
- CN prod OOM/restart status:
ssh -p 23 root@198.199.106.165 'grep "watchdog restarted CN" /root/cn-watchdog.audit | tail -5'
A fresh query always wins over this document.
Mission summary
Original request (2026-07-11): pick up three follow-up items —
- the buildtest lease-staleness-during-admission defect (challenge
challenges/2026-07-10-2138-buildtest-lease-staleness-watchdog-kills-runs-that-wait.md) — "it cost this PR two CI attempts and will bite others";
- a fix-flakey-test pass on
testDangerJarFile_oversizedStdoutLineDoesNotOomSupervisor and testHttpsFacade_slowUpstreamDoesNotHeadOfLineBlockOtherRoutes (30s-timeout flakes under slow shards);
- issue #498's remaining fixes #2/#3 (bounded/rotating CN logs), after which the cn-log-guard stopgap can be retired. "If/when the challenge is completely done, it should be deleted."
The effort expanded, in order, as evidence demanded:
- The lease fix's server side was already merged/deployed overnight (BuildTestEmbedded #289/#290 → embedded 0.0.261 → buildtest-server 0.0.164, live since 2026-07-11 09:16Z); what remained was the kotlin-build-ci result-rendering half (render infra kills as INFRASTRUCTURE/PROVISIONING, not "all N tests failed").
- While shepherding CI, the production ContainerNursery main JVM was found OOM-crash-looping ~hourly. Heap-dump (Eclipse MAT) analysis root-caused it to a transient oversized-JSON parse allocation in
foundation.url:protocol Libp2pRpcProtocol.parseValidatedJsonBody — NOT the coincident gossip-log storm. Fixed upstream.
- The three CN PRs (#515/#516/#517) kept getting evicted from the CN merge queue by two intermittent pre-existing flaky tests not related to our code. Investigating them (per the user's explicit instruction to "understand the underlying cause and propose fixes") uncovered that one —
HttpsAcceptLoopSurvivesHandlerFatalErrorTest — was a real production accept-loop-wedge bug (same class as the 2026-04-20 kotlin.directory TLS outage), now fixed.
User instructions that gate the remaining work:
- "once the fixes are approved, you can merge them and deploy the new versions" — i.e. the user wants to approve PR #520 (and the merge sequence) before merge, and separately decide who runs the prod deploy.
- Earlier steer (memory
feedback-prefer-capability-over-hardening-prs): prefer capability/high-impact over hardening; the residual log-firehose work is parked as low-leverage.
What was found and done (the chain, verified)
-
Lease-staleness rendering fix (kotlin-build-ci). TestResult.passed is false for every non-PASSED status, and several result paths used !it.passed as the failed predicate, so a droplet-less infra kill that discovered 326 tests but ran 0 rendered all 326 as "failed." Fixed to count only explicit TestStatus.FAILED, count PENDING separately as "not run," and classify droplet-less/zero-executed terminal failures as INFRASTRUCTURE/PROVISIONING. MERGED: https://github.com/CodexCoder21Organization/kotlin-build-ci/pull/175 (2026-07-11 21:29Z, service 0.0.64). Verified by an end-to-end test reproducing run 80a9b697's shape.
-
Production CN OOM crash-loop → upstream parse-budget fix (UrlProtocol). MAT on the CN-main heap dump (java_pid2377401.hprof, ~110 MiB live at OOM) showed the failing thread expanding an org.json.JSONTokener StringBuilder inside Libp2pRpcProtocol.parseValidatedJsonBody. Root cause: the existing concurrent parse-memory budget charged raw wire bytes at 1×, but String+JSONObject parsing costs ~5–7×, and acquireParseBudget clamped an over-budget frame with coerceIn and admitted it anyway. Fix: charge bytes.toLong() * PARSE_AMPLIFICATION(=7) and reject a frame whose worst-case footprint exceeds the budget with a descriptive IOException, instead of OOMing. Also restored a resolver-0.0.600-compatible constructor bridge (0.0.342 was missing it → republished 0.0.343). MERGED: https://github.com/CodexCoder21Organization/UrlProtocol/pull/355 (2026-07-11 22:44Z), artifact foundation.url:protocol:0.0.343 published to kotlin.directory. Memory: cn-oom-parse-amplification.
-
The two CN merge-queue flakes → CN #520 (review-passed, NOT merged).
- Flake A — ephemeral-port bind TOCTOU (test-only). Both
tests/testHttpsFacade_slowUpstreamDoesNotHeadOfLineBlockOtherRoutes.kts and HttpsAcceptLoopSurvivesHandlerFatalErrorTest.findFreePorts() allocated a port via ServerSocket(0), closed it, then bound that number later — a concurrent test grabs it in the gap → Failed to bind HTTPS server to port <n>: Address already in use. Reproduced 10/10 with a deterministic claimant. Fixed with a bounded (≤5) retry that fully tears down between attempts and retries only the recognized bind race.
- Flake B — REAL production accept-loop wedge (
HttpsFacadeProvider.kt, +215/−147). Under handler OOM, the coroutine accept loop could be killed outside its catch by CoroutinesInternalError; handleMixedModeTls could swallow an Error; and OOM recovery cancelled a damaged handler set while holding a lock shared by every HTTPS listener — leaving sockets bound with nothing serving (the 2026-04-20 zombie). Fix: run each accept loop on a dedicated platform thread (off coroutine machinery); catch Throwable on back-off; never synchronously log an OOM (the log lock itself can wedge); rethrow handler Errors; rotate the whole handler execution-unit generation on recovery — publish the replacement before tearing down the old and release the shared lock before shutdownNow(); per-generation permit/socket ownership so lost fatal callbacks can't leak. Evidence: reproduced the real wedge 3/3 pre-fix; passes 3/3 focused + full Gradle + 328/328 kompile suite post-fix; reintroduced-bug check (revert to Exception-only guards + inject AssertionError) confirms the strengthened test still catches the wedge. codex F summary: /code/workspace/codex-F-summary.md (local to the authoring box; not committed).
- My final review of #520 PASSED with no findings — each production change maps to a specific reproduced failure mode; swallow-under-OOM points are intentional and commented; Errors are rethrown not consumed.
-
BuildTestServerService #206 CLOSED as superseded — its production embedded-0.0.261 pin bump was already delivered by #205; its residual test-fixture-pin alignment had drifted (main advanced to embedded 0.0.263 via #207). https://github.com/CodexCoder21Organization/BuildTestServerService/pull/206
-
CN production is still OOM-restarting (write-time: /root/cn-watchdog.audit shows 51 total watchdog restarts, most recent 2026-07-12 13:17Z and 16:17Z — note the exact 3h spacing may be a periodic restart rather than an OOM; re-verify by tailing the audit and checking for OOM lines). The point stands: the OOM fix (0.0.343) is merged upstream but NOT deployed, so whatever residual crash pressure exists is unaddressed in prod until the CN redeploy (step below).
Relevant PRs / refs (write-time state — RE-VERIFY)
| PR / ref | Write-time state | Notes |
|---|---|---|
| kotlin-build-ci #175 | MERGED 2026-07-11 21:29Z | Infra-failure rendering; service 0.0.64 |
| UrlProtocol #355 | MERGED 2026-07-11 22:44Z | OOM parse-budget fix; artifact foundation.url:protocol:0.0.343 |
| BuildTestServerService #206 | CLOSED (superseded by #205) | do not reopen |
| ContainerNursery #520 | OPEN, BEHIND; bld-all-tests + kotlin.build = FAILURE; build/gradle/release = SUCCESS; review-passed, awaiting USER approval | branch fix/https-facade-test-flakes, head 8b1f10cb6. kotlin.build red = the droplet-service NPE below (run bf2c1aaa), NOT the code. Needs rebase (BEHIND) + re-run. |
| ContainerNursery #515 | OPEN, CLEAN, all checks GREEN (drifted green overnight) | flaky-test fixes; head cee0fbff7 |
| ContainerNursery #517 | OPEN, CLEAN, all checks GREEN (drifted green overnight) | pins foundation.url:protocol:0.0.343; local coord 0.0.145; head f699cc30e |
| ContainerNursery #516 | OPEN, BEHIND; kotlin.build = FAILURE | issue #498 fixes #2/#3 (bounded rotating logs); head e8e604f61 |
| ContainerNursery issue #498 | OPEN | stays open until cn-log-guard retired |
| Challenge challenges/2026-07-10-2138-buildtest-lease-staleness-watchdog-kills-runs-that-wait.md | still present in PlanRepository | delete once the lease workstream is fully done (see next steps) |
Note the important nuance: #515 and #517 are currently green by luck (their latest merge-queue runs happened not to hit the flakes). #520 is the PR that makes the CN suite reliably green. Merging #515/#517 without #520 leaves the flakes live for the next person.
Next steps (ordered — re-verify current state first)
-
Re-verify everything with the commands in the banner. The green/red picture of #515/#516/#517/#520 changes hour to hour because the flakes are intermittent and droplet infra flaps.
-
Get the two user decisions (both were asked via AskUserQuestion; user had not answered at handoff):
- (a) Approve merging #520 + the sequence? (recommended: yes — review passed, it's a real production-resilience fix.)
- (b) Who runs the production CN redeploy? (I offered to do it after merge; user may prefer to.)
-
Merge #520 first (after approval). It is BEHIND → rebase fix/https-facade-test-flakes onto origin/main, re-run, enqueue via the CN merge queue. Expect to re-drive past intermittent flakes and the droplet-service infra NPE (below) — this is normal shepherding, not a code problem. Do NOT gamble-merge #515/#517 ahead of #520 (leaves flakes live) unless the user explicitly wants speed over cleanliness.
-
Rebase #515, #516, #517 onto the post-#520 main and merge them. #516 in particular re-flaked historically on the exact HttpsFacade test #520 fixes, so rebasing it onto #520 is what makes it reliably green. Merge order within CN: #520 → then #515/#517/#516 in any order (all independent once rebased).
-
Production redeploy of ContainerNursery (pending decision 1b). This is the step that actually stops the OOM crash-loop — the 0.0.343 fix is only live once CN is rebuilt against it (#517 pins it) and redeployed to 198.199.106.165. Use the container-nursery-deploy / hardware-fabric-processes skills (NOT raw SSH unless those are down); coordinate via appending to /root/cn-watchdog.audit. After deploy, verify: no new OOM restarts, bounded/rotating logs present (#516).
-
Retire the cn-log-guard stopgap on prod (/root/cn-log-guard.sh + reconsider /etc/logrotate.d/container-nursery) once #516's rotation is DEPLOYED and verified healthy, then comment on / close out issue #498. Task tracked; blocked on the deploy.
-
Delete the merged lease-staleness challenge file once the lease workstream is fully done — i.e. after #175 merged (done) AND you're satisfied the rendering fix is deployed where it needs to be. git rm challenges/2026-07-10-2138-buildtest-lease-staleness-watchdog-kills-runs-that-wait.md, PR + auto-merge (challenge-dir deletions merge immediately like handoffs). The server-side lease fix (embedded 0.0.261) is already live; the rendering fix (#175) is merged; confirm kotlin-build-ci 0.0.64 is deployed before deleting.
-
(Low priority, parked) Residual log-firehose. Rate-bound per-request HTTP_ERROR/401 facade logs + demote per-service-per-peer FWD-SHARE/PEX-SHARE gossip logging so no logging path is unbounded. Parked as low-leverage per the user (memory feedback-prefer-capability-over-hardening-prs) — #516's rotation already bounds the disk and #355 fixes the crash. Documented in challenge 2026-07-11-1012-containernursery-main-process-oom-crash-looped-all-morning.md and the issue-#498 evidence comment. Do NOT build it out without user go-ahead.
Reusable / operational knowledge
- Live droplet-service infra defect blocking CN merge-queue runs: buildtest kotlin.build runs are failing with
INFRASTRUCTURE/PROVISIONING: Failed to create droplet in all regions (sfo3, nyc3). Last failure: SandboxException ... DropletServiceClientImpl:listDroplets() java.lang.NullPointerException. This is a droplet-service-server bug, not the PR's code, and it will block any CN merge-queue run until it recovers. Diagnose from the buildtest run's build.log on the host (/root/buildtest-data/runs/<runId>/build.log). If persistent, it's worth its own upstream fix in the droplet service / a challenge — was newly observed at this handoff, not yet filed.
- CN merge queue is flaky by nature (pre-#520): two intermittent tests (
testHttpsFacade_slowUpstreamDoesNotHeadOfLineBlockOtherRoutes port-bind, HttpsAcceptLoopSurvivesHandlerFatalErrorTest OOM-recovery) evict PRs ~50% of runs. #520 fixes both. Until #520 lands, re-driving is a coin flip — merge #520 first.
- Merging through the CN merge queue:
gh pr merge --auto is disabled; enqueue via GraphQL enqueuePullRequest. build-watchman 0.0.11 --to-merged (coursier launch buildwatchman:build-watchman:0.0.11 -r https://kotlin.directory -- --repo <o>/<r> --pr <n> --to-merged) enqueues + shepherds to MERGED and auto re-enqueues one eviction. Working coursier is ~/bin/coursier (the /usr/local/bin/coursier is broken ARM64). Memory: coursier-broken-on-code-box.
- codex gpt-5.6-sol invocation (all the implementation was delegated to it): memory
codex-gpt55-invocation — codex exec --model gpt-5.6-sol -c model_reasoning_effort=high --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check -C <dir> '<one-line prompt pointing at a task file>' < /dev/null. Watch for the cybersecurity content-filter on DoS/OOM-shaped test code (memory codex-cybersecurity-content-filter) — de-escalate to reliability/robustness framing.
- Two commits/branches to know: UrlProtocol OOM fix
496d324 (merged via #355); CN flake fix branch fix/https-facade-test-flakes head 8b1f10cb6 (PR #520, unmerged).
- BUILDTEST_DROPLET_LIMIT history and the CN-config-route silent-no-op gotcha: memory
buildtest-droplet-limit-config (65 active since 2026-07-11 10:08Z CN restart).
- Nothing unsaved is at risk: all code work is pushed to its PR branches; the only uncommitted artifacts are codex session logs/summaries under
/code/workspace/ on the authoring box (not needed for pickup — the PRs and this handoff carry everything).