fleetd #369: make GitWorktreesTest hermetic against the machine’s real git config #372
Closed
agent
wants to merge 0 commits from
worker/fleetd-369-hermetic-git-tests-e8b19a-3 into main
pull from: worker/fleetd-369-hermetic-git-tests-e8b19a-3
merge into: fleet:main
fleet:main
fleet:worker/fleetd-612-unita-87807e-1
fleet:worker/612-b3-mcpwirings-da2b58-3
fleet:worker/612-b2-cb185-176d3a-2
fleet:worker/612-b1-completion-457459-1
fleet:worker/612-agaps-73a926-2
fleet:worker/608-sleeps-3a64ff-3
fleet:worker/621-b4520b-1
fleet:worker/618-b83894-2
fleet:worker/fleetd-615-e05481-5
fleet:worker/lead-autocompact-5f1ab2-3
fleet:worker/fleetd-613-f85deb-3
fleet:worker/fleetd-608-flaky-nudge-test-d0c2d1-3
fleet:worker/lead-context-gauge-ad404f-1
fleet:worker/gauge-wiring-9158c1-4
fleet:worker/redeploy-slowstart-ead0e5-5
fleet:worker/charter-bytes-13668c-6
fleet:worker/rollover-outcome-291483-2
fleet:worker/589-f64303-2
fleet:worker/593-1a8025-5
fleet:worker/589-fcd2aa-1
fleet:worker/568-9fdaa2-3
fleet:worker/571-attempted-outcome-5739f7-2
fleet:worker/581-completionresolver-cas-sites-0542b7-6
fleet:worker/562-loop-health-wiring-test-99611c-5
fleet:worker/562-surface-loop-health-7df5cc-4
fleet:worker/575-waiter-cleanup-sites-62ad80-1
fleet:worker/572-answer-lock-release-46a9ae-5
fleet:worker/567-probe-channel-leak-a38fc5-6
fleet:worker/551-record-before-send-7cbf56-1
fleet:worker/561-listener-fanout-survives-a-throw-61d538-2
fleet:worker/555-redeploy-main-flow-seam-65c2f5-2
fleet:worker/556-injector-owns-registration-e027a5-1
fleet:worker/552-post-restart-mktemp-abort-bc2672-4
fleet:worker/553-onstatus-completion-leak-0da881-2
fleet:worker/550-shasum-linux-196132-1
fleet:worker/538-loop-dies-on-error-4a5eeb-6
fleet:worker/426-health-coverage-ef1fd4-4
fleet:worker/504-failed-reported-clean-3cfd66-3
fleet:worker/537-capturedlog-close-e4c437-2
fleet:worker/459-broken-link-targets-cadc17-5
fleet:worker/535-appender-leak-fe74c1-1
fleet:worker/512-part2-shutdown-detection-434701-9
fleet:worker/529-logger-level-sweep-2a5533-8
fleet:worker/528-drain-gate-call-site-5de83d-7
fleet:charter/forge-mcp-vs-token
fleet:worker/521-swap-guard-unpinned-28e931-5
fleet:worker/519-probe-test-harness-d25ab8-4
fleet:worker/525-logger-level-leak-1b4eb0-6
fleet:worker/518-fleetmcp-resolver-wiring-8ef96c-1
fleet:worker/512-drain-complete-line-7edd71-3
fleet:worker/517-abort-branch-and-jar-id-41b641-2
fleet:worker/500-9e52c9-3
fleet:worker/509-4912f4-2
fleet:worker/511-9a4b23-1
fleet:worker/493-479f45-2
fleet:worker/505-03f8b2-1
fleet:worker/492-followup-detect-unclear
fleet:worker/501-a31fa0-7
fleet:worker/498-451d1c-5
fleet:worker/494-1015ce-2
fleet:worker/492-209647-1
fleet:worker/489-001902-2
fleet:worker/480-relative-handover-path-906323-1
fleet:worker/480-b-handover-skill-45bf1f-5
fleet:worker/474-followup-source-pin-f54a55-17
fleet:worker/474-charter-check-on-reload-f54a55-17
fleet:worker/466-quarantine-repeatcount-report
fleet:worker/393-opencode-skill-seeding-71854b-13
fleet:worker/469-canonical-tool-names-2a472a-16
fleet:worker/466-quarantine-escalation-5ae9c1-15
fleet:worker/446-hot-exhausted-pattern-0af580-6
fleet:worker/464-charter-tool-name-guard-a85635-12
fleet:worker/463-listfleet-default-fails-open-f1c76c-11
fleet:worker/458-invariant-5-by-purpose-862f9a-10
fleet:worker/439-coordinator-row-gate-bc032a-8
fleet:worker/449-herdr-protocol-576015-4
fleet:worker/450-abstract-spawn-599e1c-5
fleet:worker/437-ack-refuses-177d91-1
fleet:worker/444-placement-window-feb56a-2
fleet:worker/440-helddurable-derived-d462d7-13
fleet:worker/425-rework-placement-resolve-c58ba1-9
fleet:worker/421-lead-peek-held-msgs-cdbad2-10
fleet:worker/435-fixed-policy-cap-fe11de-12
fleet:worker/422-gate-state-observability-9e79d6-11
fleet:worker/431-memberregistry-live-readers-cdbad2-10
fleet:worker/424-architect-slot-hot-038b41-7
fleet:worker/422-model-gate-spawn-c29f48-6
fleet:worker/425-default-profile-live-f55534-8
fleet:worker/415-coverage-wording-2cbf9c-5
fleet:worker/416-3ad1da-1
fleet:worker/418-588283-3
fleet:worker/deterministic-stamp-race-409-3cb7b6-10
fleet:worker/armed-reads-live-config-404-ed931f-9
fleet:worker/reply-peer-refusal-391-5a34bd-7
fleet:worker/models-allowlist-aa9e9b-3
fleet:worker/ttl-stamp-race-399-f1122f-8
fleet:worker/scrub-receipt-400-316b3e-5
fleet:worker/exhaustion-detection-395-105105-6
fleet:worker/scrub-abort-394-316b3e-5
fleet:fix/scrub-uid-abort
fleet:worker/task-scrub-517574-2
fleet:worker/t386-clock-bd5b78-4
fleet:worker/t384-scrub-813790-5
fleet:worker/t381-cc-748314-2
fleet:worker/t373-336973-2
fleet:worker/t365-3920c5-3
fleet:worker/t358-6e989b-1
fleet:worker/t355-8b321c-1
fleet:worker/fleetd-368-stale-lead-binding-f5682e-2
fleet:worker/fleetd-360-deploy-units-0d3793-1
fleet:worker/359-dead-lead-tabs-f1253b-4
fleet:worker/362-worktree-skills-c03e51-3
fleet:worker/361-coord-visibility-655144-1
fleet:362-plugin-visibility-and-drift
fleet:worker/errscan-bed2ca-2
fleet:worker/amqp-log-identity-bed2ca-2
fleet:worker/withdefaults-guard-561704
fleet:worker/sleepguard-82076d-1
fleet:worker/fd334-9ee1b6-5
fleet:worker/fd348-f1ab27-4
fleet:worker/fd335-a71c35-1
fleet:worker/fd342-174a17-2
fleet:worker/fd345-490d0f-3
fleet:worker/fleetd-337-5ec7d4-21
fleet:worker/fleetd-341-af5a6b-24
fleet:worker/fleetd-339-5ca0a2-23
fleet:worker/fleetd-338-83a4a1-22
fleet:worker/fleetd-333-281f46-18
fleet:worker/fleetd-329-11bdbb-16
fleet:worker/fleetd-330-2770fb-17
fleet:worker/fix-326-50506e-15
fleet:worker/fix-324-3e9bbf-14
fleet:worker/fix-323-b8287d-13
fleet:worker/fix-316b-bd0860-11
fleet:worker/fix-318-76ca36-9
fleet:worker/fix-317-486aec-8
fleet:worker/fix-315-ce47c5-6
fleet:worker/fix-307-275890-6
fleet:worker/fix-308-b4f664-7
fleet:worker/fix-309-ec3939-8
fleet:worker/fix-310-7a3974-9
fleet:worker/fix-302-52ad0e-9
fleet:worker/fix-298-ce1acb-8
fleet:worker/fix-297-66bd11-7
fleet:worker/fix-296-104622-6
fleet:worker/fix-293-bare-closetab-eb22b5-3
fleet:worker/fix-280-gone-ask-lapse-bca98e-2
fleet:worker/fix-290-reapidle-guard-coverage-9b0dd1-1
fleet:worker/fix-285-trust-seed-8f3565-10
fleet:worker/fix-284-backend-error-seat-85912c-11
fleet:worker/fix-282-chained-ask-e6d0bb-8
fleet:worker/fix-283-teardown-leaks-f40dfa-9
fleet:worker/fix-281-pin-handler-actions-4921ac-7
fleet:worker/audit-rendezvous-lifecycle-d072ae-2
fleet:worker/audit-health-placement-1a2476-6
fleet:worker/audit-teardown-exits-e207a5-3
fleet:worker/audit-launcher-asymmetry-27e370-4
fleet:worker/audit-rest-authz-6ca53c-5
fleet:worker/investigate-275-abandon-asking-fdef52-8
fleet:worker/fix-274-worktree-leak-b0095d-7
fleet:worker/fix-273-exhausted-pattern-9665b5-6
fleet:worker/fleetd-267-model-check-bd8068-1
fleet:worker/fleetd-131-archunit-18b834-7
fleet:worker/fleetd-266-sshagent-rename-a014ff-6
fleet:worker/fleetd-184-uid-claim-8e1f31-4
fleet:worker/fleetd-184-warn-b381ee-10
fleet:worker/fleetd-184-docs-be1d12-9
fleet:worker/fleetd-257-9bf010-7
fleet:worker/fleetd-103-23a113-6
fleet:worker/fleetd-247-342356-5
fleet:worker/fleetd-116-04dea8-4
fleet:worker/fleetd-252-a830e0-3
fleet:worker/fleetd-111-7e8673-9
fleet:worker/fleetd-155c-f8ef4b-8
fleet:worker/fleetd-176-b928ca-3
fleet:worker/fleetd-249-7a7878-2
fleet:worker/cb248-composition-root-b-9acdf7-15
fleet:worker/cb148-envrc-default-fa6c82-12
fleet:worker/cb201-unit5-wiring-6c12e6-8
fleet:worker/cb241-fallback-echo-1175e9-11
fleet:worker/cb149-trust-dialog-2392a5-9
fleet:worker/cb134-148-overlay-visible-c9b986-10
fleet:worker/cb234-session-id-keyed-04e1fc-1
fleet:worker/cb201-unit3-nudge-abdf5c-6
fleet:worker/cb201-unit2-policy-c1102c-5
fleet:worker/cb201-unit4-outcome-a13bfa-7
fleet:worker/cb201-unit1-classifier-91b9b1-4
fleet:worker/cb201-227-refine-831980-3
fleet:worker/cb175-model-readback-0f085f-1
fleet:worker/cb222-charter-tmpdir-17f013-1
fleet:worker/cb226-architect-slot-race-cd3aa8-3
fleet:worker/cb224-worktree-root-group-024523-2
fleet:worker/cb-123-role-demotion-c600f7-2
fleet:worker/cb-219-opencode-roots-1f677e-1
fleet:worker/cb214-claude-session-id-b9eab4-4
fleet:worker/cb213-zdotdir-wrong-process-dd6de4-3
fleet:worker/cb211-exhaustion-classification-9546e0-2
fleet:worker/cb137-ambiguous-task-4df3d8-4
fleet:worker/cb209-agentsessionid-4dfdb6-2
fleet:worker/cb185-hostenvnames-2692b5-3
fleet:worker/cb206-opencode-sqlite-128718-2
fleet:worker/cb185-worktree-group-fc0c99-1
fleet:worker/cb-137-ask-ticket-e7760c-2
fleet:worker/cb-172-broker-uri-d36ae4-4
fleet:worker/cb-175-model-readback-76ead6-3
fleet:worker/cb-161-pane-ancestry-293510-1
fleet:worker/cb-164-rebase-885863-8
fleet:worker/cb-164-empty-scrape-false-success-1a80af-3
fleet:fix/cb-197-ticket-ttl-from-completion
fleet:worker/cb-189-remote-url-coverage-4692f3-1
fleet:worker/cb-185-blockers-027756-4
fleet:worker/cb-192-gap-log-11b631-2
fleet:worker/cb-633-fix-5f4396-3
fleet:worker/cb185-router-d6436d-3
fleet:worker/cb185-router-routing-gaps-9e9d33-3
fleet:worker/cb185-paneids-992586-2
fleet:worker/cb-633-allow-list-union-ed374b-1
fleet:worker/cb-157-credential-in-remote-url-496e44-2
fleet:worker/cb-641-health-herdr-evidence-8f1f54-6
fleet:worker/cb-640-health-msg-evidence-99c9cd-1
fleet:worker/cb-642-fleets-status-skill-bbbc40-5
fleet:cb-634-ide-mcp
fleet:worker/lead-comms-wiring-c014b9-7
fleet:worker/lead-mailbox-c19577-6
fleet:worker/autocompact-window-82bc2f-5
fleet:worker/cb-634-probe-18056f-4
fleet:worker/cb635-broker-urienv
fleet:worker/cb-632-config-retry-8e0efa-7
fleet:lead/cb-622e-claude-md
fleet:lead/cb-622-followup
fleet:worker/cb-622a-165dff-1
fleet:lead/cb-622d-opencode-mount
fleet:worker/cb-622b-717c67-2
fleet:worker/cb-622c-ab7759-3
fleet:worker/cb-617b2-20ca4b-3
fleet:worker/cb-617a-5c2f4a-1
fleet:worker/cb596-4e49ef-3
fleet:worker/cb586-10500c-1
fleet:worker/cb-606-b9343a-25
fleet:worker/cb604-1445f8-24
fleet:worker/cb582-477374-21
fleet:worker/cb584-8c2281-22
fleet:worker/cb600-e6b9a9-20
fleet:worker/cb602-ce257f-19
fleet:worker/cb601-b42837-18
fleet:worker/cb598-6c7ba7-17
fleet:worker/cb599-740fe4-16
fleet:worker/cb597-282224-15
fleet:worker/cb590fix-185e9a-10
fleet:worker/cb528-recovery-race
fleet:worker/cb594-96bead-8
fleet:worker/cb590-916766-2
fleet:worker/cb527-997d99-3
fleet:worker/cb592-env-leak-3cbf9c-1
fleet:worker/cb588-async-ticket-nudge-3218f7-5
fleet:worker/cb578b-9dcb13-6
fleet:worker/cb581-d24826-5
fleet:worker/m2-u5-ef8c42-15
fleet:worker/cb578a-516499-2
fleet:worker/cb576-01a04b-17
fleet:worker/cb579-lead-tab-acba06-20
fleet:worker/cb580-terminal-health-ed6058-21
fleet:worker/cb577-f36fdc-18
fleet:worker/cb573b-3db06f-16
fleet:worker/cb568c-f36fdc-18
fleet:worker/cb568-drop-cause-c3ac1c
fleet:worker/cb575-cancelled-notification-c3ac1c
fleet:worker/m4-sol-a2cbec-3
fleet:worker/cb574-async-ask-c3ac1c
fleet:worker/cb573-health-model-8ca857-14
fleet:worker/cb572-unknown-target-7f2e35-13
fleet:worker/u4-700706-9
fleet:worker/u3-b9fcb6-6
fleet:worker/u2-ef5b68-4
fleet:worker/u1-469dce-1-clean
fleet:worker/u1-469dce-1
fleet:worker/cb-564-health-events-70cf7e-2
fleet:worker/cb-565-recycle-drops-role-98e58f-3
fleet:worker/cb-563-missing-reply-df2866-1
fleet:worker/cb-562-readiness-gate-silent-6c23c9-3
fleet:worker/cb-560-architect-presence-da8155-1
fleet:worker/cb-561-architect-silent-off-a71cab-2
fleet:worker/cb-548-bind-architect-slot-fe1b8c-1
fleet:worker/parity-overlay-settings-5fb711-1
fleet:secrets-central-store
fleet:cb-559-hot-key-correction
fleet:cb-557-fleet-role-pools
fleet:worker/cb-553-maxload-explicit-spawn-305ee3-6
fleet:worker/cb-551-idle-lead-heartbeat-f1633c-1
fleet:worker/cb-544-drain-preserves-worktree-925fad-3
fleet:worker/cb-552-docs-sync-1cb9cf-4
fleet:worker/cb-548-rendezvous-guard-rebased
fleet:worker/cb-548-rendezvous-guard-116b53-10
fleet:worker/cb-548-authz-v2-586df6-8
fleet:worker/cb-548-authz-264363-5
fleet:salvage/cb-528b-codex-home
fleet:salvage/cb-528a-codex-launcher
fleet:CB-518-primary-flow
fleet:feature/peer-launcher-spi
fleet:cb-103-injector
No Reviewers
Labels
Clear labels
blocked
needs-live-proof
ready-to-delegate
silent-default
Cannot start until something else lands. The body says what.
Merged and green, but never shown working on the running daemon. Not the same as done.
Scope, files and acceptance criteria are written. A worker can be briefed from the body alone.
A feature that compiles, passes tests, and ships turned off. Nine recurrences and counting.
No Label
Milestone
No items
No Milestone
Projects
Clear projects
No project
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: fleet/fleetd#372
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "worker/fleetd-369-hermetic-git-tests-e8b19a-3"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
fleetd #369 —
GitWorktreesTestwas reading the machine's real git config, so it could pass or fail for reasons outside the repo.The defect, measured on
mainat92c0f16:Where the leak was: the test's own
gitOutput(used bygit()) setGIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM/GIT_TERMINAL_PROMPTbut notXDG_CONFIG_HOME.status(Path, String),fullStatus(Path), and every other rawgitsubprocess this test class started (revParse,lsTree,diffNameOnly,forEachRef,blobOf,treeOf,commitTree,exitCode) set NO isolation at all, inheriting the JVM's whole real environment — including the operator's real~/.gitconfigand default excludes file ($XDG_CONFIG_HOME/git/ignore/$HOME/.config/git/ignore, applied by git with nocore.excludesFileconfigured at all —gitignore(5)).Fix (test-only, no production code touched): centralized every git subprocess this test class starts through one factory,
gitProcessBuilder(Path cwd, String... args), which always applieshermeticGitEnv(the isolation map #366 already introduced forseedingGitWorktrees's productionGitWorktreesinstances) includingXDG_CONFIG_HOMEpointed at a class-scoped throwaway@TempDir.Criterion 4 — made hard to undo by accident: added
everyGitSubprocessGoesThroughTheHermeticFactory, a self-check test that counts literalnew ProcessBuilder(constructions in this file's own source and asserts there are exactly 2 (the factory itself, and the one documented exception below). A future helper that shells out togitdirectly instead of going through the factory changes that count and fails the test — the omission that caused this ticket is now caught by name instead of rediscovered on a poisoned machine.Deliberately left alone:
worktreeCredentialHelperCompletesWithoutUsingAnInheritedHelper— its whole point is that git must resolve a synthetic "operator's global config" and then havecredential.helperNOT read it via the environment credential helper; it cannot use the shared hermetic env (which pointsGIT_CONFIG_GLOBALat/dev/null) without defeating the test. It never runsgit status, so it needs noXDG_CONFIG_HOMEisolation either. Documented in a comment at the call site and ineveryGitSubprocessGoesThroughTheHermeticFactory's javadoc.seedSkillsExcludeDoesNotLeakIntoASiblingWorktree'splainGitWorktreesinstance — already commented in-file:memberSkillsSourceis null, soseedSkillsno-ops before touchingcore.excludesFile, so it does not need the hermeticgitEnvseam.seedSkillsIsBestEffortWhenSourceDoesNotExistandseedSkillsNeverOverwritesAReposOwnSkill— checked both: the first never seeds anything past a missing-directory check, the second seeds nothing new (the skill is "kept", not "seeded") soexcludeSeededSkillsFromGitStatus(the method that reads the XDG fallback) is never reached in either case.Criterion 5 — machine dependencies found elsewhere in the test tree (reported, not changed):
EnvAllowListScrubTest(src/test/java/dev/ltms/fleet/member/EnvAllowListScrubTest.java) deliberately starts a real login zsh that sources this operator's real~/.zshenv/~/.zshrc/~/.zprofile/~/.zloginchain — documented in the class javadoc as intentional, and gated withassumeTrueso it skips cleanly on a machine with no/bin/zshor no real shell rc files.AmqpReplyInboxContractTestandLeadMailboxTest(src/test/java/dev/ltms/fleet/msg/) readAMQP_URIfrom the real environment to opt into an externally-provisioned broker in CI; locally (unset) Testcontainers spins a container instead. Documented, taggedcontract.ClaudeCodeLauncherTestalready carries a dedicated, well-designed guard for real-~/.claude.jsonwrites (fleetd #258): a@BeforeAll/@AfterAlldifferential check plus per-testuser.homeredirection to a@TempDir. Checked it — no violation found, and no test in this class writes the real file.System.getenv/user.homereliance found in the sweep (ClaudeCodeLauncherTest's own comment references aside).Build results:
Tests run: 60, Failures: 0, Errors: 0, Skipped: 0—BUILD SUCCESS(60 = the original 59 plus the new self-check test).Tests run: 1413, Failures: 0, Errors: 0, Skipped: 0—BUILD SUCCESS.mvn clean install, run unpiped:Tests run: 1413, Failures: 0, Errors: 0, Skipped: 0—BUILD SUCCESS.No assertion was weakened — every test still asserts exactly what it asserted before; only the environment each git subprocess runs in changed.
Review round 2 — criterion 4 fix
The reviewer correctly showed
everyGitSubprocessGoesThroughTheHermeticFactorywas a proxy, not the property itself: it countsnew ProcessBuilder(call sites, so it catches a new helper built the old way, but notgitProcessBuilderitself being gutted. Proof given: deletepb.environment().putAll(hermeticEnv())from inside the factory, every call site is unchanged, the count stays 2, andmvn clean install(no poison) stays green at 1413 tests.Fix: added
gitProcessBuilderCarriesTheFullHermeticEnvironment(@TempDir Path tmp), which callsgitProcessBuilderdirectly and asserts onpb.environment():GIT_CONFIG_GLOBAL==/dev/nullGIT_CONFIG_SYSTEM==/dev/nullGIT_TERMINAL_PROMPT==0XDG_CONFIG_HOMEis set, non-blank, resolves inside the class's ownCLASS_TMP(never left unset — which would inherit whatever the JVM's real environment carries), and the resolved<xdg>/git/ignoreprovably does not existThis inspects the actual environment the factory hands to
ProcessBuilder#start(), so it fails the moment the hermetic environment stops being applied — on any machine, no poison required. Kept the existing call-site count check as well; the two catch different regressions (a bypassing call site vs. a gutted factory).Proof, all four numbers requested:
pb.environment().putAll(hermeticEnv())from the factory, no poison, ran only the new test:Tests run: 1, Failures: 1—GIT_CONFIG_GLOBAL must be neutralized... ==> expected: </dev/null> but was: <null>— new test FAILS, confirmed.new ProcessBuilder("git", "-C", cwd.toString(), "status").start()in a rogue helper (factory untouched); ran only the count-check test:Tests run: 1, Failures: 1—expected: <2> but was: <3>— existing count check FAILS, confirmed still working.mvn clean install, unpiped, no poison:Tests run: 1414, Failures: 0, Errors: 0, Skipped: 0—BUILD SUCCESS(1414 = prior 1413 + 1 new test). Zero[ERROR]lines in the full log.XDG_CONFIG_HOME=/tmp/poison mvn test(whole suite):Tests run: 1414, Failures: 0, Errors: 0, Skipped: 0—BUILD SUCCESS.The smaller point (shared
CLASS_TMP): left as is. Every call tohermeticEnv()goes throughhermeticGitEnv(CLASS_TMP), which appends a freshhermetic-xdg-config-home-<System.nanoTime()>subdirectory per call — so no two git subprocesses in this class ever share the sameXDG_CONFIG_HOMEpath, and nothing ever writes into any of them (git only reads agit/ignorefile there, and none is ever created) — so the shared-parent-directory concern doesn't have a write hazard to land on. Not changing it.Pushed as
a37acd5on the same branch.everyGitSubprocessGoesThroughTheHermeticFactory counts ProcessBuilder("git", ...) call sites, so it catches a new helper built the old way, but a reviewer proved it does not catch gitProcessBuilder itself being gutted: removing pb.environment().putAll(hermeticEnv()) from inside the factory leaves every call site unchanged, the count stays 2, and the whole unpoisoned suite stays green. Add gitProcessBuilderCarriesTheFullHermeticEnvironment, which inspects what the factory actually hands to ProcessBuilder#start(): every hermetic key present with the isolating value, and XDG_CONFIG_HOME pointed inside the class's own throwaway directory rather than left unset or pointing at the operator's real one. This fails the moment the hermetic environment stops being applied, on any machine, with no poison needed. Keep the call-site count check too — the two catch different regressions.Merged into
mainas154971c(merge commit, not via the forge button), so closing this PR.Round 2 (
a37acd5) is what made it mergeable:gitProcessBuilderCarriesTheFullHermeticEnvironmentasserts on the factory's actual output instead of relying on the call-site count alone. Full reasoning, the poison control, and the merge-time mutation are in #369; the gap that mutation found is #373.Post-merge on
main:mvn clean install→ Tests run: 1425, Failures: 0, Errors: 0 — BUILD SUCCESS.Pull request closed