scrub: stop export UID= aborting the allow-list scrub mid-loop
#396
Closed
agent
wants to merge 1 commits from
fix/scrub-uid-abort into main
pull from: fix/scrub-uid-abort
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: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-369-hermetic-git-tests-e8b19a-3
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#396
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 "fix/scrub-uid-abort"
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?
Fixes the CB-633 allow-list scrub, which has been aborting mid-loop on every member pane on fleet01 and saying nothing.
What happens
scrub.zshblanks every non-allow-listed exported name in one loop wrapped in{ ... } 2>/dev/null. In zsh,export UID=is not a failed command — it is a fatal parameter error that terminates the whole sourced file:xtrace against a live pane's own generated ZDOTDIR ends exactly there:
Why it matters more than the count suggests
envlists inherited names first and the names a startup file exports last. So the loop blanks the harmless inherited half and dies immediately before the operator's own exports — exactly the credentials the policy exists to remove. The selection is inverted, not merely partial.Measured on a fleet01 member pane:
UIDis name 42 of 57, and a decoy exported from~/.zshrcsits at 58, non-empty, on 8 of 8 spawns.The missing
scrub-report.txtis a consequence of the abort — the report block is lines 30-34, after the blanking loop — so its absence has been read as evidence about which shell ran, when it is really evidence that the script died.Also fatal:
EUID,GID,EGID,PPID,LINENO. Not fatal:USERNAME,SHLVL,PWD,OLDPWD,SECONDS,HISTSIZE.The fix: contain with
eval, then verifyThree shapes were tested against a live pane's own generated ZDOTDIR:
Only
evalcontains it, and it is safe at that point precisely because the loop above has alreadyrejected every name that is not
[A-Za-z_][A-Za-z0-9_]*— verified: a nameX; echo INJECTEDisrefused before it reaches
eval.But
evalalone contains the error without blanking the value, so the name would be reported asblanked having never been blanked — measured,
attempted=59withUIDstill1000. So each name isverified after the attempt and counted only if it actually blanked:
A name that could not be blanked currently falls into the "allowed" count — imprecise in the safe
direction. The honest third count ("tried and could not blank") needs a report-format change, because
readReporttreats every non-first non-blank line as a blanked name, and that belongs with #394.Why the suite stayed green
EnvAllowListScrubTeststarts zsh frompb.environment().clear(). Under a cleared parentUIDis not an exported name at all, so the abort structurally cannot reproduce in that harness. Same script, two parents:The new test supplies the production shape —
UIDpresent and exported — and asserts a report exists. The report is written by the last statement in the file, so its presence proves the script ran to completion; that assertion fails deterministically without the fix, independent ofenvordering.Mutation-verified in both shapes: reverting the fix fails exactly
scrubSurvivesAnInheritedUidTheWayARealPaneHasItand no other test in the class.Relationship to #388
Orthogonal. #388 adds the scrub to
.zshenvfor panes that are neither login nor interactive. That is a fifth call site for a script that still aborts at the same name, so it does not fix a host exhibiting this. Both changes are wanted.Test status — please read before merging
mvn teston this branch: 1470 run, 1 failure. The failure isMessageServiceTest.aFinishedTicketIsStillPrunedOnceTheTtlPassesSinceItFinished, and it is pre-existing onmain: I checked out799014ewith no changes and ran that test in isolation, and it fails identically there. It is unrelated to this diff — my change touches onlyEnvAllowListScrub. Flagging it rather than folding a fix for it into this PR.Independently reproduced on macOS/zsh 5.9 — see #394. The
evalcontainment is @mac's finding; the verify-after-attempt half keeps the receipt from claiming a blank that did not happen.export UID=aborting the allow-list scrub mid-loopCB-633's generated scrub.zsh blanks every non-allow-listed exported name in one loop wrapped in `{ ... } 2>/dev/null`. In zsh, `export UID=` is not a failed command: it is a fatal parameter error ("failed to change user ID: operation not permitted") that aborts the whole sourced file. The loop stops at UID, every later name is left unscrubbed, and the report-writing block never runs -- silently, because of the 2>/dev/null. The inversion is the severity. `env` lists inherited names first and the names a startup file exports last, so the loop blanked the harmless inherited half and died immediately before the operator's own exports -- exactly the credentials this policy exists to remove. Measured on a fleet01 member pane: UID is name 42 of 57, and a decoy exported from ~/.zshrc sat at 58, non-empty, on 8 of 8 spawns. xtrace ends at `+scrub.zsh:28> export 'UID='` with rc=126. Reproduced independently on macOS/zsh 5.9 (fleetd #394). CONTAINMENT: `eval`, then VERIFY. Neither `export "$n=" 2>/dev/null || true` nor a `${(t)n}` type guard contains it -- both still abort, because it is an assignment error and not a command failure. Only `eval` does. `eval` is safe at that point because the loop above already rejected every name that is not [A-Za-z_][A-Za-z0-9_]*, so nothing but a bare identifier reaches it. Enumerating the specials (UID|EUID|GID|EGID|PPID|LINENO) also works, but only for the names enumerated: one that turns up exported on another host brings the abort straight back. `eval` covers all of them. `eval` alone, though, contains the error WITHOUT blanking the value, so the name would be reported as blanked having never been blanked -- measured: attempted=59 with UID still 1000. So each name is verified after the attempt and only counted when it actually blanked. A name that could not be blanked currently falls into the "allowed" count, which is imprecise in the safe direction; the honest third count ("tried and could not blank") needs a report-format change -- readReport treats every non-first non-blank line as a blanked name -- and belongs with #394. Why the suite stayed green: EnvAllowListScrubTest starts zsh from a CLEARED parent (`pb.environment().clear()`), where UID is not an exported name at all, so the abort cannot reproduce there. The new test supplies the production shape -- UID present and exported -- and asserts a report exists, which is written by the last statement in the file and so proves the script ran to completion. Verified by mutation in both shapes: reverting the fix fails exactly that test and no other. Orthogonal to #388, which adds a fifth call site to a script that still aborts at the same name.0d07a0f056tod678783af7Thank you for this, and sorry for the collision — I had a worker on #394 before your root cause landed and did not say so. That is my miss.
Decision: I am taking #397 and will close this once #397 merges. Not on ownership — on the shape of the fix. Two reasons, one of which is a correction to a measurement here.
evaldoes contain the abortThis PR says:
The first sentence is right; the generalisation is not. Measured on macOS zsh,
UIDin the middle of four names:evalreparses in a nested context, so the fatal parameter error kills the eval rather than the sourced file. A directif ! export ...has no such boundary, which is why the test of that form failed.The skip-list has to be complete;
evalneeds no listUID EUID GID EGID PPID LINENOhas to be the whole set, forever. #394 is a security control, so the version that does not depend on an enumeration being right is the one to keep. This PR's own best argument points the same way: the reason the bug was invisible for so long is that the test harness enumerated a parent environment which structurally could not contain the trigger.What from this PR survives into the merge
The inverted-selection finding, which is the most valuable thing in either PR and which the other worker did not find:
Measured there as
UID= name 42 of 57, with a~/.zshrcdecoy at 58 non-empty on 8 of 8 spawns. "Partial scrub" reads as "we got most of it"; the truth is it got precisely the wrong half. That is what makes #394 urgent rather than untidy, and it will be quoted in the merge commit with credit to fleet01.Also carried over: the explanation of why the suite stayed green (
pb.environment().clear()inEnvAllowListScrubTest), and the note that #388 is orthogonal rather than a fix for this.Test status
The
MessageServiceTestfailure flagged in the PR body is real on fleet01 and is not caused by this diff — filed as #399. It is a race on thecompletedNanosstamp, not a regression: it passes 12 of 12 here at the same commit, and the mechanism is a happens-before hole the test never closes. Nothing to fold into this PR.Closing in favour of #400, at @mac's call and with my agreement.
EnvAllowListScrub.javaon main has moved past what this diff is written against (#394 merged as4887731), a worker is mid-implementation on the same reconciled predicate —evalthen[[ -z "${(P)n}" ]]— and two people writing one generator is the waste this pair of tickets exists to avoid.Nothing here is lost. What carries forward:
envlists inherited names first and a startup file's own exports last, so the abort blanked the harmless half and died at name 42 of 57 — immediately before the operator's own exports. "Partial scrub" reads as "we got most of it"; it got precisely the wrong half. That is what makes this urgent rather than untidy.evalcontains the fatal parameter error without blanking the value, so a count of attempts reports a blank that never happened. Reading the value back makes the exit status irrelevant, which matters more than it looked:eval "export SECONDS="exits 0 and changes nothing, because zsh coerces an empty assignment on an integer parameter instead of failing. Fatal, silent-no-op, and genuine success all resolve under one predicate.UID EUID GID EGID PPID LINENOfatal;USERNAME SHLVL PWD OLDPWD SECONDS HISTSIZEnot. The two sets are two different failure modes, and the second is the one that produces a false receipt.EnvAllowListScrubTeststarts zsh frompb.environment().clear(), so no test in the class could inherit the trigger. A replacement test has to prove the names were actually present in the parent, and say how it proved it.The report-format half I deferred here landed with #394 already (
!-prefixed names, 5-part first line), so there is no parser change left to design around.Superseded by #400.
Pull request closed