Handoff: BuildTest droplet-limit admission — hermetic e2e tests + 4 admission bug fixes across 4 PRs (all OPEN, CI red/stuck at write time, driver session died)
The user's concern, escalated over four messages (verbatim intent preserved):
The user's concern, escalated over four messages (verbatim intent preserved):
Moves it out of every priority list and into ArchiveArea.
⚠️ RE-VERIFY BANNER — all state below is a write-time snapshot and is already partly stale. The driver session and its
codex execworker are dead, CI runs referenced here were mid-flight or failed on infrastructure, and main has moved substantially (six related PRs merged 2026-07-11→12; see the chain). Before acting, re-verify every claim:
- PRs:
gh pr view <n> --repo CodexCoder21Organization/BuildTestEmbedded --json state,mergeStateStatus,statusCheckRollup,mergedAt(and same for theBuildTestServerServicePR).- A buildtest CI run's real status:
https://buildtest.kotlin.build/run?id=<runId>(the GitHub check can readIN_PROGRESSfor a run whose executor died hours ago — see PR 294).- Merged main state:
gh pr list --repo CodexCoder21Organization/BuildTestEmbedded --state merged --limit 12. A fresh query always wins over this document.
A fresh session drove this forward. Main independently landed the same fix series (#288–#308), so two of the four PRs are now largely redundant. A codex gpt-5.6-sol reconciliation pass (rebased each branch onto origin/main, dropped hunks main already implements, ran hermetic tests locally) produced these evidence-backed verdicts:
| PR | Verdict | Detail |
|----|---------|--------|
| BuildTestEmbedded #294 | KEEP | Rebased + force-pushed 00359cc3. Dropped reservation/fleet-listing hunks redundant with #298/#300; kept the 14 hermetic droplet-limit admission tests + unique src (provider-quota watchdog authority during retries, durable admission-grant timing, persisted shard partitions). Local: 19/19 changed + 13/13 regression pass. CI IN_PROGRESS at write time (run started 21:42 UTC). ⚠️ Retains reserved artifact coord 0.0.276 (not main's published 0.0.267) because kompile dedup discarded the local build otherwise — verify this is sound before merge. |
| BuildTestServerService #209 | KEEP | Rebased + pushed 2ec26c2f (downstream VT-carrier-pinning fix, needed because main's #304 moved orchestration to Java-21 virtual threads). CI IN_PROGRESS (run started 22:16 UTC). An earlier run this session (ee8f6dd4) failed — re-verify the new run's outcome. |
| BuildTestEmbedded #291 | SUPERSEDED (partial) | Bug A (restart recovery bypasses admission) → main #300; Bug B (quota-422 resume → requeue PENDING) → main #297. Unique survivors worth salvaging: stale-persisted-droplet-ID preflight before treating restart reservations as usable, and persisting PENDING before deletion is externally observable. Reconciled local head preserved at branch salvage/291-reconciled-ae2c77d on origin. |
| BuildTestEmbedded #301 | SUPERSEDED (partial) | Bug C → main #296; Bug D (watchdog counts queue-wait) → main #289/#290; execution-timing → main #300's admissionGrantedAt. Only unique survivor: a 15-line ManualClock test-stabilization hunk in e2eShardedMixedProvisioningAndTestingFailureFinalizesStartedTests.kts. Reconciled local head preserved at branch salvage/301-reconciled-c7e0fd1 on origin. |
Revised conclusion plan (replaces the original Next steps below):
0.0.276 coordinate check on 294); then leave for the user to merge (never auto-merge code PRs).salvage/291-reconciled-ae2c77d into a small, comprehensively-tested new PR, then close #291. The salvage material is preserved on origin regardless.salvage/301-reconciled-c7e0fd1) is marginal; fold it into 294 or a tiny test-only PR, or drop it, then close #301.salvage/* branches so closing loses nothing.Note: the driver session paused here at the user's request (workers/loops stopped). At pause, PR 294 & 209 CI were still IN_PROGRESS — re-verify their outcomes first before the final review.
The user's concern, escalated over four messages (verbatim intent preserved):
BUILDTEST_DROPLET_LIMIT is a real concurrency cap — waitForAdmission() blocks a run until activeDroplets + reservedDroplets + requestedDroplets <= dropletLimit, and <= 0 disables admission entirely (unlimited). Source default is 0.Work followed the global CLAUDE.md division of labor: codex gpt-5.6-sol (high reasoning) did all implementation, tests, reviews, and CI-shepherding; Claude only defines problems and does the final review (findings routed back to codex, never patched by Claude). Never merge these PRs — the user merges. Tests are hermetic (FakeDropletService/in-file DropletService fakes + ManualClock); no real DigitalOcean, no mocks.
~11 real defects were found and fixed across the session (verified by reproducers that failed before the fix). The production trigger: url://buildtest/ had no BUILDTEST_DROPLET_LIMIT set (=0=admission disabled) until 2026-07-10; the only cap was DigitalOcean's account quota (422s). See memory buildtest-droplet-limit-config (history UNSET→100→85→65; the live value is now 65 in /root/ContainerNursery/config.json, set by a different lane for DO-account oversubscription).
The fixes were split across four PRs (three in BuildTestEmbedded, one downstream in BuildTestServerService):
waitForAdmission slept on a backoff instead of being notified on release. Fixed with a lock-protected wait/notify + a clock.scheduled fallback notify; also fixed reservation double-counting (consume the reservation exactly when the droplet becomes provider-visible) and a missed-fallback-notification race.waitForAdmission. Quota-422 during resume → permanent FAILED ("Bug B"): resume-time DO quota errors now requeue as PENDING via a shared allocation-failure helper. Recovery now sorts oldest-first and enqueues admission placeholders; shard plans persisted across restart; readmission state made observable atomically.tests/idempotentDropletProvisioningDoesNotPinVirtualThreadScheduler.kts (a scheduler-sentinel VT must still be schedulable while a create is gated).Why this is being handed off now (verified):
codex exec wrap-up worker (PID 506256, ~12h old) died sometime after 05:08 UTC. No codex exec process is alive (ps confirms). Its log froze at 451 MB.CronList → "No scheduled jobs"), which is why updates stopped between ~05:08 and ~17:00 UTC.git status clean + git log @{u}.. empty in each of /code/workspace/BuildTestEmbedded, BuildTestEmbedded-bugfix, BuildTestServerService-aux-fix; BuildTestEmbedded-main-diagnostic is a detached read-only checkout at ba22f32.)b68acd34 (its last commit is "Preserve admitted execution timeout across restart"). The carrier-pinning test instead became PR 209 in the downstream repo. Whether the failed-boot droplet hardening I watched being drafted in the log was committed anywhere is unverified — grep for it before assuming PR 301 is complete (see Next steps).Main has diverged significantly (merged since these branches were cut — re-check, several overlap these PRs' subject matter):
| PR | Branch / head | CI (write-time) | Notes |
|----|---------------|-----------------|-------|
| BuildTestEmbedded #294 | droplet-limit-e2e-tests @ d22ae7b3 | STUCK — run e4b18150 IN_PROGRESS since 03:50 UTC (13h+ = lost/zombie dispatch) | 13 hermetic scenarios + wake-on-release/reservation/race fixes. Never got a green verdict. Prior attempts failed on infrastructure (DO account rate-limit exhaustion; run d8130f89 = "0 passed, 0 failed, 528 total"). |
| BuildTestEmbedded #291 | fix-restart-recovery-droplet-admission @ 74771f73 | FAILURE — run a10cb66a at 06:22 UTC | The 4 post-rebase test failures were addressed by 74771f73 ("Make restart readmission state observable atomically"); this run then failed on a different infrastructure error: SandboxException: SJVM virtual method invocation timed out after 120s: BuildTestServiceClientImpl.getTestResults. Needs a re-run, not (necessarily) a code fix. |
| BuildTestEmbedded #301 | fix-queued-run-cancellation-and-watchdog @ b68acd34 | SUCCESS (run from 2026-07-11 17:17Z) | Green but stale — never rebased onto current main (was DIRTY; now UNKNOWN). Codex's intended review amendments did not land here (see chain). |
| BuildTestServerService #209 | fix-virtual-thread-droplet-create-pinning @ 87867ba | FAILURE — CI at 02:47 UTC | "Prevent droplet creation from pinning virtual-thread carriers." Failed CI predates the DO rate-limit reset — likely infrastructure; re-verify the failure category. |
Supporting context in session memory (~/.claude/projects/-code/memory/): buildtest-droplet-limit-config, codex-gpt55-invocation, merge-queue-eviction-playbook, w3walletdaemon-merge-path.
e4b18150 still IN_PROGRESS, or did it finally resolve? A 13h+ run is a lost dispatch — do not wait on it.origin/main, resolve conflicts faithfully, and re-run each branch's own new tests locally (export PATH="$HOME/bin:$PATH" for working coursier; scripts/test.bash). This is codex's job per the division of labor — dispatch codex gpt-5.6-sol (high reasoning) per the codex-gpt55-invocation memory; Claude does only the final review.scratchpad/pr294-ci-failure-diagnosis.md (TailingInputStream liveness hypothesis) — do not weaken tests/timeouts.getTestResults RPC timeout as a real defect to root-cause, not to paper over.git log --all --oneline -S 'scheduler-sentinel' and grep -rl 'failedDropletDeletionWaiters' /code/workspace/*/ to confirm whether the failed-boot droplet hardening codex drafted was committed anywhere or was lost when the worker died. Then rebase and re-run./loop 20m /status-report).codex gpt-5.6-sol invocation + failure modes: memory codex-gpt55-invocation (exact codex exec ... --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check command; always < /dev/null + task-in-a-file to avoid the stdin hang and the pkill -f self-kill; model-capacity crashes leave the tree intact — resume from git status).IN_PROGRESS for many hours after the executor droplet died. Always cross-check https://buildtest.kotlin.build/run?id=<runId>; treat >~2.5h as lost and re-request."0 passed, 0 failed, N total" = suite never ran (provisioning/quota); SandboxException: SJVM ... timed out after 120s = foundation.url sandbox flake; explicit named test failures = code. Only the last warrants a code change.POST /v2/droplets ... ratelimit-remaining=0). Re-requesting after the window resets (the run page prints provider reset=<ts>) is the remedy; a timed rerequest via gh api -X POST repos/.../check-runs/<id>/rerequest works.export PATH="$HOME/bin:$PATH" (the /usr/local/bin/coursier is wrong-arch — memory coursier-broken-on-code-box)./code/workspace/BuildTestEmbedded (294), /code/workspace/BuildTestEmbedded-bugfix (currently on 291's branch), /code/workspace/BuildTestServerService-aux-fix (209), /code/workspace/BuildTestEmbedded-main-diagnostic (read-only main checkout). Scratchpad task files + the pr294-ci-failure-diagnosis.md are under /tmp/claude-1000/-code/40d56168-7a5d-423f-a0f5-7cb52b42f4d4/scratchpad/.