From 8b790dfcd8c63ac23cf1dfda501d43bc008dc662 Mon Sep 17 00:00:00 2001 From: Jiang Bohan Date: Tue, 19 May 2026 18:00:43 +0800 Subject: [PATCH] fix(daemon): guard runTask with per-model thinking_level validator (MUL-2339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ValidateThinkingLevel existed but had no call site — `task.Agent. ThinkingLevel` flowed straight into ExecOptions, so `xhigh` configured on a non-Opus Claude model, or API-side stale values that escaped the provider enum gate, would be injected anyway. Run the validator before building ExecOptions. Invalid combinations log a warning and drop the level instead of failing the task: the agent still runs, just at the runtime's default reasoning effort. Discovery errors fail open (keep the level, let the CLI surface any objection) so a transient `claude --help` failure can't strand work. Empty model is forwarded as-is; the validator resolves it to the provider's default model internally per the cross-package contract. Co-authored-by: multica-agent --- server/internal/daemon/daemon.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/server/internal/daemon/daemon.go b/server/internal/daemon/daemon.go index 010bd3027..5b0e79959 100644 --- a/server/internal/daemon/daemon.go +++ b/server/internal/daemon/daemon.go @@ -2439,6 +2439,34 @@ func (d *Daemon) runTask(ctx context.Context, task Task, provider string, slot i if task.Agent != nil { thinkingLevel = task.Agent.ThinkingLevel } + // Per-model guard: the server validates the literal token against the + // provider's enum, but per-model gaps (Claude's `xhigh` on a non-Opus + // model, Codex's per-model `supported_reasoning_levels`) only resolve + // here, against the daemon's local CLI catalog. Invalid combinations + // log a warning and drop the level rather than failing the task, so a + // stale persisted value never blocks execution. Empty model is passed + // through unchanged — ValidateThinkingLevel resolves it to the + // provider's default model internally so default-model tasks aren't + // misjudged. Discovery errors fail open: if we can't list models, we + // keep the persisted level and let the CLI surface any objection. + if thinkingLevel != "" { + ok, err := agent.ValidateThinkingLevel(ctx, provider, entry.Path, model, thinkingLevel) + if err != nil { + taskLog.Warn("thinking_level: catalog lookup failed; passing through", + "provider", provider, + "model", model, + "thinking_level", thinkingLevel, + "error", err, + ) + } else if !ok { + taskLog.Warn("thinking_level: not valid for this (provider, model); skipping injection", + "provider", provider, + "model", model, + "thinking_level", thinkingLevel, + ) + thinkingLevel = "" + } + } execOpts := agent.ExecOptions{ Cwd: env.WorkDir, Model: model,