Handoff: resolve the write-progress deadline conflict and finish the UrlProtocol near-cutoff fix
Written 2026-10-19, UTC.
RE-VERIFY: This is a write-time snapshot. Recheck main with git fetch origin, and related pull requests with gh pr view <full URL> --json state,mergedAt,statusCheckRollup. Recheck https://buildtest.kotlin.build/run?id=d426a92b for the infrastructure result. Branch evidence is permanent; local caches and absolute paths are disposable.
Design decisions in force
The user requested, verbatim, "Fix the stall-close logic so a connection that made write progress before the cutoff is not closed, while a genuinely stuck connection still is (keep/extend a test proving the stuck case still closes on time). Never raise timeouts, cut iterations, weaken assertions or skip tests."
Other explicit requirements: "Force that exact ordering deterministically in a fail-first test through the public API (use a controllable Clock if the code takes one; no wall-clock tuning)." "Do NOT merge, enqueue, publish or deploy." Check disk before clone/build; stop under 10 GB. Cap every JVM, one heavy local job, prefer remote. Kill only recorded own PIDs; only remove cache entries containing this checkout's path. Commits end with Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>; the eventual PR body ends with ๐ค Generated with [Claude Code](https://claude.com/claude-code).
Mission and why
The required remote check on https://github.com/CodexCoder21Organization/UrlProtocol/pull/724 failed in testWriteStuckConnectionRecoveredJustBeforeGraceIsNotRetired (https://buildtest.kotlin.build/run?id=9658b094). Earlier main failures were reported on 2026-10-17. It blocks UrlProtocol changes involved in fixing a TinySite production outage. The brief asks for reproduction above 50%, mechanism identification, a deterministic public regression, an owning-code fix preserving stuck retirement, full exact-head remote tests with native XML first-attempt counts, and a PR from main; no merge/publication/deploy.
Findings and verified state
- Original baseline was main https://github.com/CodexCoder21Organization/UrlProtocol/commit/c7d9a191b78cda2ba2108757e7cd67c61b137cf9. Local isolated original test passed 1/1 in native XML (14.23 seconds). A four-concurrent-caller prototype also passed 1/1 (14.554 seconds); it is retained as an insufficient amplifier, not as a regression guard.
- A standalone Java caller invokes the original compiled public test function directly (no reflection), with 16 genuine concurrent host-pair/RPC scenarios per batch, ten batches. Actual CPU affinity 0,1; SerialGC; heap 256 MB; direct memory 128 MB; metaspace 192 MB; ActiveProcessorCount=2; Netty eventLoopThreads=2. Result: 7/10 batches failed, 91/160 calls failed. All 91 were the exact assertion at original test line 178, and all have a corresponding debug line saying AwaitedResponseOutbox retired the root after 10000 ms without a completed response. There were no memory/setup/timeout errors in the valid amplifier run. Each call ran once, not retried to green. The Java main exits zero after recording all outcomes, so its exit code is not the verdict; BATCH/TOTAL and stack traces are.
- Verified mechanism in Libp2pHostFactory.kt:
updateStalledRetirementLocked creates one child deadline on first queued/in-flight pause. Successful whole-response completion renews it; a queued response inherits it on handoff. retireIfStillStalled checks identity/close/live ownership and unconditionally claims root retirement while any attached answer remains pending. It deliberately checks neither current writability nor partial progress and has no progress timestamp. Recovery inside the final 250 ms is insufficient unless the entire response completes before the old deadline. This is explicit policy, not a stale timestamp or check-then-act race.
- The brief's fixed-offset claim is not universal. Native original run attempts all failed, but attempt 2 reached 786390 of 920073 bytes; attempts 1 and 3 stopped at 524246. The difference is a 262144-byte Yamux window. Full native record is saved.
- A policy decision is pending. README lines 604-608 explicitly says only completed response writes renew, and partial prefixes never renew/remove the obligation.
tests/testReview718PartialWritesCannotKeepPausedBudget.kts sends real 16-byte prefixes every four seconds from two incomplete 3.5 MB responses and requires a third healthy connection to get shared capacity within its existing bound. tests/testReview718QueuedGraceSurvivesPendingWritableClose.kts requires a queued deadline to survive a writable handoff with a held write promise. Unbounded byte-progress renewal would contradict those tests and the requested "never weaken assertions" constraint. The user was asked whether real byte progress should renew (reconciling the budget test) or whether the whole-response policy should remain and the near-cutoff test should test it. No answer or permission was inferred from silence. No implementation fix, deterministic policy-changing regression, full fixed-head suite or PR has been attempted.
- The only remote run this session, https://buildtest.kotlin.build/run?id=d426a92b, ended FAILED before tests started: provisioning exceeded 600000 ms, zero created droplets; a DropletService connection had been invalidated after renewDroplet timed out. Notes report 501 provisional-creation records against a 500 account limit. This is infrastructure evidence, not a test verdict. Native remote record is saved. No remote rerun/deletion/rerequest was performed. Watchman exited with RED; the CLI exited 1.
- https://github.com/CodexCoder21Organization/UrlProtocol/pull/723 merged at 2026-10-19T09:44:55Z after the baseline was built. It claimed version 0.0.1425. The next implementation must start from fresh main and preserve that change; do not reuse 0.0.1425. https://github.com/CodexCoder21Organization/UrlProtocol/pull/724 remains open at this snapshot.
Relevant PRs / refs
| Repo |
Branch |
Remote head |
PR |
Contents |
State |
| CodexCoder21Organization/UrlProtocol |
https://github.com/CodexCoder21Organization/UrlProtocol/tree/wip/r79-stall-recovery-evidence |
76a5f3e0b8aaf69e8d0e5f301c2c7f0404976cce |
no PR |
investigation/r79: findings, exact failure record, amplifier source/setup, valid amplifier log, native baseline XML and remote provisioning record |
reproduction only; no production code change |
Nothing was deployed, published, enqueued or merged by this session. The only writes were local investigation files and pushes to the evidence branch. Source jars/classes/caches were deliberately not pushed; the portable recipe reconstructs them from a fresh native baseline run.
Next steps
- Reverify main, the evidence branch and related PR states first. Read investigation/r79/findings.md for the full invariant table and exact commands. Required documents were ~/.claude/CLAUDE.md, ~/.claude/skills/fix-flakey-test/SKILL.md, live https://github.com/CodexCoder21Organization/DocumentationRepository/blob/main/architecture/TESTING.md, PHILOSOPHY.md, and the response-retirement parts of the repository README. No AGENTS.md existed in the repo root.
- Resolve the pending policy conflict with the owner before changing either production semantics or an opposite assertion. A refuted premise is valid evidence: the source implements the documented whole-response deadline deliberately; no hidden stale-progress read was found.
- Following that decision, add deterministic public-API tests. Existing
createServerHost(reconciliationClock=...) supplies a Clock for response reconciliation, but stall retirement itself still schedules on the Netty event loop. Introducing a clock seam is distinct from fixing expiry: verify the deterministic regression fails against clock-only old policy, then apply the policy-approved fix. Keep stuck retirement on the unchanged grace and test shared-capacity/close/re-entry/late-write ownership.
- Run the original amplifier and deterministic mutation controls after the fix; independently review tests and implementation. Rebase on current main before builds, run the exact-head full suite remotely, and report first-attempt native XML counts. Treat any pre-test machine setup failure as infrastructure and link it.
- Open the requested PR with the failure/production motivation first and the requested footer last. No merge, queue entry, publication or deploy.
Reproduction and operational notes
The exact recipe and prepare-classpath.py are on the evidence branch. Copy investigation/r79 out before checking out the baseline revision. Build the original test once through scripts/test.bash to generate native main and compiled test artifacts; the Python helper reconstructs those 59 main artifact paths and verifies their hashes. Compile BaselineAmplifier.java against that classpath, then run the documented capped, CPU-pinned command with arguments 16 10. This amplifies scheduling for diagnosis; it is not the final deterministic guard.
Local box had about 15 GB shared RAM and 59 GB free disk at final check. Heavy compile took about three minutes with a 768 MB JVM cap; the amplifier needed only a 256 MB heap. No caches were removed. All test/remote/watcher jobs are terminal; no background wait loops or subagents remain. No scheduled status loop was installed because that capability was unavailable in this harness.