fleetd #267: warn once per profile when the opencode model check cannot run #271

Closed
agent wants to merge 0 commits from worker/fleetd-267-model-check-bd8068-1 into main
Member

fleetd #267 — the #175 model-mismatch check never ran for most opencode spawns### What was wrongOpenCodeLauncher.SessionAwareHandle.agentSessionId() is the only caller of checkModelMatch(...), and it sits right after the fleetd #249 worktree gate:javaif (!worktreeProvisioned) { return null; // checkModelMatch never reached}So a fleet_spawn without worktree:true — the ticket calls this "the ordinary, expected shape of the large majority of spawns" — never had its model checked at all, and the gap was completely silent (no log line).### Approach taken: WARN, not decoupleI looked hard at decoupling the model check from agentSessionId()'s resolved id, since the ticket's "shape" section explicitly invites that as the preferred fix. It is not safe here, and I want to say plainly why rather than just asserting it.OpenCodeSessionDiscovery has exactly one way to find "the model actually running": sessionIdForDirectory(directory), which picks the row with the highest time_updated for that directory. That is the SAME heuristic the fleetd #249 gate exists to distrust — when the cwd is shared (no worktree), it can and does return a sibling session's row instead of this member's own (fleetd #234 measured this: a three-day-old row from a different profile). actualModelForSessionId is deliberately keyed on the RESOLVED id for exactly this reason (see its javadoc) — re-deriving a second, independent answer from directory for the model check alone would reintroduce precisely the false-positive risk fleetd #234 already fixed for the id: a sibling profile running a different, correctly-configured model would look like this profile's mismatch, and fleetd would quarantine an innocent credential. The ticket is explicit that a false positive is the worst outcome here ("that is the single most important safety rule").Since nothing else in opencode's session table (no terminal/pid column, nothing) can disambiguate a shared directory, the model genuinely cannot be determined without a provisioned worktree. So I took the ticket's explicitly-sanctioned fallback: log once per profile, at WARN, naming the profile — the exact treatment discoveryUnavailable already gets a few lines above in the same method. A logged UNKNOWN replaces the old total silence.### What changed- OpenCodeLauncher.java: added a per-profile (not per-launcher-instance, since several profiles can each hit this independently) Set<String> modelCheckSkippedWarned, threaded through to SessionAwareHandle. When !worktreeProvisioned and the profile has a configured model, log a WARN once naming the profile and pointing at worktree:true as the fix. The fleetd #249 gate itself is untouched — agentSessionId() still returns null in this case, exactly as before.- OpenCodeLauncherTest.java: two new tests, both driven through SessionManager.acquire()/get() (fleetd #209's real late-resolve path), not through checkModelMatch or a handle built in isolation: - aProvisionedWorktreeSpawnRunsTheModelCheckThroughSessionManagerAndNeverLogsTheSkipWarn — the WITH-worktree case: check still fires and quarantines, and the new WARN never fires (regression guard for the new code). - aSpawnWithoutAProvisionedWorktreeNeverRunsTheModelCheckButWarnsOncePerProfile — the WITHOUT-worktree case (the bug): a plausible-mismatch row exists for the shared directory, nothing is quarantined (fleetd #234 safety preserved), and the new WARN fires exactly once across acquire() + two get() re-polls, naming the profile.### Mutation proofStashed only the production change (OpenCodeLauncher.java), kept the new tests, and reran:mvn test -Dtest=OpenCodeLauncherTest#aProvisionedWorktreeSpawnRunsTheModelCheckThroughSessionManagerAndNeverLogsTheSkipWarn+aSpawnWithoutAProvisionedWorktreeNeverRunsTheModelCheckButWarnsOncePerProfileResult: RED, as expected —org.opentest4j.AssertionFailedError: exactly one 'cannot run' WARN across acquire() + two get() re-polls — the old code logged NOTHING here, which is the bug this ticket fixes; got: [] ==> expected: <1> but was: <0>[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0[INFO] BUILD FAILURERestored the production file (git stash pop) and reran OpenCodeLauncherTest — 54/54 green. git diff --exit-code confirmed the working tree matched what was committed (only the two intended files, no stray changes from the stash cycle).### Buildcd fleetd && mvn clean install, full unpiped output read:[INFO] Tests run: 1271, Failures: 0, Errors: 0, Skipped: 0[INFO] BUILD SUCCESS[INFO] Total time: 42.235 s(OpenCodeLauncherTest alone: Tests run: 54, Failures: 0, Errors: 0, Skipped: 0.)### NotePer the ticket, out of scope and not touched: making worktree:true the default for opencode spawns, and the live fleetd.yaml config (gitignored, not visible from this worktree — nothing here assumes a specific profile shape).Ticket: #267

## fleetd #267 — the #175 model-mismatch check never ran for most opencode spawns### What was wrong`OpenCodeLauncher.SessionAwareHandle.agentSessionId()` is the only caller of `checkModelMatch(...)`, and it sits right after the fleetd #249 worktree gate:```javaif (!worktreeProvisioned) { return null; // checkModelMatch never reached}```So a `fleet_spawn` without `worktree:true` — the ticket calls this "the ordinary, expected shape of the large majority of spawns" — never had its model checked at all, and the gap was completely silent (no log line).### Approach taken: WARN, not decoupleI looked hard at decoupling the model check from `agentSessionId()`'s resolved id, since the ticket's "shape" section explicitly invites that as the preferred fix. It is not safe here, and I want to say plainly why rather than just asserting it.`OpenCodeSessionDiscovery` has exactly one way to find "the model actually running": `sessionIdForDirectory(directory)`, which picks the row with the highest `time_updated` for that directory. That is the SAME heuristic the fleetd #249 gate exists to distrust — when the cwd is shared (no worktree), it can and does return a sibling session's row instead of this member's own (fleetd #234 measured this: a three-day-old row from a different profile). `actualModelForSessionId` is deliberately keyed on the RESOLVED id for exactly this reason (see its javadoc) — re-deriving a second, independent answer from `directory` for the model check alone would reintroduce precisely the false-positive risk fleetd #234 already fixed for the id: a sibling profile running a *different, correctly-configured* model would look like *this* profile's mismatch, and fleetd would quarantine an innocent credential. The ticket is explicit that a false positive is the worst outcome here ("that is the single most important safety rule").Since nothing else in opencode's session table (no terminal/pid column, nothing) can disambiguate a shared directory, the model genuinely cannot be determined without a provisioned worktree. So I took the ticket's explicitly-sanctioned fallback: log once per profile, at WARN, naming the profile — the exact treatment `discoveryUnavailable` already gets a few lines above in the same method. A logged UNKNOWN replaces the old total silence.### What changed- `OpenCodeLauncher.java`: added a per-profile (not per-launcher-instance, since several profiles can each hit this independently) `Set<String> modelCheckSkippedWarned`, threaded through to `SessionAwareHandle`. When `!worktreeProvisioned` and the profile has a configured model, log a WARN once naming the profile and pointing at `worktree:true` as the fix. The fleetd #249 gate itself is untouched — `agentSessionId()` still returns `null` in this case, exactly as before.- `OpenCodeLauncherTest.java`: two new tests, both driven through `SessionManager.acquire()`/`get()` (fleetd #209's real late-resolve path), not through `checkModelMatch` or a handle built in isolation: - `aProvisionedWorktreeSpawnRunsTheModelCheckThroughSessionManagerAndNeverLogsTheSkipWarn` — the WITH-worktree case: check still fires and quarantines, and the new WARN never fires (regression guard for the new code). - `aSpawnWithoutAProvisionedWorktreeNeverRunsTheModelCheckButWarnsOncePerProfile` — the WITHOUT-worktree case (the bug): a plausible-mismatch row exists for the shared directory, nothing is quarantined (fleetd #234 safety preserved), and the new WARN fires exactly once across `acquire()` + two `get()` re-polls, naming the profile.### Mutation proofStashed only the production change (`OpenCodeLauncher.java`), kept the new tests, and reran:```mvn test -Dtest=OpenCodeLauncherTest#aProvisionedWorktreeSpawnRunsTheModelCheckThroughSessionManagerAndNeverLogsTheSkipWarn+aSpawnWithoutAProvisionedWorktreeNeverRunsTheModelCheckButWarnsOncePerProfile```Result: RED, as expected —```org.opentest4j.AssertionFailedError: exactly one 'cannot run' WARN across acquire() + two get() re-polls — the old code logged NOTHING here, which is the bug this ticket fixes; got: [] ==> expected: <1> but was: <0>[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0[INFO] BUILD FAILURE```Restored the production file (`git stash pop`) and reran `OpenCodeLauncherTest` — 54/54 green. `git diff --exit-code` confirmed the working tree matched what was committed (only the two intended files, no stray changes from the stash cycle).### Build`cd fleetd && mvn clean install`, full unpiped output read:```[INFO] Tests run: 1271, Failures: 0, Errors: 0, Skipped: 0[INFO] BUILD SUCCESS[INFO] Total time: 42.235 s```(`OpenCodeLauncherTest` alone: `Tests run: 54, Failures: 0, Errors: 0, Skipped: 0`.)### NotePer the ticket, out of scope and not touched: making `worktree:true` the default for opencode spawns, and the live `fleetd.yaml` config (gitignored, not visible from this worktree — nothing here assumes a specific profile shape).Ticket: https://git.ltms.dev/fleet/fleetd/issues/267
agent added 1 commit 2026-09-04 03:36:51 +02:00
fleetd #267: warn once per profile when the model check can't run
CI / contract (pull_request) Successful in 1m6s
CI / build (pull_request) Successful in 1m50s
e028a0ae54
OpenCodeLauncher.SessionAwareHandle.agentSessionId() is the only caller of
checkModelMatch (fleetd #175), and it sits behind the fleetd #249 worktree
gate. A spawn with no worktree:true — the ordinary shape of most opencode
spawns — never reached the check at all, and the gap was totally silent.

The check cannot be decoupled from agentSessionId()'s resolved id: doing so
would re-derive 'whatever is newest in the shared directory' and reintroduce
the false-positive risk fleetd #234 fixed (a sibling's differently-configured
model looking like a mismatch for a profile that never actually ran it). The
#249 gate is correct and stays as-is.

Instead, log once per profile at WARN, naming the profile, the same
treatment discoveryUnavailable already gets a few lines above — a logged
UNKNOWN beats a check that silently never runs.
ltms closed this pull request 2026-09-04 03:49:41 +02:00
Some checks are pending
CI / contract (pull_request) Successful in 1m6s
CI / build (pull_request) Successful in 1m50s

Pull request closed

Sign in to join this conversation.