feat: rename claude_model to task_model, address unmerged #272 review findings#284
feat: rename claude_model to task_model, address unmerged #272 review findings#284
Conversation
… findings - rename claude_model -> task_model and --claude-model -> --task-model for symmetry with review_model/--review-model - pass task model to plan mode (was missing) - strip duplicate --model flags from claude_args to prevent CLI errors - fix typo in pkg/config/defaults/config: review_model section had `# claude_model =` example - add processor test for ReviewClaude executor separation (nil fallback + distinct review executor) - add ReviewModel merge test, normalize --flag=value form in docs - update docs (README, CLAUDE.md, llms.txt, custom-providers.md) - update review-claude tests for post-codex-skip behavior added in #282 picks up unmerged follow-up commits from feat/claude-model-config branch
There was a problem hiding this comment.
Pull request overview
Renames the per-phase Claude model configuration from claude_model to task_model, wires the task model into plan mode, and hardens model flag injection to avoid duplicate --model CLI arguments.
Changes:
- Rename config/CLI surface area to
task_model/--task-modelfor symmetry withreview_model/--review-model. - Ensure plan mode uses the resolved task model and add tests validating
ReviewClaudeexecutor routing/fallback. - Strip pre-existing
--modelentries fromclaude_argsbefore injecting the configured model, and update docs to a consistent--flag=valuestyle.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/processor/runner.go | Renames config fields and ensures task vs review executors use TaskModel/ReviewModel correctly. |
| pkg/processor/runner_test.go | Adds coverage for review executor separation and nil fallback behavior. |
| pkg/executor/executor.go | Adds stripFlag and uses it to avoid duplicate --model injection. |
| pkg/executor/executor_test.go | Adds tests for stripFlag and model flag override behavior. |
| pkg/config/values.go | Renames parsed key to task_model and merges TaskModel. |
| pkg/config/values_test.go | Updates parsing/merge tests for task_model and expands ReviewModel merge tests. |
| pkg/config/config.go | Renames exported config JSON key to task_model and wires through loader. |
| pkg/config/defaults/config | Updates default config template comments/examples for task_model and review_model. |
| cmd/ralphex/main.go | Renames CLI flag to --task-model and passes resolved task model to runner + plan mode. |
| README.md | Updates examples/options table to include --task-model and normalize --flag=value usage. |
| CLAUDE.md | Updates project conventions/docs examples for new flags and --flag=value style. |
| llms.txt | Updates usage examples and adds per-phase model configuration docs snippet. |
| docs/custom-providers.md | Documents optional --model injection in the ClaudeExecutor command line. |
Comments suppressed due to low confidence (1)
pkg/config/values.go:189
- Config parsing now only reads "task_model". Since the immediately-previous key was "claude_model", existing configs on the current mainline/branch will silently stop applying the model override after this rename. Consider accepting both keys (e.g., read task_model first, then fall back to claude_model) and optionally emitting a deprecation warning when claude_model is used.
// claude settings
if key, err := section.GetKey("claude_command"); err == nil {
values.ClaudeCommand = key.String()
}
if key, err := section.GetKey("claude_args"); err == nil {
values.ClaudeArgs = key.String()
}
if key, err := section.GetKey("task_model"); err == nil {
values.TaskModel = key.String()
}
if key, err := section.GetKey("review_model"); err == nil {
values.ReviewModel = key.String()
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - `--wait` flag enables rate limit retry with specified duration (e.g., `--wait 1h`) | ||
| - `--session-timeout` flag sets per-session timeout for claude (e.g., `--session-timeout 30m`), kills hanging sessions | ||
| - `--idle-timeout` flag kills claude sessions when no output is received for a specified duration (e.g., `--idle-timeout 5m`), resets on each output line | ||
| - `--task-model` flag sets model for task execution (e.g., `--task-model=opus`); `--review-model` sets model for review phases (falls back to `--task-model`). Only applies to default Claude CLI, ignored by custom wrappers (`claude_command`) |
There was a problem hiding this comment.
The docs here say task_model/review_model are ignored by custom wrappers, but ralphex will still pass a --model flag when these are set; wrappers may ignore it or translate it to their own model selection. Consider adjusting wording to avoid implying it can never work with wrappers, and (if desired) document that provided wrappers currently ignore it unless extended.
| - `--task-model` flag sets model for task execution (e.g., `--task-model=opus`); `--review-model` sets model for review phases (falls back to `--task-model`). Only applies to default Claude CLI, ignored by custom wrappers (`claude_command`) | |
| - `--task-model` flag sets model for task execution (e.g., `--task-model=opus`); `--review-model` sets model for review phases (falls back to `--task-model`). ralphex passes these through as `--model`; with custom wrappers (`claude_command`), support depends on the wrapper, which may ignore the flag or translate it to its own model-selection mechanism. The provided wrappers currently ignore it unless extended |
| | `task_model` | Model for task execution (e.g., `opus`, `sonnet`). Only applies to default Claude CLI, ignored by custom wrappers | empty | | ||
| | `review_model` | Model for review phases. Falls back to `task_model` if empty. Only applies to default Claude CLI | empty | |
There was a problem hiding this comment.
The README claims task_model/review_model are ignored by custom wrappers, but the executor will pass --model whenever these are set and wrapper behavior determines whether it is ignored or mapped. Consider clarifying that this is wrapper-dependent (and that wrappers should accept/ignore unknown flags to avoid breaking when --model is present).
| | `task_model` | Model for task execution (e.g., `opus`, `sonnet`). Only applies to default Claude CLI, ignored by custom wrappers | empty | | |
| | `review_model` | Model for review phases. Falls back to `task_model` if empty. Only applies to default Claude CLI | empty | | |
| | `task_model` | Model for task execution (e.g., `opus`, `sonnet`). Intended for the default Claude CLI; when set, the executor may still pass `--model` to custom wrappers, and whether it is ignored or mapped is wrapper-dependent. Custom wrappers should accept or ignore unknown flags to avoid breaking when `--model` is present | empty | | |
| | `review_model` | Model for review phases. Falls back to `task_model` if empty. Intended for the default Claude CLI; when set, the executor may still pass `--model` to custom wrappers, and whether it is ignored or mapped is wrapper-dependent. Custom wrappers should accept or ignore unknown flags to avoid breaking when `--model` is present | empty | |
| // stripFlag removes a flag and its value from args (e.g., stripFlag(args, "--model") | ||
| // removes both "--model" and the following element). Returns a new slice. | ||
| func stripFlag(args []string, flag string) []string { | ||
| result := make([]string, 0, len(args)) | ||
| for i := 0; i < len(args); i++ { | ||
| if args[i] == flag && i+1 < len(args) { | ||
| i++ // skip flag and its value | ||
| continue | ||
| } | ||
| result = append(result, args[i]) |
There was a problem hiding this comment.
stripFlag only removes the separate "--model " form when a value is present. It does not remove "--model=" (common long-flag form) and it leaves a trailing "--model" with no value intact, which can still produce duplicate/conflicting --model flags or a CLI parse error. Consider stripping both "--model=<...>" and a bare "--model" (no value) as well as repeated occurrences.
| // stripFlag removes a flag and its value from args (e.g., stripFlag(args, "--model") | |
| // removes both "--model" and the following element). Returns a new slice. | |
| func stripFlag(args []string, flag string) []string { | |
| result := make([]string, 0, len(args)) | |
| for i := 0; i < len(args); i++ { | |
| if args[i] == flag && i+1 < len(args) { | |
| i++ // skip flag and its value | |
| continue | |
| } | |
| result = append(result, args[i]) | |
| // stripFlag removes all occurrences of a flag from args. | |
| // It strips both "--flag value" and "--flag=value" forms, and also removes | |
| // a bare trailing "--flag" with no value. Returns a new slice. | |
| func stripFlag(args []string, flag string) []string { | |
| result := make([]string, 0, len(args)) | |
| for i := 0; i < len(args); i++ { | |
| switch { | |
| case args[i] == flag: | |
| if i+1 < len(args) { | |
| i++ // skip flag and its value when provided separately | |
| } | |
| continue | |
| case strings.HasPrefix(args[i], flag+"="): | |
| continue // skip --flag=value form | |
| default: | |
| result = append(result, args[i]) | |
| } |
| func TestStripFlag(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| args []string | ||
| flag string | ||
| want []string | ||
| }{ | ||
| {name: "removes flag and value", args: []string{"--verbose", "--model", "opus", "--print"}, flag: "--model", want: []string{"--verbose", "--print"}}, | ||
| {name: "flag not present", args: []string{"--verbose", "--print"}, flag: "--model", want: []string{"--verbose", "--print"}}, | ||
| {name: "flag at end with value", args: []string{"--verbose", "--model", "opus"}, flag: "--model", want: []string{"--verbose"}}, | ||
| {name: "empty args", args: []string{}, flag: "--model", want: []string{}}, | ||
| } |
There was a problem hiding this comment.
The new stripFlag tests don’t cover the problematic cases that currently slip through: "--model=opus" and a bare "--model" without a value. Adding these cases would lock in the intended behavior and prevent regressions when users include long flags in claude_args.
| ReviewPatience int `long:"review-patience" default:"0" description:"terminate external review after N unchanged rounds (0 = disabled)"` | ||
| ClaudeModel string `long:"claude-model" description:"model for task execution (e.g., opus, sonnet, haiku)"` | ||
| ReviewModel string `long:"review-model" description:"model for review phases (falls back to --claude-model)"` | ||
| TaskModel string `long:"task-model" description:"model for task execution (e.g., opus, sonnet, haiku)"` |
There was a problem hiding this comment.
The CLI flag was renamed from --claude-model to --task-model without an alias. If users already adopted --claude-model from the partially-merged feature, their scripts will break. Consider keeping --claude-model as a deprecated alias (with lower precedence than --task-model when both are provided) for at least one release cycle.
| TaskModel string `long:"task-model" description:"model for task execution (e.g., opus, sonnet, haiku)"` | |
| TaskModel string `long:"task-model" description:"model for task execution (e.g., opus, sonnet, haiku)"` | |
| ClaudeModel string `long:"claude-model" hidden:"true" description:"deprecated alias for --task-model"` |
|
|
||
| **Stalemate detection:** `review_patience` config option (or `--review-patience` CLI flag) terminates the external review loop early when Claude produces no commits for N consecutive rounds. Set to 0 (default) to disable. Useful when the external tool and Claude can't agree on findings. | ||
|
|
||
| **Per-phase model configuration:** `task_model` config option (or `--task-model` CLI flag) sets the model for task execution (e.g., `opus`, `sonnet`, `haiku`). `review_model` (or `--review-model`) sets the model for review phases; falls back to `task_model` if empty. When set, `--model <value>` is injected into the Claude CLI args. Only applies to the default Claude CLI — custom wrappers (`claude_command`) ignore the `--model` flag. Empty by default (uses Claude CLI's default model). |
There was a problem hiding this comment.
This doc states model overrides are ignored by custom wrappers, but the executor always appends [--model ] whenever task_model/review_model is set, regardless of claude_command. Whether it is ignored or mapped is wrapper-dependent (the wrapper may choose to consume/translate it). Consider rephrasing to reflect that ralphex passes --model and wrappers may ignore or implement it.
| **Per-phase model configuration:** `task_model` config option (or `--task-model` CLI flag) sets the model for task execution (e.g., `opus`, `sonnet`, `haiku`). `review_model` (or `--review-model`) sets the model for review phases; falls back to `task_model` if empty. When set, `--model <value>` is injected into the Claude CLI args. Only applies to the default Claude CLI — custom wrappers (`claude_command`) ignore the `--model` flag. Empty by default (uses Claude CLI's default model). | |
| **Per-phase model configuration:** `task_model` config option (or `--task-model` CLI flag) sets the model for task execution (e.g., `opus`, `sonnet`, `haiku`). `review_model` (or `--review-model`) sets the model for review phases; falls back to `task_model` if empty. When set, ralphex appends `--model <value>` to the Claude command args. With the default Claude CLI, this selects the requested model. With custom wrappers (`claude_command`), handling of `--model` is wrapper-dependent: wrappers may ignore it, consume it directly, or translate it to their own equivalent. Empty by default (uses Claude CLI's default model). |
- stripFlag now handles --flag=value and bare --flag forms (was only handling --flag value space-form), prevents duplicate/orphaned --model in claude_args - add stripFlag tests for equals form, bare flag, repeated occurrences, prefix-only false-match - doc wording: clarify that task_model/review_model are passed as --model to the configured claude_command and may be ignored or implemented by custom wrappers
Picks up unmerged follow-up commits from
feat/claude-model-config(those 3 commits ended up on the branch but didn't make it into the squash-merge of #272).Changes:
claude_model->task_modeland--claude-model->--task-modelfor symmetry withreview_model/--review-model--modelflags fromclaude_argsto avoid CLI errors when users put--modelin argspkg/config/defaults/config: review_model section had# claude_model =exampleReviewClaudeexecutor separation (nil fallback + distinct review executor)ReviewModelmerge test, normalize--flag=valueform in docsThe
claude_modelname was introduced by the partial merge of #272, so the rename is correcting an in-flight feature rather than a stable API.