fleetd#219: fix OpenCodeLauncher config/discovery roots under memberHerdrSocket #221

Merged
ltms merged 1 commits from worker/cb-219-opencode-roots-1f677e-1 into main 2026-09-01 10:33:05 +02:00
Member

Fixes fleetd#219 — OpenCodeLauncher decided both the ephemeral opencode.json config directory and opencode session discovery from fleetd's own filesystem (java.io.tmpdir / user.home), which breaks the moment memberHerdrSocket puts member panes under a different OS user (the #213 defect, twice).

Site 1 (config root, OpenCodeLauncher#writeConfig / #configParentDir): under memberHerdrSocket, the directory now goes under worktreeRoot and is shared read-only with worktreeGroup via EnvAllowListScrub#shareWithGroup (widened to package-private and generalized — the exact mechanism #213 built for the ZDOTDIR scrub, reused rather than duplicated). Unlike the ZDOTDIR scrub's degrade-to-overlay fallback, a missing worktreeRoot/worktreeGroup here REFUSES the spawn (IllegalStateException) instead of degrading — this config file is the member's only way to learn where the bridge MCP is, so writing it somewhere unreadable would just produce an undeliverable member with no signal pointing at the cause. memberHerdrSocket absent is byte-identical to before (config==null and a live-but-unset config supplier both verified).

Site 2 (discovery root, OpenCodeLauncher#defaultDiscoveryRoot / SessionAwareHandle#agentSessionId): under memberHerdrSocket, agentSessionId() now declares session discovery unavailable and logs one WARN per launcher instance instead of silently scanning fleetd's own /Users/dai.ha (opencode.db actually lives under the MEMBER's home). This is a decision, not a patch — full reasoning (three options weighed, why 'declare unavailable' was picked over a new config key or deriving the home) is in defaultDiscoveryRoot()'s javadoc.

Shared plumbing: HerdrPeerLauncher#memberHerdrSocketConfigured/memberScrubParentDir/memberGroup widened from private to package-private so OpenCodeLauncher reuses the exact same config resolution instead of re-deriving it.

Same-shape finding, NOT fixed (out of scope): ClaudeCodeLauncher#writeCharterFile (~line 465) writes the role-charter temp file via Files.createTempFile with no directory argument, i.e. under java.io.tmpdir — the same site-1 shape, unaddressed for the Claude Code adapter. Left for its own ticket per the brief.

Tests: 5 new tests in OpenCodeLauncherTest (site 1 success/2 refusal variants/byte-identical-absent, site 2 discovery-gated). All 4 behavior-asserting tests were verified red before the fix (reverted OpenCodeLauncher.java only, kept the test file): wrong parent dir used, IllegalStateException not thrown (x2), and agentSessionId() returned the hidden record instead of null. The byte-identical-absent test passes in both states, as expected for a regression guard.

Build: mvn clean install from fleetd/ — BUILD SUCCESS, Tests run: 1086, Failures: 0, Errors: 0, Skipped: 0.

Fixes fleetd#219 — OpenCodeLauncher decided both the ephemeral opencode.json config directory and opencode session discovery from fleetd's own filesystem (java.io.tmpdir / user.home), which breaks the moment memberHerdrSocket puts member panes under a different OS user (the #213 defect, twice). **Site 1 (config root, OpenCodeLauncher#writeConfig / #configParentDir):** under memberHerdrSocket, the directory now goes under worktreeRoot and is shared read-only with worktreeGroup via EnvAllowListScrub#shareWithGroup (widened to package-private and generalized — the exact mechanism #213 built for the ZDOTDIR scrub, reused rather than duplicated). Unlike the ZDOTDIR scrub's degrade-to-overlay fallback, a missing worktreeRoot/worktreeGroup here REFUSES the spawn (IllegalStateException) instead of degrading — this config file is the member's only way to learn where the bridge MCP is, so writing it somewhere unreadable would just produce an undeliverable member with no signal pointing at the cause. memberHerdrSocket absent is byte-identical to before (config==null and a live-but-unset config supplier both verified). **Site 2 (discovery root, OpenCodeLauncher#defaultDiscoveryRoot / SessionAwareHandle#agentSessionId):** under memberHerdrSocket, agentSessionId() now declares session discovery unavailable and logs one WARN per launcher instance instead of silently scanning fleetd's own /Users/dai.ha (opencode.db actually lives under the MEMBER's home). This is a decision, not a patch — full reasoning (three options weighed, why 'declare unavailable' was picked over a new config key or deriving the home) is in defaultDiscoveryRoot()'s javadoc. **Shared plumbing:** HerdrPeerLauncher#memberHerdrSocketConfigured/memberScrubParentDir/memberGroup widened from private to package-private so OpenCodeLauncher reuses the exact same config resolution instead of re-deriving it. **Same-shape finding, NOT fixed (out of scope):** ClaudeCodeLauncher#writeCharterFile (~line 465) writes the role-charter temp file via Files.createTempFile with no directory argument, i.e. under java.io.tmpdir — the same site-1 shape, unaddressed for the Claude Code adapter. Left for its own ticket per the brief. **Tests:** 5 new tests in OpenCodeLauncherTest (site 1 success/2 refusal variants/byte-identical-absent, site 2 discovery-gated). All 4 behavior-asserting tests were verified red before the fix (reverted OpenCodeLauncher.java only, kept the test file): wrong parent dir used, IllegalStateException not thrown (x2), and agentSessionId() returned the hidden record instead of null. The byte-identical-absent test passes in both states, as expected for a regression guard. **Build:** `mvn clean install` from fleetd/ — BUILD SUCCESS, Tests run: 1086, Failures: 0, Errors: 0, Skipped: 0.
agent added 1 commit 2026-09-01 09:39:19 +02:00
fleetd#219: OpenCodeLauncher config/discovery roots must not assume fleetd's own filesystem
CI / contract (pull_request) Successful in 44s
CI / build (pull_request) Successful in 1m48s
d9168de43e
Site 1 (config root): under memberHerdrSocket, writeConfig() now places the
ephemeral opencode.json directory under worktreeRoot and shares it read-only
with worktreeGroup, reusing EnvAllowListScrub#shareWithGroup (widened to
package-private and generalized) — the same mechanism #213 built for the
ZDOTDIR scrub, rather than a second copy. Unlike the ZDOTDIR scrub's
degrade-to-overlay fallback, a missing worktreeRoot/worktreeGroup here
REFUSES the spawn (IllegalStateException from buildLaunch): this file is the
member's only way to learn where the bridge MCP is, so writing it somewhere
unreadable would just produce an undeliverable member with no signal
pointing at the cause. memberHerdrSocket absent stays byte-identical.

Site 2 (discovery root): under memberHerdrSocket, agentSessionId() now
declares session discovery unavailable and logs one WARN per launcher
instance instead of silently scanning fleetd's own $HOME (opencode.db lives
under the MEMBER's home under this config key). Decision + reasoning for why
this is a declare-unavailable rather than a new config key is in
defaultDiscoveryRoot()'s javadoc.

Widened HerdrPeerLauncher#memberHerdrSocketConfigured/memberScrubParentDir/
memberGroup to package-private so OpenCodeLauncher reuses the exact same
config resolution rather than re-deriving it.

Same-shape finding (not fixed, out of scope): ClaudeCodeLauncher#writeCharterFile
(line ~465) writes the role-charter temp file via Files.createTempFile with no
directory argument, i.e. under java.io.tmpdir — the same site-1 shape, unfixed
for the Claude Code adapter.
ltms merged commit f9d2ee2a2b into main 2026-09-01 10:33:05 +02:00
Sign in to join this conversation.