Repository navigation
fix(op): mint each runtime environment's Root from the spec's anchor - #402
Merged
Merged
Conversation
added 2 commits
August 13, 2026 14:03
A RuntimeEnvironmentSpec carried a live fsroot.Root, so every path that minted one without reaching a closing Run leaked the confined Root's directory handle. Windows refuses RemoveAll on an open directory, which is why 18 of the standing 48 windows-leg failures are t.TempDir() cleanup failures rather than assertion failures; POSIX unlink hides the identical leak. Per the amended #393 ruling (option 4), the spec now carries only the anchor path and access mode -- serializable values, never a handle. NewRuntimeEnvironment mints the Root through the new fsroot.Open(dir, mode) and RuntimeEnvironment.Close releases it, the single-owner contract it already held. Nothing mints before environment build, so the pre-Run error paths cannot leak. Resume is no special case: every Run mints fresh from the anchor, which is all that survives a cross-process resume anyway. The deciding fact was verified across the tree before the ruling was amended: nothing between spec construction and Run dereferences the spec's Root. The field had exactly two production readers, all nine host mint-to-Run gaps were clean, and no .star script or doc touches spec.root. NewRuntimeEnvironment now returns an error, and a mint failure at Run lands the preflight-failed terminal before any dispatch. Provider.Spec keeps its fail-fast contract by probing the anchor open-and-release, so the mint itself stays the only definition of a valid root. Six writ spec factories lost their only error source and are now infallible; adopt and migrate-register collapse their planning and execute spec pairs into one shared spec. Three defects heal with it. cmd/lore's deploy loop handed one spec to N executors, so every iteration after the first dispatched against a Root the previous run had closed. devlore-test aliased the spec's root into TestContext for post-close reads plus a double close, benign only because unconfined Close is a no-op. verify and readback never closed their loading environments at all; both now do, through iox.Close. Riders: cmd/lore/lore/integration_test.go removed (//go:build ignore, referencing op.contextBase and other long-deleted APIs); and a go-style sweep over the touched set -- 77 multi-line doc summaries split, 37 over-120-column lines rewrapped, one blank-line-after-signature added. Verified: make test reports zero failures repository-wide; make vet-all, lint-all, and build-all are green on linux, darwin, and windows (lint reports 0 issues per GOOS); gofmt clean over all 41 files. The -18 on the windows leg is CI-side proof -- POSIX cannot observe the leak. Assisted-by: Claude Signed-off-by: David Noble <[email protected]>
loadRecipientsFile discarded its close error in a defer lambda while loadAgeIdentities, ten lines up in the same file, already used the iox.Close idiom. Named returns plus defer iox.Close(&err, file) joins a close failure onto the result instead of dropping it. Assisted-by: Claude Signed-off-by: David Noble <[email protected]>
The first windows measurement on this PR came back 48 -> 45, not the predicted ~30. Fifteen failures still carried the leak signature, and none of them were the production paths #393 diagnosed: they are test environments that are never closed. Whoever constructs a runtime environment closes it; these tests never did -- before this branch or after -- so relocating the mint could not fix them. A natural experiment inside the plan package proves it. lifecycle_e2e, lifecycle_starlark, and result_flow_starlark each close their environment and none of their tests appear in the failure list; provider_test (2 mints), lifecycle_api (10), gather_api (1), git_resume (1), and receipt_integration (1) close nothing and all of their tests do. Fifteen unclosed mint sites, fifteen leak failures. Add t.Cleanup(func() { _ = environment.Close() }) at all fifteen, matching the idiom the three passing helpers already use. Verified by enumeration rather than assumption: repo-wide, every test file that sets a root now closes it. The files that mint without a root hold no handle, and TestNewRuntimeEnvironment_BadAnchorFails mints nothing because the mint is what fails. The plan document records the miss rather than quietly restating the target: the option-4 verification asked whether anything dereferences spec.Root between spec construction and Run (it does not, and that holds), but never asked who closes the environment in the failing tests. The -18 estimate was inherited from the #393 diagnosis and repeated without its own enumeration. Expected windows effect: 45 -> ~30. Unlike the first estimate, this one names its mechanism -- fifteen unclosed handles, fifteen cleanups. Assisted-by: Claude Signed-off-by: David Noble <[email protected]>
This was referenced Aug 13, 2026
David-Noble-at-work
added a commit
that referenced
this pull request
Aug 13, 2026
…403) PR #402 closed the handle-leak cluster: the leak signature on the windows leg went 18 -> 0, with zero panics and zero [build failed] packages, measured uncapped against head 27d23e8's check-runs. The campaign burn-down is now 85 -> 57 -> 48 -> 50 -> 48 -> 28. The correction matters more than the number. This campaign predicted -18 from the #393 ruling; the framework change alone delivered -3, and the remaining -17 came from closing fifteen test runtime environments that were never closed. The production leaks #393 diagnosed were real and are fixed -- lore's deploy loop ran N executors off one spec against a Root the first run had closed, writ verify and readback never closed their loading environments, devlore-test aliased the spec's root into TestContext -- but they were not what those eighteen tests were failing on. A natural experiment inside the plan package proved it: the three helpers that close their environment had no failing tests; the five that did not accounted for exactly fifteen. A right number reached by wrong reasoning is still a miss, and the campaign's counts are only as trustworthy as their attributions, so both plans record it rather than restating the target. env-minted-root.md is marked complete with the three-point measurement table; the 48-row in platform-test-matrix.md now points at the correction instead of the superseded diagnosis. Remaining 28: file-provider write/permission semantics, path semantics, chown, git argv, 2 CLI error-text expectations, and singles including #376's Starlark path escape. Assisted-by: Claude Signed-off-by: David Noble <[email protected]> Co-authored-by: David Noble <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the amended #393 ruling
(option 4):
RuntimeEnvironmentSpeccarries no livefsroot.Root— only the anchor path andaccess mode — and every
RuntimeEnvironmentmints its own Root and closes it.The confined Root's directory handle leaked on every path that minted one without a matching
close. Windows refuses
RemoveAllon an open directory, so those leaks surface ast.TempDir()cleanup failures on the windows leg; POSIX
unlinkhides the identical leak, which is why noneof this was visible on the ubuntu-only gate.
What changed
pkg/fsroot: newMode(zero valueModeConfined) andOpen(dir, mode)— onemode-dispatched constructor over the three existing implementations.
pkg/op:Spec.Root→RootPath+RootMode;WithRoot(path, mode);NewRuntimeEnvironmentmints and returns an error;Closereleases (the single-owner contractit already held). A mint failure at
Runlandspreflight_failedbefore any dispatch. Resumere-mints from the anchor — all that survives a cross-process resume anyway.
plan.Provider.Spec: keeps its fail-fast contract via an open-and-release probe, so themint itself remains the only definition of a valid root.
adopt and migrate-register collapse their planning/execute spec pairs into one shared spec.
Defects fixed en passant
cmd/lore/lore/commands.gohanded one spec to N executors in a loop, so every iterationafter the first dispatched against a Root the previous run had closed.
devlore-testaliased the spec's root intoTestContextfor post-close reads plus a doubleclose — benign only because unconfined
Closeis a no-op.writ verifyandwrit readbacknever closed their loading environments.Measurement, corrected
The first windows run on this branch returned 48 → 45, not the ~30 this PR originally
predicted. The framework change alone delivered −3. Fifteen failures still carried the leak
signature, and they were not the production paths #393 diagnosed — they were test
environments that are never closed, which relocating the mint could not fix. The natural
experiment: the three plan-package helpers that close their environment have no failing tests;
the five that do not, account for exactly fifteen.
The follow-up commit closes those fifteen. Expected: 45 → ~30, and this estimate names its
mechanism rather than inheriting one.
Verification
make test— zero failures repository-widemake vet-all,make lint-all,make build-all— green on linux, darwin, and windows(lint reports
0 issuesper GOOS)gofmtclean; both mechanical style detectors report zero findingsTestRun_SequentialExecutorsShareOneSpec— two executors, one spec, both runs green; thecmd/lore bug shape, observable on every platform
Refs #393. Campaign: #373 /
docs/plans/platform-test-matrix.md.Plan:
docs/plans/env-minted-root.md.Assisted-by: Claude