Handoff: Remove temporary storage after a test JVM is forcibly terminated
Written 2026-10-21, about 16:43 UTC.
RE-VERIFY: This is a write-time snapshot. Recheck https://github.com/CodexCoder21Organization/TinySiteWui/pull/25 with gh pr view 25 --repo CodexCoder21Organization/TinySiteWui --json state,mergeStateStatus,statusCheckRollup,mergedAt and query its mergeQueueEntry before acting. Recheck branch heads with git ls-remote. No merge is authorized.
Design decisions in force
From the original request, 2026-10-21:
Tests must close/delete what they open even on failure.
Fix at the source (production code if it leaks; otherwise the test fixture), never by a periodic sweeper.
Run targeted tests locally one JVM at a time (scripts/test.bash --local --test <file>; check df -h / > 5 GB and ~3 GB memory first).
Do NOT enqueue or merge; the supervisor decides.
Rules: never raise timeouts, never lower iteration counts, never weaken/remove assertions in existing tests, no mocks, no reflection, no Thread.sleep. Do not delete /tmp directories you did not create in this task.
Branch fix/tinysite-storage-temp-cleanup from origin/main, fetch+rebase right before pushing, open a PR whose description opens with the why above, ends with ๐ค Generated with Claude Code; commits end with Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com.
The original request specified build-watchman 0.0.21 plain --pr for the required bld-build check; that watch has completed successfully. No deployment or artifact publication was requested or performed.
Mission summary
The user asked: "Find and fix a test temp-directory leak. Why: during the TinySite deploy-history work on 2026-10-21 the shared /code machine's disk hit 100% and crashed unrelated test runs with 'No space left on device'; cleanup found 74 leftover directories /tmp/tinysite-storage<random digits> of ~160 MB each, created by test runs and never deleted."
Four genuine ordinary fixture leaks have been reproduced, fixed, and verified in the open PR. The historical 160 MiB case is different: preserved run records show the large-file test being forcibly terminated at its timeout, so its existing finally cannot run. The current PR does not fix that parent-process ownership gap. A scope question was sent to the user: include a separate parent-owned runner cleanup change (recommended), or keep the reviewed PR fixture-only? No reply had arrived at write time. This document preserves that remaining work; it does not claim the original killed-JVM case is fixed.
What was found and done
rg found 41 exact "tinysite-storage" creators, all in TinySiteWui tests. Production source creates none. Four fixtures omitted both manager.close and directory deletion: testBrowserShowsUsageTimesInTheVisitorsTimezone, testUsageChecksNeverHoldUpWritingOrStopping, testUsageRecordingCountsEveryConcurrentRequest, and testSiteUsageSurvivesARestartAndIsWrittenOffTheRequestPath.
- Each was extended locally to run its original real HTTP/browser scenario, then create a second real Filedrop fixture, upload a non-empty file, and throw an exact sentinel exception. Each requires both owned roots to be absent before emergency cleanup. All four failed the directory-absence assertion before their fix. The usage-recording baseline measured about 1.4 MiB, so these ordinary leaks must not be presented as proof of every historical 160 MiB directory.
- The fix closes OkioSimpleFileSystemManager with use and removes the owned root in an outer finally. Usage records live under that root across the scenario's intentional server restarts. The existing server cleanup runs before manager close. Real disk is retained because these scenarios use concurrent requests and their FakeFileSystem supports one thread. No existing assertion, timeout, iteration count, or sleep call was changed; no new sleeps, mocks, reflection, production changes, or dependencies were added.
- The heap-sized streaming test already had stop/close/delete cleanup since its introduction. Added only an owned-path diagnostic and post-teardown directory-absence assertion. It passed locally in 19.002 seconds. All five targeted local invocations passed, one at a time, after checking disk and memory. The code and test review passes preserved all original assertion lines and reviewed resource/error ordering.
- Later inspection of existing shared records found direct evidence of the historical mechanism. M19's preserved failure says
Test JVM max heap: 134217728 bytes; serving 167772160 bytes, followed by TIMEOUT: Test ... exceeded per-test timeout of 30000ms; child process killed. Its stack shows the large-file HTTP read still running before termination. Multiple C12/M19/T2 records show similar timeouts. This is strong evidence for how that payload size remains after a killed run, although the 74 removed paths cannot individually be mapped to records. The complete M19 trace is preserved remotely below.
- Pinned toolchain inspection: kompile-cli 0.0.124 selects community.kotlin.kompile.testrunner.jvm:community-kotlin-kompile-testrunner-jvm:0.0.29. Static javap inspection of the exact cached 0.0.29 artifact confirms buildInnerCommand sets java.io.tmpdir to an owned scratch root only for DockerSandboxConfig; native children get no corresponding parent-owned root. Current upstream source has the same boundary: https://github.com/CodexCoder21Organization/community.kotlin.kompile.testrunner.jvm/blob/main/src/community/kotlin/kompile/testrunner/jvm/JvmUnitTestLauncher.kt . No upstream checkout, code change, or release was made.
- Opened the fixture PR, with the requested motivation and attribution. Required bld-build passed: the Actions artifact contains 386 test records, all successful, with only tests/buildrule/test elements and no retry-history elements. Optional kotlin.build(remote) failed before tests: BuildTest reported RECOVERING_LOCAL_STATE while creating its upload session. The other optional remote-build check was neutral because its service had not established a BuildTest connection. Exact summaries/stack traces are preserved; no CI rerun, enqueue, merge, or service mutation occurred.
- build-watchman 0.0.21 plain --pr exited GREEN/0. Its recorded PID no longer exists. Every earlier test/controller process also reached a terminal result. The product checkout is clean, without stashes or local-only commits. The task-created jars/ launcher directory was removed after tests. No pre-existing /tmp directories were deleted.
Relevant PRs and refs
| Repo |
Branch |
Remote head SHA |
PR |
What is on it |
State |
| TinySiteWui |
fix/tinysite-storage-temp-cleanup |
3a5e0f5ade966b52c11745c137d4c1347b783fd7 |
https://github.com/CodexCoder21Organization/TinySiteWui/pull/25 |
Four fixture cleanup fixes and the large-file normal-cleanup guard |
OPEN, unmerged, no auto-merge or merge-queue entry; five local checks pass; required CI 386/386 pass |
| TinySiteWui |
evidence/tinysite-storage-cleanup-20261021 |
6659a0c608735a212552a4faed5f86ad057d270c |
No PR; evidence only |
Product head plus investigation logs, pinned launcher bytecode inspection, original timeout record, CI artifact XML, and findings |
Pushed and remote SHA verified; do not merge this evidence branch |
Useful evidence under handoff-evidence/TMP1:
Deployed/published but not merged: none. No production route, server process, service binary, or Maven release was changed; this session used source commits, PR metadata, read-only CI/service queries, and handoff operations only.
Next steps
- Re-verify current state first, including whether the supervisor has since merged the fixture PR. Do not push to a merged PR branch. Do not enqueue or merge without the supervisor's instruction.
- Resolve the scope question: include parent-owned cleanup of native test scratch directories, or accept only the already-reviewed ordinary fixture cleanup. The forced-termination path is not fixed by another finally or deleteOnExit in the child JVM.
- If extending scope, first write an invariant table for the parent-owned scratch root, launched process and descendants, and launch failures. Determine the correct owning layer: native JVM launcher per-test roots are one candidate; a test-invocation wrapper is another, with a different lifetime. Preserve any explicit caller-owned tmpdir contract. Do not select a solution without checking the existing API/ownership rules.
- Build a deterministic fail-first public-API reproducer in the consuming path and owning upstream module where relevant. A child should create a small non-empty temp directory, report its exact path through the real launcher, and terminate abruptly; after the real launcher returns, assert that path is gone. Also cover ordinary completion, body exceptions, timeout termination, and start failure. Keep an emergency guard that deletes only paths the test itself created. There is no need to manufacture hundreds of megabytes or calibrate concurrency to this host: forced termination is the condition to discriminate on.
- Implement at the owner, verify against the same reproducer, review tests and implementation independently, and follow the upstream publication/dependency workflow if a library change is needed. Do not claim the actual TinySite runner has changed merely because a new launcher artifact was tested in isolation: the pinned CLI dependency path must consume the corrected owner. Keep unrelated fixture work in its existing reviewed PR unless the user directs otherwise.
- Follow the original local-resource, attribution, and CI-watching constraints for any new PR. Never use CI to diagnose the fix or requeue unchanged failures. Stop every owned wait before finishing.
Operational knowledge and omissions
- Testing guidance read: https://github.com/CodexCoder21Organization/DocumentationRepository/blob/main/architecture/TESTING.md . Engineering philosophy and the relevant READMEs were read as well. The full historical trace and exact pinned bytecode are the primary evidence for the killed-JVM boundary; do not substitute a passing ordinary run for a forced-termination proof.
- This installed gh has no
search code command. Use gh api 'search/code?q=...+org:CodexCoder21Organization'.
gh pr edit fails while querying retired Projects classic fields. Write the exact body to a JSON file and use gh api --method PATCH repos/CodexCoder21Organization/TinySiteWui/pulls/25 --input <file>.
- The earlier dependency-preparation failure was Connection refused from kotlin.directory. It was not counted as a test-body reproduction; after the exact POM returned HTTP 200, a local invocation reached the intended cleanup assertion. No CI rerun was used.
- report-challenge's CLI automatically merges a documentation PR. Because this brief prohibits enqueue/merge, these incidents were recorded in the findings instead of running that automatic mutation path.
- The handoff skill's current service workflow overrides the legacy README paragraph about merging handoff PRs. No handoff PR or queue entry was created.
- Build outputs, dependency caches, downloaded CI ZIPs, and redundant compile-thread snapshots are intentionally not committed. The useful logs, full relevant traces, result XML, and source work are remote. No ongoing upstream patch exists only on local disk.