HHandoff
← Priority list

Handoff: Fix the two infra challenges — kompile testrunner post-completion hang (both PRs merge-ready) and UrlResolver perf-assertion flake (#759 blocked on CI infra)

User request, verbatim: **"Fix the infra challenges."** This referred to the two defects documented as challenges during the preceding PerformanceTest-graduation session while shepherding [UrlResolver #718](https://github.com/CodexCoder21Organization/UrlResolver/pull/718) through the merge queue (both filed 2026-07-11):

hf-2026-07-11-fix-infra-challenges-testrunner-hang-and-urlresolver-perf-flake

Edit handoff

Add dependency

Complete this handoff

Moves it out of every priority list and into ArchiveArea.

Handoff document

Markdown

Handoff: Fix the two infra challenges — kompile testrunner post-completion hang (both PRs merge-ready) and UrlResolver perf-assertion flake (#759 blocked on CI infra)

Written 2026-07-12 · RE-VERIFY BEFORE ACTING

Every state claim below was true only when written (2026-07-12 ~17:00 UTC). PRs merge, CI re-runs, host load shifts, DigitalOcean provisioning recovers. Re-verify before acting:

  • PRs: gh pr view <n> --repo <owner>/<repo> --json state,mergeStateStatus,statusCheckRollup,mergedAt
  • buildtest run ground truth: ssh -p 23 root@198.199.106.165 'stat -c "%y %s" /root/buildtest-data/runs/<runId>/build.log; tail -3 /root/buildtest-data/runs/<runId>/build.log'
  • prod host load: ssh -p 23 root@198.199.106.165 'uptime'

A fresh query always wins over this document.

Mission summary

User request, verbatim: "Fix the infra challenges." This referred to the two defects documented as challenges during the preceding PerformanceTest-graduation session while shepherding UrlResolver #718 through the merge queue (both filed 2026-07-11):

  1. challenge 2026-07-11-1539 — kompile testrunner parent hangs a finished run when a child RPC frame CNFEs. Also tracked as community.kotlin.kompile.testrunner.jvm issue #34.
  2. challenge 2026-07-11-1659 — flaky wall-clock perf assertion in UrlResolver's synchronous-peer-exchange stress test.

Both were fixed at the owning layer via TDD, with codex gpt-5.6-sol doing implementation + test-comprehensiveness + adversarial reviews, and a final review by the driving agent. Workstream 1's two PRs are green and merge-ready. Workstream 2's PR is code-complete and review-passed but cannot get a clean CI green because both of its checks keep dying on unrelated production infrastructure — that blocker is the entire reason this handoff exists.

What was found and done (the chain)

Workstream 1 — testrunner post-completion hang (VERIFIED root cause, fix GREEN + merge-ready)

Root cause (verified from the host build.log of buildtest run a7ce4d7c, 2026-07-11 15:23 UTC): the child test JVM Java-serialized raw Throwables over the parent↔child TCP RPC. BootstrapRunner.reportUncaughtException sent new TcpMessage("reportUncaughtException", new Object[]{t}). When t's class exists only on the test classpath (production case: foundation.url.protocol.RelayException; issue #34's case: org.junit.ComparisonFailure), the parent's input.readObject() in TcpCommunicationHandler.handleCommunication (JvmUnitTestLauncher.kt:1239) threw ClassNotFoundException, the Throwable catch logged "Unexpected error in TCP communication" and ended the communication thread. Consequences observed: (a) the real test-failure identity was lost (masked as a generic timeout); (b) the child stayed alive, so the parent idle-looped Control socket idle for 60000ms ... child process is still alive every 60 s for ~40 min until an external force-FAIL. This cost the #718 merge-queue effort two otherwise-green group builds.

Fix, at both owning layers (TDD; codex-implemented, dual-reviewed, final-review passed):

  • Child sidebootstrap PR #30, bumps community.kotlin.kompile.testrunner.bootstrap to 0.0.12. New ThrowableSanitizer converts every Throwable in any RPC argument position into a bootstrap-owned plain-JDK RuntimeException graph (never a test-classpath class on the wire), preserving the original FQN + message + stack + cause chain + suppressed. It is identity-cycle-safe and budget-bounded: depth 64, 256 nodes, 16 KiB message, 256 stack frames, 32 suppressed-per-throwable, each bound emitting an explicit truncation-marker node (a depth-truncation marker cause was added in review). Hostile getMessage()/getCause()/getStackTrace() accessors are guarded; toString() is never invoked. BootstrapRunner.main's independent root-cause traversal was also made identity-aware and bounded. Local suite: 91/91.
  • Parent sidejvm PR #36, bumps community.kotlin.kompile.testrunner.jvm to 0.0.20. A ClassNotFoundException or InvalidClassException while reading a frame now writes exactly one parent-only error response (built from parent-side classes only, so an old synchronous child unblocks) and then performs orderly relay-fatal cleanup. This was the key review correction: the initial implementation tried to continue the loop after CNFE, but Java's ObjectInputStream contract specifies stream state as indeterminate after any deserialization exception, so continuing to parse is unsound even though today's OpenJDK happens to consume a full default-serialized graph. Multiple poisoned frames and a following CLOSE are deliberately not parsed. No timeout constant changed — the hang disappears because the handler thread no longer dies mid-run. Local suite: 89/89.

Wire compatibility (verified by review with real-socket tests): new-parent + old-child → an unknown-class frame gets one parent-only error and the relay closes (child unblocks, exits promptly). Old-parent + new-child → the child sends only JDK RuntimeException graphs, so nothing test-only reaches the wire.

Both PRs went through: codex implementation → codex test-comprehensiveness + adversarial review (10 real findings fixed, incl. the resync-soundness correction and the false old-parent/new-child compat claim) → driving-agent final review (no findings) → CI green.

Workstream 2 — UrlResolver perf flake (VERIFIED; fix code-complete + review-passed; BLOCKED on CI infra)

Root cause (verified from buildtest run 8f520e61): tests/stressTestSynchronousPeerExchangeServiceDiscovery.kts asserted discoveryTimes.average() < 500 ms. On a shared 2-vCPU CI droplet it came in at 521 ms — a 4 % overshoot of a hard wall-clock mean — and failed, evicting a merge-queue entry, even though all 20 client iterations did discover the service immediately (failures == 0 held).

FixUrlResolver PR #759, branch fix/synchronous-peer-exchange-stress-flake, head 1d817750a97b9a8b0b7d24c995fec7290dc724e7. Single-file test diff (tests/stressTestSynchronousPeerExchangeServiceDiscovery.kts, ~44 lines):

  • Keeps assertEquals(0, failures, ...) as the primary regression guard. Independently re-proven in review: reintroducing the production-bootstrap-ID filter (the original bug) makes it fail 20/20.
  • Adds eagerlyJoinNetwork = false to the measured clients so the timed window honestly covers a fresh client's full synchronous addBootstrapPeerjoinNetwork → immediate discovery (with eager join on, the constructor could perform its own exchange and mask a joinNetworkInternal peer-selection regression).
  • Replaces mean < 500 ms with an upper-median < 1000 ms defense-in-depth bound (11th of 20 sorted samples): rejects the multi-second async-gossip-fallback regime, tolerates a few isolated scheduler-stalled handshakes.
  • Adds a hard fixture-readiness assertion (assertTrue(providerVerified, ...)) — the setup previously polled for provider verification but never asserted the result, a latent flake source.
  • No iteration-count reduction (still 20), no @Timeout raised.

Verification (from the codex session + review): baseline (old assertion, artificial CPU contention) 0/12 passes; fixed 12/12; simulated regression 20/20 fail; full remote suite 1084/1084. Code is complete, codex-reviewed, and the driving-agent final review passed with no findings.

THE BLOCKER — #759 cannot reach a clean simultaneous green because both checks keep failing on unrelated production infrastructure, not the code:

  • kotlin.build (remote) — three separate attempts died in DigitalOcean droplet provisioning, before/around test execution, self-classified INFRASTRUCTURE/PROVISIONING:
    • 0f9ae3cc, 158cbae7createBuildDroplet failed outright (rate-limit / capacity).
    • af5281d2 — got 66/100 tests PASSED with 0 failures, including the reworked stress test, then a createBuildDroplet NullPointerException on shards 8/9 killed the remaining shards and failed the whole run. This is the closest to proving the fix green on the heavy check.
  • bld-build (GitHub Actions) — failed on netlab environment tests (testNetlabResolutionUnderLatencyAndPacketLoss, testNetlabDynamicFailureRecovery) — the documented netlab infra-flake class — while prod host 198.199.106.165 was at load average 79–95 (later recovered to ~15–30). One bld-build re-run additionally hit a transient Failed to resolve external Maven coordinates kompile:build-kotlin-jvm:0.0.23 (kotlin.directory 401/flap; the artifact IS present at the correct path). Notably the netlab failure log showed Failed to report exception to parent: TCP communication error in reportUncaughtException — the exact launcher defect Workstream 1 fixes.

Neither failure touches #759's one-file test diff.

Challenges filed this session: the two fixed defects (1539, 1659) plus 2026-07-11-2144 (lost kotlin.build check-suite dispatches) and 2026-07-11-2133 (gh pr edit fails on deprecated Projects Classic — use REST gh api -X PATCH repos/.../pulls/<n> -f body=... instead).

Relevant PRs / refs (write-time state — 2026-07-12 ~17:00 UTC)

| PR / ref | State @ write | Notes | |---|---|---| | bootstrap #30 | OPEN · CLEAN · all checks green | head 41a8c0c4; bumps bootstrap → 0.0.12; MERGE-READY, merge FIRST | | jvm #36 | OPEN · CLEAN · all checks green | head 201c73e8; bumps jvm → 0.0.20; MERGE-READY, merge SECOND (depends on published bootstrap 0.0.12) | | UrlResolver #759 | OPEN · BLOCKED · both checks FAIL (infra) | branch fix/synchronous-peer-exchange-stress-flake, head 1d817750; code done + review-passed; blocked on CI infra only | | jvm issue #34 | OPEN | Resolved by #30 + #36 together (PR bodies link it; Fixes #34 deliberately NOT used until both land) |

Nothing is uncommitted — all work is pushed to the PR branches. Session checkouts still in /code/workspace: community.kotlin.kompile.testrunner.bootstrap, community.kotlin.kompile.testrunner.jvm (each on its PR branch), UrlResolver (on fix/synchronous-peer-exchange-stress-flake). They can be re-cloned fresh if gone.

Next steps

  1. Re-verify current state first (banner commands above) — especially whether #759's checks have been re-run to green, and whether any of the three PRs were merged.

  2. Launcher pair #30 + #36 are independently merge-ready right now and do NOT depend on #759. When the user approves merging (do not merge code PRs without explicit user approval):

    • Merge bootstrap #30 first, then jvm #36 (jvm's build depends on the published bootstrap 0.0.12).
    • Post-merge deploy chain to actually retire the production hang (the fix isn't live until buildtest runs the new testrunner): publish bootstrap 0.0.12 to kotlin.directory (publish-maven-artifact skill) → publish jvm 0.0.20 → bump kompile-cli's testrunner dependency to those versions → buildtest picks it up on its next build.
  3. Drive #759 to a simultaneous green. The code is done; this is pure CI-infra shepherding:

    • Confirm prod host load is low (uptime, want load avg < ~20) AND DigitalOcean provisioning is healthy before re-running — re-running into a load-90 host or a DO rate-limit window just burns another ~90 min.
    • Re-run both failed checks: for bld-build, gh run rerun 29176363521 --repo CodexCoder21Organization/UrlResolver --failed; for kotlin.build (remote), warm the webhook receiver (for i in 1 2 3; do curl -s -o /dev/null --max-time 60 https://github-webhooks.wasmserver.com/; done) then rerequest the kotlin-build-ci-test check-suite (gh api -X POST repos/CodexCoder21Organization/UrlResolver/check-suites/<id>/rerequest, id from gh api repos/.../commits/<headSha>/check-suites). Verify the fresh run materializes on the host (ssh ... ls /root/buildtest-data/runs/<newId>) — per the watch-build skill, don't trust suite counters after a rerequest.
    • Watch each with the watch-build skill (build-watchman --repo <o>/<r> --pr 759, or host ground-truth per the banner). Distinguish the benign Control socket idle idle-poll (test-events.jsonl still advancing, run healthy) from a genuine wedge (both build.log and test-events frozen > 20 min).
    • Once both checks are simultaneously green, #759 is ready for user merge (again: do not merge without explicit approval).
  4. When all three are merged and the launcher deploy chain is done, close out this handoff (delete the file + merge the deletion, per handoffs/README.md).

Reusable / operational knowledge

  • codex gpt-5.6-sol invocation: codex exec --model gpt-5.6-sol -c model_reasoning_effort="high" --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check -C <dir> "<prompt>" < /dev/null. It runs long (implementation was ~670k tokens, perf-flake ~1.5M); run it as a tracked background job and read its final summary.
  • coursier on the /code box: at ~/bin/coursier, NOT reliably on PATH — export PATH=$HOME/bin:$PATH first. /usr/local/bin/coursier is the wrong architecture (ARM64 on x86_64).
  • kompile build/test: ./scripts/build.bash <pkg>.buildMaven and ./scripts/test.bash --test tests (needs the --test tests arg or it prints usage). After editing source, a stale build cache can run pre-change bytecode — clear ~/.aibuildcaches/<slugged-repo-path> (the codex launcher session hit and diagnosed exactly this).
  • gh pr edit is broken org-wide (deprecated Projects Classic GraphQL, 401/error). Update PR bodies via REST: gh api -X PATCH repos/<o>/<r>/pulls/<n> -f body="$(cat file)". See challenge 2026-07-11-2133.
  • build-watchman 0.0.11 refuses to attach to a PR whose head commit carries stale failed queue-run check-suites (exits 1 "checks completed with failures" even when required branch checks are green). Workaround used for #718: a hand-rolled queue-entry poll (MERGED / entry-GONE / timeout, 4-min cadence). For plain branch-CI watching (#30/#36/#759) it works fine.
  • buildtest run ground truth beats any HTTP symptom. https://buildtest.kotlin.build/api/test-results?id=<runId> flaps HTTP 503 under load — that is degraded observability, not run death. ssh -p 23 root@198.199.106.165 then stat/tail on /root/buildtest-data/runs/<runId>/build.log and test-events.jsonl is authoritative. Count test events by .status (SUCCESS/FAILED), not by .passed.
  • HardwareControlFabric CLI is dead on the /code box (no mTLS certs at ~/.config/hardware-control-fabric/); SSH (ssh -p 23 root@198.199.106.165, key ~/.ssh/id_ed25519) is the working fallback for all host operations despite being the documented last resort.
  • The prod host 198.199.106.165 is chronically oversubscribed — load spiked to 79–95 this session (recovered to 15–30). DO droplet provisioning rate-limits/NPEs and netlab-acquisition failures both track host health; gate CI re-runs on a low uptime load average.