Handoff: ContainerNursery audit follow-up PRs #518 (no-op PUT) & #519 (singleton lock) — code done, awaiting merge approval + one clean remote CI pass
Written 2026-07-12 (UTC). RE-VERIFY BEFORE ACTING.
Every state claim below is a write-time snapshot and goes stale. PRs merge, CI re-runs, branches get force-pushed, the buildtest droplet fleet's health changes hour to hour. Before doing anything, re-verify:
- PRs:
gh pr view <n> --repo CodexCoder21Organization/ContainerNursery --json state,mergeStateStatus,statusCheckRollup,mergedAt
- buildtest run health (ground truth beats the flaky frontend):
ssh -p 23 root@198.199.106.165 'stat -c "%y %s" /root/buildtest-data/runs/<id>/build.log /root/buildtest-data/runs/<id>/test-events.jsonl; grep -c test_completed /root/buildtest-data/runs/<id>/test-events.jsonl'
- A fresh query always wins over this document.
Mission summary
This handoff is the follow-up tail of handoffs/2026-07-07-containernursery-challenge-fixes-noop-put-singleton-lock-cli-flags.md (which is now fully resolved and should be deleted — see below). The user's instruction this session was:
"Let's drive [that handoff] - determine if it is a good idea as per our best practices, or if it is bad. If good, drive it to completion."
Verdict reached: the handoff was good — its three changes are root-cause fixes at specific code sites (unconditional container teardown removed; a single-instance OS file-lock invariant added; a genuine CLI capability gap filled), not timeout/fallback band-aids. So it was driven to completion.
Driving it surfaced new work: per global CLAUDE.md the two merged ContainerNursery PRs still needed their post-merge test-comprehensiveness + adversarial review passes, and the open CLI PR needed the same. Those reviews (run via codex exec -m gpt-5.6-sol -c model_reasoning_effort="high") each returned real findings, which were fixed:
- ContainerNurseryCli #38 (
--max-metaspace-mb / --max-concurrency flags): review findings fixed on-branch, then MERGED.
- ContainerNursery #496 (no-op PUT): audit found two MAJOR production defects → fixed in follow-up PR #518.
- ContainerNursery #497 (singleton lock): audit found three MAJOR production defects → fixed in follow-up PR #519.
#518 and #519 are the in-flight work this handoff covers.
What was found and done (the chain)
-
Confirmed the three original deliverables all merged (verified live, not remembered):
-
Post-merge audits (codex gpt-5.6-sol, two passes each: TESTING.md-comprehensiveness + adversarial). Findings were real and were fixed with strict fail-first TDD (each fix's test was shown to fail on the pre-fix code before the fix flipped it green):
-
#496 → follow-up #518 (ConfigManager.kt, RouteManagementApiE2ETest.kt; branch noop-put-followup-fixes @ 646352b1103e0166302174b6031604f70c559402):
- MAJOR: canonicalization stripped explicit JSON nulls at every nesting level, so a genuinely different config could compare equal and the no-op branch would silently skip a real update. Fixed: explicit nulls and unknown keys now count as changed (conservative — bounce the container when unsure).
- MAJOR: the no-op branch skipped persistent-route auto-start, so an unchanged
keepWarmSeconds < 0 route whose container had died was never restored. Fixed: on a no-op, a running container is left untouched but a dead persistent route is started.
- Verification: 35 targeted tests, 3 expected fail-first failures, all 35 green after; full remote CI green.
-
#497 → follow-up #519 (ContainerNurseryServer.kt, NurseryPidFile.kt; branch singleton-lock-followup-fixes @ 04a904303ebc1362704a374134021910169fa085):
- MAJOR: the lifetime lock was released by an independent JVM shutdown hook that could run before provider/container cleanup, letting a watchdog replacement acquire the lock and start while the incumbent was still draining — recreating the original double-spawn overlap. Fixed: lock held until the process actually exits.
- MAJOR: a losing process read holder metadata unsynchronized and could advise stopping a stale/
unknown/PID-reused process. Fixed: holder PID + start-instant validated against a live process before any actionable "stop PID N" message.
- MAJOR: OS-vs-JVM lock selection was by
fileSystem === FileSystem.SYSTEM identity, so any real-disk ForwardingFileSystem wrapper silently got the per-JVM semaphore fallback (no cross-process protection). Fixed: fail closed for non-system filesystems unless JVM-only locking is explicitly opted into.
- One MINOR (swallowed exceptions in
close()) was rejected with documented reasoning in the PR (no public non-reflection path can induce it).
- Verification: 12 targeted singleton/pid-file tests green + real child-JVM E2E tests (contention, incumbent-survival, SIGKILL-release, shutdown-ordering); 776 local Gradle tests green.
-
CI shepherding hit a wall of buildtest INFRASTRUCTURE failures (not code). Multiple challenge reports were filed and auto-merged:
-
#38 was landed after its own run finally cleared the queue (run 2d5037e1 green → enqueued via GraphQL enqueuePullRequest → MERGED). #518 reached fully-green/CLEAN. #519's remote check never got a clean pass: every attempt died in droplet provisioning — rate-limit-queue budget exhaustion, then (most recent run e7ca05bb) 329 tests passed then some shards hit SandboxException: java.lang.InternalError ... DropletServiceClientImpl.listDroplets() NullPointerException during a mid-run droplet allocation, failing the whole run. The code is not implicated — an earlier run reached 327/328 with the sole failure being a known load-flaky HTTPS stress test.
Relevant PRs / refs (write-time state — RE-VERIFY)
| PR | Title | State @ write time | Branch / head SHA |
|----|-------|--------------------|-------------------|
| ContainerNursery #518 | Fix no-op PUT /routes follow-ups: preserve null semantics, persistent-route auto-start, conservative unknown-key handling | OPEN, all 5 checks green, mergeStateStatus CLEAN | noop-put-followup-fixes @ 646352b |
| ContainerNursery #519 | Singleton-lock follow-ups: hold exclusivity to process exit, verified-incumbent messaging, fail closed on non-system filesystems | OPEN; 4/5 green; kotlin.build (remote) FAILURE (infra); mergeStateStatus BEHIND | singleton-lock-followup-fixes @ 04a9043 |
| ContainerNurseryCli #38 | add-route/update-route --max-metaspace-mb and --max-concurrency | MERGED 2026-07-12 01:18 UTC | — |
| ContainerNursery #496 | PUT /routes no-op skip-restart | MERGED 2026-07-08 | — |
| ContainerNursery #497 | Refuse second CN instance (singleton lock) | MERGED 2026-07-08 | — |
ContainerNursery main head at write time: eecb5d4a79338a76e7bbf1e25e5169cb13176b80.
All code is pushed to the branches above — nothing is unsaved. The session's working clones live in the session scratchpad (…/scratchpad/ContainerNursery-496, …/ContainerNursery-497, …/ContainerNurseryCli-38) which is ephemeral; the durable artifacts are the pushed branches. Re-clone if needed.
Next steps (ordered)
-
Re-verify current state first — run the gh pr view commands above for #518 and #519. #518 may already be mergeable; #519's remote check state and BEHIND status will have changed.
-
#518 — awaiting user merge approval only. It is all-green and CLEAN. It is a code PR, so it needs explicit user approval before merging (NOT pre-approved like this handoff). Once approved, enqueue via GraphQL enqueuePullRequest (repo uses a mandatory merge queue; gh pr merge --auto is rejected).
-
#519 — rebase, then re-drive the remote check, then user approval.
- Rebase on main:
git fetch origin && git rebase origin/main && git push --force-with-lease on singleton-lock-followup-fixes (currently BEHIND eecb5d4a). This re-triggers CI.
- The
kotlin.build (remote) FAILUREs have all been DigitalOcean droplet-provisioning infrastructure, never the code (proven: a prior run reached 327/328, and run e7ca05bb reached 329 passing tests before shards hit a DropletServiceClientImpl.listDroplets() NPE). Do NOT "fix" the code for these — re-drive when the DO fleet is healthy. Prefer a low-load window (host load was 97 at peak Saturday evening, ~19 by early Sunday). Warm webhooks (curl https://github-webhooks.wasmserver.com/ ×3) then gh api -X POST repos/CodexCoder21Organization/ContainerNursery/check-suites/<suiteId>/rerequest; verify a NEW run id appears on the head commit's check-runs before trusting it; watch with coursier launch buildwatchman:build-watchman:0.0.12 -r https://kotlin.directory -- --run <id> (fetch a working cs per the local-coursier-broken memory — /usr/local/bin/coursier is wrong-arch).
- Once green, #519 also needs explicit user merge approval (code PR).
-
Watch the buildtest infra defects. If the three filed challenges (§3 above) get fixed upstream, #519's check will pass first try. Until then, expect to re-drive during low-load windows and to hit the 30-min GitHub-wrapper timeout on any run that queues too long.
-
Close out THIS handoff once #518 and #519 are both merged: delete this file via a PR and merge it (handoff-dir deletions are pre-approved — enqueue + merge immediately).
Reusable / operational knowledge
- Delegate implementation to codex gpt-5.6-sol, orchestrate from the main loop (user directive). Canonical invocation:
codex exec -m gpt-5.6-sol -c model_reasoning_effort="high" --dangerously-bypass-approvals-and-sandbox "<prompt>" from the target clone; codex exec resume <session-id> … continues a review session into its fix pass with context intact.
/code/workspace is root-owned / NOT writable by user u. Clone into the session scratchpad. gh auth setup-git is needed before git can push over HTTPS (plain git clone https://… fails "could not read Username"); or clone via gh repo clone.
- Global git identity may be unset at session start:
git config --global user.name "codexcoder21" && git config --global user.email "ucorp35@gmail.com".
- buildtest frontend flaps under load — trust host ground truth, not the API.
https://buildtest.kotlin.build/api/* returns 503 / times out while runs keep executing. The real signal is on the host: /root/buildtest-data/runs/<id>/build.log mtime+size and test-events.jsonl (grep -c test_completed). build-watchman 0.0.12 correctly classifies these as degraded observability, not run death.
- WEDGE vs infra death: a run is only wedged if build.log mtime+content AND test-events are all frozen >20 min. A provisioning failure (
INFRASTRUCTURE/PROVISIONING, DropletAllocationException, rate-limit-queue budget exhausted, listDroplets NPE) is a terminal infra failure, not a wedge — re-drive, don't delete-and-nothing. Only the single orchestrating operator deletes/rerequests; log intent to /root/cn-watchdog.audit first.
- CN change-detection correctness (#518): the danger is mis-classifying a real change as a no-op (silently dropping a config update). The fix errs toward "changed" whenever unsure (unknown key, nested null). Keep that invariant if you touch it.
- CN singleton-lock correctness (#519): a stale lock from a crashed CN must remain acquirable (OS releases
FileLock on death); refusing to start must never kill the incumbent or touch its containers; the lock must be held for the whole process life (a GC'd FileChannel releases its lock — hold a strong reference).
- Merge-queue enqueue (all these repos):
gh pr merge --auto is rejected; use GraphQL enqueuePullRequest with the PR node id, then poll gh pr view <n> --json state until MERGED.