CB-634: the parity overlay copies .env/.envrc into worker worktrees — the same shape as CB-633 #148
Closed
opened 2026-08-23 07:53:56 +02:00 by ltms
·
5 comments
No Branch/Tag Specified
main
worker/fleetd-612-unita-87807e-1
worker/612-b3-mcpwirings-da2b58-3
worker/612-b2-cb185-176d3a-2
worker/612-b1-completion-457459-1
worker/612-agaps-73a926-2
worker/608-sleeps-3a64ff-3
worker/621-b4520b-1
worker/618-b83894-2
worker/fleetd-615-e05481-5
worker/lead-autocompact-5f1ab2-3
worker/fleetd-613-f85deb-3
worker/fleetd-608-flaky-nudge-test-d0c2d1-3
worker/lead-context-gauge-ad404f-1
worker/gauge-wiring-9158c1-4
worker/redeploy-slowstart-ead0e5-5
worker/charter-bytes-13668c-6
worker/rollover-outcome-291483-2
worker/589-f64303-2
worker/593-1a8025-5
worker/589-fcd2aa-1
worker/568-9fdaa2-3
worker/571-attempted-outcome-5739f7-2
worker/581-completionresolver-cas-sites-0542b7-6
worker/562-loop-health-wiring-test-99611c-5
worker/562-surface-loop-health-7df5cc-4
worker/575-waiter-cleanup-sites-62ad80-1
worker/572-answer-lock-release-46a9ae-5
worker/567-probe-channel-leak-a38fc5-6
worker/551-record-before-send-7cbf56-1
worker/561-listener-fanout-survives-a-throw-61d538-2
worker/555-redeploy-main-flow-seam-65c2f5-2
worker/556-injector-owns-registration-e027a5-1
worker/552-post-restart-mktemp-abort-bc2672-4
worker/553-onstatus-completion-leak-0da881-2
worker/550-shasum-linux-196132-1
worker/538-loop-dies-on-error-4a5eeb-6
worker/426-health-coverage-ef1fd4-4
worker/504-failed-reported-clean-3cfd66-3
worker/537-capturedlog-close-e4c437-2
worker/459-broken-link-targets-cadc17-5
worker/535-appender-leak-fe74c1-1
worker/512-part2-shutdown-detection-434701-9
worker/529-logger-level-sweep-2a5533-8
worker/528-drain-gate-call-site-5de83d-7
charter/forge-mcp-vs-token
worker/521-swap-guard-unpinned-28e931-5
worker/519-probe-test-harness-d25ab8-4
worker/525-logger-level-leak-1b4eb0-6
worker/518-fleetmcp-resolver-wiring-8ef96c-1
worker/512-drain-complete-line-7edd71-3
worker/517-abort-branch-and-jar-id-41b641-2
worker/500-9e52c9-3
worker/509-4912f4-2
worker/511-9a4b23-1
worker/493-479f45-2
worker/505-03f8b2-1
worker/492-followup-detect-unclear
worker/501-a31fa0-7
worker/498-451d1c-5
worker/494-1015ce-2
worker/492-209647-1
worker/489-001902-2
worker/480-relative-handover-path-906323-1
worker/480-b-handover-skill-45bf1f-5
worker/474-followup-source-pin-f54a55-17
worker/474-charter-check-on-reload-f54a55-17
worker/466-quarantine-repeatcount-report
worker/393-opencode-skill-seeding-71854b-13
worker/469-canonical-tool-names-2a472a-16
worker/466-quarantine-escalation-5ae9c1-15
worker/446-hot-exhausted-pattern-0af580-6
worker/464-charter-tool-name-guard-a85635-12
worker/463-listfleet-default-fails-open-f1c76c-11
worker/458-invariant-5-by-purpose-862f9a-10
worker/439-coordinator-row-gate-bc032a-8
worker/449-herdr-protocol-576015-4
worker/450-abstract-spawn-599e1c-5
worker/437-ack-refuses-177d91-1
worker/444-placement-window-feb56a-2
worker/440-helddurable-derived-d462d7-13
worker/425-rework-placement-resolve-c58ba1-9
worker/421-lead-peek-held-msgs-cdbad2-10
worker/435-fixed-policy-cap-fe11de-12
worker/422-gate-state-observability-9e79d6-11
worker/431-memberregistry-live-readers-cdbad2-10
worker/424-architect-slot-hot-038b41-7
worker/422-model-gate-spawn-c29f48-6
worker/425-default-profile-live-f55534-8
worker/415-coverage-wording-2cbf9c-5
worker/416-3ad1da-1
worker/418-588283-3
worker/deterministic-stamp-race-409-3cb7b6-10
worker/armed-reads-live-config-404-ed931f-9
worker/reply-peer-refusal-391-5a34bd-7
worker/models-allowlist-aa9e9b-3
worker/ttl-stamp-race-399-f1122f-8
worker/scrub-receipt-400-316b3e-5
worker/exhaustion-detection-395-105105-6
worker/scrub-abort-394-316b3e-5
fix/scrub-uid-abort
worker/task-scrub-517574-2
worker/t386-clock-bd5b78-4
worker/t384-scrub-813790-5
worker/t381-cc-748314-2
worker/t373-336973-2
worker/t365-3920c5-3
worker/t358-6e989b-1
worker/t355-8b321c-1
worker/fleetd-369-hermetic-git-tests-e8b19a-3
worker/fleetd-368-stale-lead-binding-f5682e-2
worker/fleetd-360-deploy-units-0d3793-1
worker/359-dead-lead-tabs-f1253b-4
worker/362-worktree-skills-c03e51-3
worker/361-coord-visibility-655144-1
362-plugin-visibility-and-drift
worker/errscan-bed2ca-2
worker/amqp-log-identity-bed2ca-2
worker/withdefaults-guard-561704
worker/sleepguard-82076d-1
worker/fd334-9ee1b6-5
worker/fd348-f1ab27-4
worker/fd335-a71c35-1
worker/fd342-174a17-2
worker/fd345-490d0f-3
worker/fleetd-337-5ec7d4-21
worker/fleetd-341-af5a6b-24
worker/fleetd-339-5ca0a2-23
worker/fleetd-338-83a4a1-22
worker/fleetd-333-281f46-18
worker/fleetd-329-11bdbb-16
worker/fleetd-330-2770fb-17
worker/fix-326-50506e-15
worker/fix-324-3e9bbf-14
worker/fix-323-b8287d-13
worker/fix-316b-bd0860-11
worker/fix-318-76ca36-9
worker/fix-317-486aec-8
worker/fix-315-ce47c5-6
worker/fix-307-275890-6
worker/fix-308-b4f664-7
worker/fix-309-ec3939-8
worker/fix-310-7a3974-9
worker/fix-302-52ad0e-9
worker/fix-298-ce1acb-8
worker/fix-297-66bd11-7
worker/fix-296-104622-6
worker/fix-293-bare-closetab-eb22b5-3
worker/fix-280-gone-ask-lapse-bca98e-2
worker/fix-290-reapidle-guard-coverage-9b0dd1-1
worker/fix-285-trust-seed-8f3565-10
worker/fix-284-backend-error-seat-85912c-11
worker/fix-282-chained-ask-e6d0bb-8
worker/fix-283-teardown-leaks-f40dfa-9
worker/fix-281-pin-handler-actions-4921ac-7
worker/audit-rendezvous-lifecycle-d072ae-2
worker/audit-health-placement-1a2476-6
worker/audit-teardown-exits-e207a5-3
worker/audit-launcher-asymmetry-27e370-4
worker/audit-rest-authz-6ca53c-5
worker/investigate-275-abandon-asking-fdef52-8
worker/fix-274-worktree-leak-b0095d-7
worker/fix-273-exhausted-pattern-9665b5-6
worker/fleetd-267-model-check-bd8068-1
worker/fleetd-131-archunit-18b834-7
worker/fleetd-266-sshagent-rename-a014ff-6
worker/fleetd-184-uid-claim-8e1f31-4
worker/fleetd-184-warn-b381ee-10
worker/fleetd-184-docs-be1d12-9
worker/fleetd-257-9bf010-7
worker/fleetd-103-23a113-6
worker/fleetd-247-342356-5
worker/fleetd-116-04dea8-4
worker/fleetd-252-a830e0-3
worker/fleetd-111-7e8673-9
worker/fleetd-155c-f8ef4b-8
worker/fleetd-176-b928ca-3
worker/fleetd-249-7a7878-2
worker/cb248-composition-root-b-9acdf7-15
worker/cb148-envrc-default-fa6c82-12
worker/cb201-unit5-wiring-6c12e6-8
worker/cb241-fallback-echo-1175e9-11
worker/cb149-trust-dialog-2392a5-9
worker/cb134-148-overlay-visible-c9b986-10
worker/cb234-session-id-keyed-04e1fc-1
worker/cb201-unit3-nudge-abdf5c-6
worker/cb201-unit2-policy-c1102c-5
worker/cb201-unit4-outcome-a13bfa-7
worker/cb201-unit1-classifier-91b9b1-4
worker/cb201-227-refine-831980-3
worker/cb175-model-readback-0f085f-1
worker/cb222-charter-tmpdir-17f013-1
worker/cb226-architect-slot-race-cd3aa8-3
worker/cb224-worktree-root-group-024523-2
worker/cb-123-role-demotion-c600f7-2
worker/cb-219-opencode-roots-1f677e-1
worker/cb214-claude-session-id-b9eab4-4
worker/cb213-zdotdir-wrong-process-dd6de4-3
worker/cb211-exhaustion-classification-9546e0-2
worker/cb137-ambiguous-task-4df3d8-4
worker/cb209-agentsessionid-4dfdb6-2
worker/cb185-hostenvnames-2692b5-3
worker/cb206-opencode-sqlite-128718-2
worker/cb185-worktree-group-fc0c99-1
worker/cb-137-ask-ticket-e7760c-2
worker/cb-172-broker-uri-d36ae4-4
worker/cb-175-model-readback-76ead6-3
worker/cb-161-pane-ancestry-293510-1
worker/cb-164-rebase-885863-8
worker/cb-164-empty-scrape-false-success-1a80af-3
fix/cb-197-ticket-ttl-from-completion
worker/cb-189-remote-url-coverage-4692f3-1
worker/cb-185-blockers-027756-4
worker/cb-192-gap-log-11b631-2
worker/cb-633-fix-5f4396-3
worker/cb185-router-d6436d-3
worker/cb185-router-routing-gaps-9e9d33-3
worker/cb185-paneids-992586-2
worker/cb-633-allow-list-union-ed374b-1
worker/cb-157-credential-in-remote-url-496e44-2
worker/cb-641-health-herdr-evidence-8f1f54-6
worker/cb-640-health-msg-evidence-99c9cd-1
worker/cb-642-fleets-status-skill-bbbc40-5
cb-634-ide-mcp
worker/lead-comms-wiring-c014b9-7
worker/lead-mailbox-c19577-6
worker/autocompact-window-82bc2f-5
worker/cb-634-probe-18056f-4
worker/cb635-broker-urienv
worker/cb-632-config-retry-8e0efa-7
lead/cb-622e-claude-md
lead/cb-622-followup
worker/cb-622a-165dff-1
lead/cb-622d-opencode-mount
worker/cb-622b-717c67-2
worker/cb-622c-ab7759-3
worker/cb-617b2-20ca4b-3
worker/cb-617a-5c2f4a-1
worker/cb596-4e49ef-3
worker/cb586-10500c-1
worker/cb-606-b9343a-25
worker/cb604-1445f8-24
worker/cb582-477374-21
worker/cb584-8c2281-22
worker/cb600-e6b9a9-20
worker/cb602-ce257f-19
worker/cb601-b42837-18
worker/cb598-6c7ba7-17
worker/cb599-740fe4-16
worker/cb597-282224-15
worker/cb590fix-185e9a-10
worker/cb528-recovery-race
worker/cb594-96bead-8
worker/cb590-916766-2
worker/cb527-997d99-3
worker/cb592-env-leak-3cbf9c-1
worker/cb588-async-ticket-nudge-3218f7-5
worker/cb578b-9dcb13-6
worker/cb581-d24826-5
worker/m2-u5-ef8c42-15
worker/cb578a-516499-2
worker/cb576-01a04b-17
worker/cb579-lead-tab-acba06-20
worker/cb580-terminal-health-ed6058-21
worker/cb577-f36fdc-18
worker/cb573b-3db06f-16
worker/cb568c-f36fdc-18
worker/cb568-drop-cause-c3ac1c
worker/cb575-cancelled-notification-c3ac1c
worker/m4-sol-a2cbec-3
worker/cb574-async-ask-c3ac1c
worker/cb573-health-model-8ca857-14
worker/cb572-unknown-target-7f2e35-13
worker/u4-700706-9
worker/u3-b9fcb6-6
worker/u2-ef5b68-4
worker/u1-469dce-1-clean
worker/u1-469dce-1
worker/cb-564-health-events-70cf7e-2
worker/cb-565-recycle-drops-role-98e58f-3
worker/cb-563-missing-reply-df2866-1
worker/cb-562-readiness-gate-silent-6c23c9-3
worker/cb-560-architect-presence-da8155-1
worker/cb-561-architect-silent-off-a71cab-2
worker/cb-548-bind-architect-slot-fe1b8c-1
worker/parity-overlay-settings-5fb711-1
secrets-central-store
cb-559-hot-key-correction
cb-557-fleet-role-pools
worker/cb-553-maxload-explicit-spawn-305ee3-6
worker/cb-551-idle-lead-heartbeat-f1633c-1
worker/cb-544-drain-preserves-worktree-925fad-3
worker/cb-552-docs-sync-1cb9cf-4
worker/cb-548-rendezvous-guard-rebased
worker/cb-548-rendezvous-guard-116b53-10
worker/cb-548-authz-v2-586df6-8
worker/cb-548-authz-264363-5
salvage/cb-528b-codex-home
salvage/cb-528a-codex-launcher
CB-518-primary-flow
feature/peer-launcher-spi
cb-103-injector
v1.1.0
v1.0.0
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#148
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 "%!s()"
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?
Found while implementing #144 (CB-633), reported by the implementer and not fixed there — it was outside that unit's scope.
The shape
CB-633 exists because a control that lives in a file
fleetddoes not own can be undone by that file. The credential guard sat inside${SHARED_ENV}/tools/secrets.shand a latersourcehanded the values straight back.The CB-511 parity overlay has the same shape pointing the other way.
Profile.parityOverlaydefaults to copying.envand.envrcfrom the primary's checkout into every worker worktree. So a member's environment is shaped by filesfleetddoes not own and does not read — and a repo-committed.envrccan carry whatever it likes into a member.direnvmakes this concrete: an.envrcis executable shell that runs oncd. If the checkout has one, it runs inside the member with whatever it exports.Why it is not simply "the same bug"
Two differences worth keeping straight, because they change the fix:
parityOverlay), so an operator can narrow it today. The defect is the default, not the absence of a knob.But CB-633's allow-list runs after the login shell, and
.envrcis evaluated bydirenvoncd— which happens after the shell's startup files. So the scrub does not cover it. A credential re-exported by a copied.envrcsurvives CB-633 completely.What to decide
direnvtoo, or is that out of scope? Adirenvhook runs on everycd, so a one-shot scrub in.zlogincannot catch it..envrccome out of the defaultparityOverlaylist, leaving.env(data, not executable)? That is the smaller change and it removes the executable half.fleetdshould log which overlay files it actually copied for each spawn. Right now the copy is silent, so nobody can tell from the logs whether a member got an.envrcat all — and a silent copy is what makes this hard to notice.Acceptance
copied N of M candidates, notcopied N(#113).Not verified
I have not checked whether this repo's checkout actually has an
.envrc, nor whetherdirenvis installed on this host. So this is a real defect in the mechanism, not a demonstrated live exposure. Establish that first — the answer changes the priority.Checked the "not verified" section myself before anyone acts on this:
.envor.envrcexists in this checkout — not at the repo root, not inbridged/.direnvis not installed on this host.So there is no live exposure today. The overlay copies nothing, because there is nothing to copy, and even a copied
.envrcwould sit inert withoutdirenv.That drops the priority to low. It does not close the ticket: the default is still "copy an executable file we do not own into every member", and the day someone adds an
.envrcfor local convenience it becomes live with nothing warning them. That is the shape worth fixing while it is cheap.Point 3 — logging which overlay files were copied — is worth doing on its own merit and independent of the decision on points 1 and 2. Right now the only reason I can tell you there is no exposure is that I went and looked at the filesystem; the daemon's logs say nothing either way.
Lead decision on points 1 and 2, with the facts first
The ticket says the live exposure was never established and that the answer changes the priority. I checked both hosts.
.envin the checkout.envrcin the checkoutdirenvon PATH.env/.envrcin any live worktreeSo there is no live exposure today on either host. This is a latent defect in the default, exactly as suspected. That sets the priority: fix it, but it is not urgent.
Point 1 — does the scrub need to cover
direnv? No, out of scope.Two reasons. First,
direnvis not installed on either host, so this would be a control for a tool we do not run. Second, and this is the lasting reason: adirenvhook runs on everycd, so a one-shot scrub in.zlogincan never catch it. Building acd-time scrub means owning a hook in a file the member controls — which is the very shape CB-633 exists to get away from. The right control is to not copy the executable file at all, which is point 2.Point 2 — drop
.envrcfrom the default? Yes..envis data..envrcis executable shell that runs inside the member. Parity means a worker can reproduce the primary's build, and an executable hook is not needed for that. Dropping it removes the executable half and keeps the feature. The knob stays, so an operator who genuinely wants.envrccan still list it — the defect is the default, not the absence of a choice.The default is one line:
FleetConfig.java:411,List.of(".env", ".envrc")→List.of(".env").I am holding this half back for now.
FleetConfig.javais owned by #201/#227 Unit 5, which is in flight. I will make the one-line change myself once Unit 5 merges, rather than create a conflict in a central file for a latent defect. Tracking it here so it is not lost.Point 3 — do it regardless, and it pairs with #134
GitWorktrees.overlayParity(GitWorktrees.java:483-508) logs every step atdebug, so at the default level the copy is silent — which is what makes this hard to notice, as the ticket says. The same method also marks a copied tracked file--skip-worktreeat line 503 and says nothing, which is #134. Same method, same silence, so I am delegating them as one unit. The log will report the denominator (copied N of M candidates) per #113.Point 3 is done — merged to main in
0d7b4fb(PR #242).overlayParitynow reports the outcome per spawn atinfo, with the denominator as this ticket and #113 asked:The copy and mark logic is byte-for-byte unchanged; only logging is new. Verified: 1168 tests, 0 failures; reverting either line back to
log.debuggoes red with 0 compile errors, so the fix cannot regress to an invisible level.There is also a test that a member worktree gets exactly the configured overlay set and nothing else, which is the last acceptance line here.
Still open on this ticket
Point 2 only — dropping
.envrcfrom the default atFleetConfig.java:411. Decided above, held back becauseFleetConfig.javais owned by #201/#227 Unit 5, which is still in flight. I will make that one-line change myself as soon as Unit 5 merges.Point 1 was decided as out of scope, with reasons, in my earlier comment.
One correction
My PR comment originally paired this with #134. That was wrong: #134 is about
isolateToolSurface, a different method. #148 point 3 is fully delivered here; #134 is reopened and being worked separately.Decision on points 1 and 2 (lead), with the evidence the ticket asked for first
Live exposure: none today. I checked this host on 2026-09-03:
ls -la .env .envrcin the primary checkout → both absent.command -v direnv→ not on PATH.So the copy has nothing to copy and nothing would evaluate it. This is a defect in the default, not a demonstrated leak. That lowers the urgency but does not change the fix — the ticket's own note says creating either file at the repo root is enough to make it live, and that is a one-command accident.
Point 2 — yes, drop
.envrcfrom the default. New default:[.env]..envis data: a copy of it can only carry values..envrcis executable shell thatdirenvruns oncd, so a copy of it can carry behaviour. Those are not the same risk and they do not deserve the same default. Parity is still the feature, so the knob stays: an operator who genuinely wants.envrccopied writesparityOverlay: [".env", ".envrc"]and owns that choice explicitly. Nothing is removed, only un-defaulted.Point 1 — the scrub should not be extended to
direnv. Out of scope, and it is the wrong layer.The CB-633 scrub is a one-shot
.zloginunderZDOTDIR. Adirenvhook runs on everycd, which is after the shell's startup files by definition. So a one-shot scrub can never cover it — not because we wrote it wrong, but because the two mechanisms run at different times, and no amount of work on the scrub closes that. Chasing it would mean owning adirenvhook of our own inside every member, which is a much larger surface than the thing it protects.Point 2 makes point 1 unnecessary instead: if fleetd never copies the executable file, there is nothing for
direnvto evaluate that fleetd put there. That is the smaller change and it removes the executable half, exactly as the ticket proposed.One limit, stated plainly: this only covers
.envrcfiles fleetd copies. A repo that has its own committed.envrcstill gets it in every worktree, because a worktree is a checkout of the repo. Nothing here changes that, and nothing should — that file is the repo's, not ours. This repo has none.Point 3 — already done
Shipped in
0d7b4fb. The overlay now logs atinfowith the denominator the ticket asked for:Point 2 is next; I will close this ticket when it lands with its test.
All three points are done. Closing.
direnv?.zloginand adirenvhook runs on everycd, so no work on the scrub can cover it. Point 2 removes the need instead..envrcfrom the default[".env"].43206ca0d7b4fbVerified: 1216 tests, 0 failures, 0 compile errors,
BUILD SUCCESS.The escape hatch is pinned by its own test — an operator writing
parityOverlay: [".env", ".envrc"]still gets both, verbatim. Without that test this change would be a removal rather than a re-default, and nothing would notice if it silently became one.Mutation check: reverting the default to
List.of(".env", ".envrc")turnsparityOverlayDefaultsToDotEnvOnlyred, with 0 compile errors counted separately.Two things the worker found and handled well:
WorktreeSessionManagerTesthardcoded the same default at another layer and broke the build. That is outside the file list I named in the brief. It fixed it and said so rather than folding it in quietly.docs/CB-301-ext-Worktree-Provisioning.mdanddocs/Worker-Git-Workflow.mdstill describe the old[.env, .envrc]pair. Reported in one line and correctly left alone. Small doc drift, not worth a ticket on its own; whoever next edits those files should fix the sentence.One limit worth restating, because this ticket does not remove it. This only covers
.envrcfiles fleetd copies. A repo that commits its own.envrcstill gets it in every worktree, because a worktree is a checkout of that repo. Nothing here changes that and nothing should — that file belongs to the repo, not to fleetd. This repo has none.