+52
-7
@@ -79,6 +79,7 @@ six weeks, and the table alone will not carry it.
|
||||
| [An exhausted backend is quarantined even from a chrome-only pane](#an-exhausted-backend-is-quarantined-even-from-a-chrome-only-pane) | automatic | #211 | `inject/CompletionResolver` |
|
||||
| [The credential scrub follows the member's own user](#the-credential-scrub-follows-the-members-own-user) | `memberLoginShell:` + `worktreeGroup:` | #213 | `member/HerdrPeerLauncher` |
|
||||
| [An opencode member's config follows the member's own user](#an-opencode-members-config-follows-the-members-own-user) | `memberHerdrSocket:` + `worktreeGroup:` | #219 | `member/OpenCodeLauncher` |
|
||||
| [Every file a member must read follows the member's own user](#every-file-a-member-must-read-follows-the-members-own-user) | `memberHerdrSocket:` + `worktreeGroup:` | #222 #224 | `member/ClaudeCodeLauncher`, `session/GitWorktrees` |
|
||||
| [A role fleetd cannot bind is refused](#a-role-fleetd-cannot-bind-is-refused-not-quietly-downgraded) | automatic | #123 | `auth/MemberRegistry` |
|
||||
|
||||
Nearly every knob above lives in one file, on one profile:
|
||||
@@ -2812,6 +2813,39 @@ beats an answer read from the wrong directory.
|
||||
|
||||
---
|
||||
|
||||
## Every file a member must read follows the member's own user
|
||||
|
||||
**What.** Two more places where fleetd used to write a file into its own process's filesystem and
|
||||
then hand a member the path. The claude-code **role/reply charter** now goes under `worktreeRoot`
|
||||
instead of `java.io.tmpdir`, shared with `worktreeGroup`. And `worktreeRoot` **itself** is now made
|
||||
group-traversable where it is created, not only the worktrees inside it.
|
||||
|
||||
**On.** `memberHerdrSocket:` plus `worktreeRoot`/`worktreeGroup`. With `memberHerdrSocket:` absent,
|
||||
the charter path is byte-identical to before; with `worktreeGroup:` unset, the root is untouched.
|
||||
|
||||
**Why it exists.** This is the same shape as the two entries above, found twice more. The charter one
|
||||
became load-bearing without anyone noticing: the charter used to ride inline on
|
||||
`--append-system-prompt`, and the file was the exception. #220 made the file the **only** delivery
|
||||
path, always, to keep the launch command inside the pane's 1024-byte line. So under
|
||||
`memberHerdrSocket:` every claude-code member would have been handed a charter path it could not
|
||||
read. Measured against the real binary (claude 2.1.258), that is the loud failure — it prints
|
||||
`Error reading append system prompt file: EACCES` and exits 1 before it touches auth or the network,
|
||||
so the pane dies and the spawn fails at the readiness gate. Loud, but with no clue in the message.
|
||||
|
||||
The `worktreeRoot` one is the floor the other three stand on. A different uid needs the execute bit
|
||||
on **every** ancestor directory, so a root at `0700` makes every carefully-shared child unreachable —
|
||||
the member cannot read the opencode config, cannot reach its own checkout, and cannot read the
|
||||
credential scrub. `Files.createDirectories` respects the umask, so whether this bites depends on the
|
||||
umask of whatever shell started the daemon. It would work on the machine it was developed on and fail
|
||||
on the next one, and the failure looks like a member that never becomes deliverable.
|
||||
|
||||
**The gotcha: both refuse rather than degrade, and both are invisible until you turn the key on.**
|
||||
A charter is not a control, it is the member's turn contract — the rule that ends every turn with
|
||||
`fleet_reply` — so a member that cannot read it is broken, not weakened. Missing config refuses the
|
||||
spawn and names the key; an untraversable root refuses and names the root, its current mode and the
|
||||
group. None of this changes anything on a host where `memberHerdrSocket:` is unset, which is why it
|
||||
can look like dead code until the #185 rollout starts.
|
||||
|
||||
## A role fleetd cannot bind is refused, not quietly downgraded
|
||||
|
||||
**What.** A spawn asking for `role: architect` on a profile that no configured slot carries now fails
|
||||
@@ -2834,13 +2868,24 @@ Refusing was chosen over binding anyway for one reason: an architect's identity
|
||||
was bound to. With no slot, there is nothing to bind an identity to, so binding would mean inventing
|
||||
one. The operator's fix is one line of config, and the refusal names it.
|
||||
|
||||
**The gotcha: only the config gap is closed, not the race.** If a slot exists but is already bound
|
||||
when the bind runs, the member has already launched on the architect charter and is then recorded as
|
||||
`dev`. The roster stays honest and the demotion is logged at WARN, so this no longer hides — but the
|
||||
charter and the bound role still disagree for that member. A pre-check cannot close that window;
|
||||
#226 carries the reserve-then-bind fix. The refusal is also scoped to `architect` alone: dev and
|
||||
reviewer pools are placement candidates, not identity bindings, so an explicit profile outside them
|
||||
stays a supported override.
|
||||
**The race is closed too, by reserving first (#226).** A pre-check cannot close it: "does the config
|
||||
carry a slot" is stable, but "will a slot still be free after the launch" depends on a spawn that may
|
||||
not have happened yet, so widening the check only moves the window. Instead fleetd now **reserves** a
|
||||
matching slot before it launches, **binds** that reservation after, and **releases** it if the launch
|
||||
fails. A spawn that would lose the race is refused before a process exists, so no member is ever
|
||||
started on a charter it will not hold.
|
||||
|
||||
Two things were deliberately kept. The old `acquired` fallback and its WARN stay, because a
|
||||
reservation narrows the window and claiming it removes the window is the mistake to avoid. And a
|
||||
failed launch must return its reservation, or the pool shrinks silently with every failure — that is
|
||||
the way this shape usually goes wrong, so it is the case the tests pin.
|
||||
|
||||
**The gotcha: a full architect pool now fails your spawn instead of demoting it.** `fleet_spawn{role:
|
||||
"architect"}` used to hand back a plain `dev` when every slot was taken. It now throws. That is the
|
||||
point — a refusal is recoverable and a mismatched charter is not — but it is a visible behaviour
|
||||
change for anyone who relied on the old silence. The refusal stays scoped to `architect` alone: dev
|
||||
and reviewer pools are placement candidates, not identity bindings, so an explicit profile outside
|
||||
them stays a supported override.
|
||||
|
||||
### A note for anyone briefing a worker to read this page
|
||||
|
||||
|
||||
Reference in New Issue
Block a user