From a32457b5bc0ca641ac17c44e3bf9f75dd07200e5 Mon Sep 17 00:00:00 2001 From: Mathias Bergqvist Date: Mon, 20 Apr 2026 08:55:03 +0200 Subject: [PATCH] feat(exec): pass --model flag to claude subprocess for cloud-tier dispatch --- internal/exec/executor.go | 5 +++- internal/exec/executor_test.go | 55 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/internal/exec/executor.go b/internal/exec/executor.go index eed6474..cbc7e7b 100644 --- a/internal/exec/executor.go +++ b/internal/exec/executor.go @@ -72,8 +72,11 @@ func (e *Executor) Run(ctx context.Context, req Request) (Result, error) { "--tools", tools, "--json-schema", Schema, "--output-format", "json", - prompt, } + if strings.HasPrefix(req.Model, "claude-") { + args = append(args, "--model", req.Model) + } + args = append(args, prompt) cmd := exec.CommandContext(ctx, e.cfg.ClaudeBinary, args...) cmd.Env = append(os.Environ(), "LITELLM_API_KEY="+e.cfg.LiteLLMAPIKey) diff --git a/internal/exec/executor_test.go b/internal/exec/executor_test.go index 0b9a25e..6610573 100644 --- a/internal/exec/executor_test.go +++ b/internal/exec/executor_test.go @@ -75,3 +75,58 @@ func TestExecutorTimesOut(t *testing.T) { _, err := ex.Run(context.Background(), iexec.Request{TaskPrompt: "slow"}) assert.ErrorContains(t, err, "timeout") } + +func TestExecutorPassesModelFlagForCloudModel(t *testing.T) { + // The script captures its args to a temp file so we can assert --model was passed. + argsFile := filepath.Join(t.TempDir(), "args.txt") + envelope := `{"type":"result","subtype":"success","is_error":false,"structured_output":{"status":"pass","phase":"review","skill":"review","file_path":"","runner_output":"","verified":true,"model_used":"claude-sonnet-4-6","message":"ok"}}` + + dir := t.TempDir() + script := filepath.Join(dir, "claude") + content := "#!/bin/sh\necho \"$@\" > " + argsFile + "\necho '" + envelope + "'\n" + require.NoError(t, os.WriteFile(script, []byte(content), 0755)) + + ex := iexec.New(iexec.Config{ + ClaudeBinary: script, + SystemPrompt: "sys", + Timeout: 5 * time.Second, + }) + + _, err := ex.Run(context.Background(), iexec.Request{ + SkillPrompt: "review rules", + TaskPrompt: "do review", + Model: "claude-sonnet-4-6", + }) + require.NoError(t, err) + + argsData, err := os.ReadFile(argsFile) + require.NoError(t, err) + assert.Contains(t, string(argsData), "--model claude-sonnet-4-6") +} + +func TestExecutorSkipsModelFlagForLocalModel(t *testing.T) { + argsFile := filepath.Join(t.TempDir(), "args.txt") + envelope := `{"type":"result","subtype":"success","is_error":false,"structured_output":{"status":"pass","phase":"review","skill":"review","file_path":"","runner_output":"","verified":true,"model_used":"ollama/devstral","message":"ok"}}` + + dir := t.TempDir() + script := filepath.Join(dir, "claude") + content := "#!/bin/sh\necho \"$@\" > " + argsFile + "\necho '" + envelope + "'\n" + require.NoError(t, os.WriteFile(script, []byte(content), 0755)) + + ex := iexec.New(iexec.Config{ + ClaudeBinary: script, + SystemPrompt: "sys", + Timeout: 5 * time.Second, + }) + + _, err := ex.Run(context.Background(), iexec.Request{ + SkillPrompt: "review rules", + TaskPrompt: "do review", + Model: "ollama/devstral", + }) + require.NoError(t, err) + + argsData, err := os.ReadFile(argsFile) + require.NoError(t, err) + assert.NotContains(t, string(argsData), "--model") +}