Repository navigation
feat: add per task prop selection - #109
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
WalkthroughAdds per-task runtime overrides (IDE, Model, ReasoningEffort) configurable via CLI flag, interactive form, workspace config, and extension hook; resolves and applies these overrides during planning and execution with guards against post-resolution mutation and UI/event propagation. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI
participant Form
participant Planner
participant HookExt
participant Executor
participant Agent
User->>CLI: compozy start --task-runtime type=frontend,model=gpt-5.4
CLI->>Form: optionally run collectStartTaskRuntimeForm
Form->>CLI: return executionTaskRuntimeRules
CLI->>Planner: prepare workflow + task-runtime rules
Planner->>HookExt: dispatch plan.pre_resolve_task_runtime (per task)
HookExt-->>Planner: optional TaskRuntimePatch
Planner->>Planner: RuntimeForTask(target) (type then id)
Planner->>Executor: hand off prepared jobs with resolved IDE/Model/ReasoningEffort
Executor->>Executor: snapshot prepared state
Executor->>HookExt: dispatch run.pre_start (guarded)
HookExt-->>Executor: possibly mutate (rejected if runtime changed)
Executor->>Agent: createACPClient(job) using per-job runtime
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (3)
internal/cli/task_runtime_flag.go (1)
141-144: Consider simplifying thestringPointerhelper.The intermediate variable
clonedis unnecessary sincevalueis already a copy (string is passed by value in Go).♻️ Suggested simplification
func stringPointer(value string) *string { - cloned := value - return &cloned + return &value }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cli/task_runtime_flag.go` around lines 141 - 144, The helper function stringPointer unnecessarily uses an intermediate variable; simplify it by returning the address of the incoming string parameter directly (i.e., modify the stringPointer function to take value string and return &value) so you remove the cloned variable while preserving semantics and avoiding extra allocation.internal/core/model/task_runtime.go (1)
160-166: Unexpected trimming incloneOptionalString.The
cloneOptionalStringfunction trims whitespace during cloning (line 164), which is a side effect that might be unexpected for a function named "clone". While this is likely intentional for normalization, consider either:
- Renaming to
cloneAndTrimOptionalStringfor clarity, or- Adding a brief comment explaining the trimming behavior
📝 Suggested documentation improvement
+// cloneOptionalString returns a deep copy of the string pointer with +// whitespace trimmed. The trimming normalizes selector/override values +// during rule cloning. func cloneOptionalString(value *string) *string { if value == nil { return nil } cloned := strings.TrimSpace(*value) return &cloned }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/model/task_runtime.go` around lines 160 - 166, The function cloneOptionalString currently trims whitespace while cloning which is surprising given its name; rename the function to cloneAndTrimOptionalString (and update every call site) to make the behavior explicit, or alternatively keep the name but add a short comment above cloneOptionalString stating that it trims whitespace as part of cloning; whichever you choose, ensure the function signature and behavior remain unchanged and update all references to the new name if you rename it.internal/cli/task_runtime_form.go (1)
364-388: Redundant trimming logic informatBaseTaskRuntime.Lines 368-374 contain redundant operations:
labelis already set fromstate.ideand trimmed, then checked and re-trimmed unnecessarily.♻️ Suggested simplification
func formatBaseTaskRuntime(state *commandState) string { if state == nil { return "" } - label := strings.TrimSpace(state.ide) - if label != "" { - label = strings.TrimSpace(label) - } - if display := strings.TrimSpace(label); display != "" { - label = display - } + label := strings.TrimSpace(state.ide) modelName := strings.TrimSpace(state.model) reasoning := strings.TrimSpace(state.reasoningEffort) parts := make([]string, 0, 3)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cli/task_runtime_form.go` around lines 364 - 388, The function formatBaseTaskRuntime contains redundant trimming and reassignment for label: after setting label := strings.TrimSpace(state.ide) the code re-trims and uses a temporary display variable; remove the extra checks and trims (the second TrimSpace and the display variable) and simply use the already-trimmed label when deciding whether to append it to parts, leaving the rest (modelName, reasoning, parts assembly) unchanged; update references to label, state.ide, and ensure modelName and reasoning behavior is preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@internal/cli/form.go`:
- Around line 35-42: The current branch flips
state.replaceConfiguredTaskRunRules and clears state.executionTaskRuntimeRules
whenever inputs.defineTaskRuntime is false, which causes existing
workspace-configured rules or explicit --task-runtime flags to be lost if the
user declines the extra form; change the logic in the start-flow handlers (the
block using state.kind == commandKindStart, the collectStartTaskRuntimeForm
call, and the other similar spots) so that you only mutate
state.replaceConfiguredTaskRunRules or clear state.executionTaskRuntimeRules
when the user explicitly chooses to define/replace per-task rules (i.e., when
inputs.defineTaskRuntime is true and/or when collectStartTaskRuntimeForm returns
a positive confirmation), and do not change or clear those fields when the user
declines the form; apply the same fix to the other occurrences mentioned (the
blocks around state.taskRuntimeRules() usages).
In `@internal/cli/root_test.go`:
- Around line 657-673: Wrap the existing
TestAddCommonFlagsUseResilientRetryDefaults body in a t.Run subtest with a
"Should ..." name (e.g., t.Run("Should use resilient retry defaults", func(t
*testing.T) { ... })), move the t.Parallel() call into that subtest, and keep
all existing assertions and lookups (state := newCommandState, cmd :=
newTestCommand, checks for state.maxRetries and flag.DefValue) unchanged inside
the subtest so the test complies with the t.Run("Should...") pattern.
In `@internal/cli/state.go`:
- Around line 73-74: The defaultMaxRetries constant currently set to 2 causes
non-idempotent jobs to be retried by default; change defaultMaxRetries to 0 (no
retries) and ensure retry behavior is opt-in via the existing CLI retry flag or
configuration (update any code paths referencing defaultMaxRetries such as where
retries are parsed/applied and any help text or tests); specifically update the
constant defaultMaxRetries and adjust related logic that merges defaults into
actual retry settings so retries only occur when the user explicitly enables
them (also update any unit tests or docs that assume a default of 2).
In `@internal/cli/workspace_config_test.go`:
- Around line 229-279: The test
TestApplyWorkspaceDefaultsKeepsConfiguredTaskRuntimeRulesAndBuildConfigAppendsCLIOverrides
is not hermetic because applyWorkspaceDefaults reads the global config from
HOME; make the test isolated by setting HOME to a temp directory before calling
state.applyWorkspaceDefaults (or use t.Setenv("HOME", tmpDir) if available) and
restore/cleanup after, or alternatively remove t.Parallel() to avoid concurrent
interference; ensure you perform this HOME isolation before calling
writeCLIWorkspaceConfig/chdirCLITest and before state.applyWorkspaceDefaults so
configuredTaskRuntimeRules only reflects the test fixtures.
In `@internal/core/run/executor/execution.go`:
- Around line 220-222: finalizeExecution() should not use the caller's
cancelable ctx when waiting for observer hooks because that causes shutdown
steps to be skipped on cancellation; change the call to
model.WaitForObserverHooks to use a bounded shutdown context that ignores the
original ctx (e.g., create a new context via
context.WithTimeout(context.Background(), <reasonableDuration>) and defer
cancel()) and pass that to model.WaitForObserverHooks along with
internalCfg.RuntimeManager so the rest of finalization (run.pre_shutdown,
terminal events, task meta refresh, sound notification) still runs even if the
run ctx is canceled.
In `@internal/core/run/executor/runner.go`:
- Around line 205-208: jobRuntimeChanged currently trims runtimes so a hook can
make whitespace-only changes (e.g., "codex" -> "codex ") and bypass the guard
before applyHookModelJob writes the raw value; update jobRuntimeChanged to treat
whitespace-only edits as a runtime mutation by comparing both the trimmed values
and the raw values (or explicitly checking if rawBefore != rawAfter even when
trimmedBefore == trimmedAfter) and return true for any difference, then keep
applyHookModelJob as-is; make the same change for the other occurrence
referenced (lines 272-275) so whitespace-only changes cannot slip through.
In `@internal/core/run/internal/acpshared/command_io_test.go`:
- Around line 186-194: The test TestCreateACPClientUsesPerJobRuntimeWhenPresent
must not call t.Parallel() because SwapNewAgentClientForTest mutates
package-global constructor state; remove the t.Parallel() invocation from that
test (or convert the test into a non-parallel setup section that calls
SwapNewAgentClientForTest and then runs parallel subtests with t.Run +
t.Parallel() if needed) so that the global factory swap performed by
SwapNewAgentClientForTest is not observed by other parallel tests.
In `@internal/core/workspace/config_test.go`:
- Around line 361-394: The tests TestLoadConfigParsesStartTaskRuntimeRules (and
the similar one at 434-452) call LoadConfig which merges global HOME config, so
isolate the test from a developer's ~/.compozy/config.toml by invoking
isolateWorkspaceConfigHome(t) (or stubbing osUserHomeDir) before calling
LoadConfig, and remove t.Parallel() from the test since
isolateWorkspaceConfigHome requires non-parallel execution; update both test
functions to call isolateWorkspaceConfigHome(t) at the start (or set a temporary
HOME via osUserHomeDir stub) and then call LoadConfig as before.
In `@pkg/compozy/events/event_test.go`:
- Around line 141-142: Rename the failing subtest names that use plain strings
to the required t.Run("Should...") pattern: update the subtest names currently
written as "job queued" and "job started" (and the other case at lines ~159-160)
to descriptive "Should..." names (e.g., "Should queue job" and "Should start
job") wherever t.Run is invoked in the event tests that construct
kinds.JobQueuedPayload / kinds.JobStartedPayload so they comply with the
enforced naming rule.
In `@sdk/extension/compat_test.go`:
- Around line 37-38: Rename the test case name fields that feed into t.Run to
follow the mandated "Should..." pattern: update the case with public:
extension.TaskRuntime{} (currently name "TaskRuntime") to a "Should..."
description (e.g., "Should retain TaskRuntime compatibility") and update the
case with public: extension.TaskRuntimeTask{} (currently name "TaskRuntimeTask")
to a "Should..." description (e.g., "Should retain TaskRuntimeTask
compatibility"); these name values are the tc.name passed into t.Run(tc.name,
...) so ensure the name fields in the test cases slice are changed accordingly.
---
Nitpick comments:
In `@internal/cli/task_runtime_flag.go`:
- Around line 141-144: The helper function stringPointer unnecessarily uses an
intermediate variable; simplify it by returning the address of the incoming
string parameter directly (i.e., modify the stringPointer function to take value
string and return &value) so you remove the cloned variable while preserving
semantics and avoiding extra allocation.
In `@internal/cli/task_runtime_form.go`:
- Around line 364-388: The function formatBaseTaskRuntime contains redundant
trimming and reassignment for label: after setting label :=
strings.TrimSpace(state.ide) the code re-trims and uses a temporary display
variable; remove the extra checks and trims (the second TrimSpace and the
display variable) and simply use the already-trimmed label when deciding whether
to append it to parts, leaving the rest (modelName, reasoning, parts assembly)
unchanged; update references to label, state.ide, and ensure modelName and
reasoning behavior is preserved.
In `@internal/core/model/task_runtime.go`:
- Around line 160-166: The function cloneOptionalString currently trims
whitespace while cloning which is surprising given its name; rename the function
to cloneAndTrimOptionalString (and update every call site) to make the behavior
explicit, or alternatively keep the name but add a short comment above
cloneOptionalString stating that it trims whitespace as part of cloning;
whichever you choose, ensure the function signature and behavior remain
unchanged and update all references to the new name if you rename it.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b8d6f0af-9c95-4475-9332-a9abbcd60cee
⛔ Files ignored due to path filters (50)
.agents/skills/qa-execution/SKILL.mdis excluded by!**/*.md,!.agents/**.agents/skills/qa-execution/assets/issue-template.mdis excluded by!**/*.md,!.agents/**.agents/skills/qa-execution/assets/verification-report-template.mdis excluded by!**/*.md,!.agents/**.agents/skills/qa-execution/references/checklist.mdis excluded by!**/*.md,!.agents/**.agents/skills/qa-execution/references/e2e-coverage.mdis excluded by!**/*.md,!.agents/**.agents/skills/qa-execution/references/project-signals.mdis excluded by!**/*.md,!.agents/**.agents/skills/qa-execution/references/web-ui-qa.mdis excluded by!**/*.md,!.agents/**.agents/skills/qa-execution/scripts/discover-project-contract.pyis excluded by!.agents/**.agents/skills/qa-report/SKILL.mdis excluded by!**/*.md,!.agents/**.agents/skills/qa-report/assets/issue-template.mdis excluded by!**/*.md,!.agents/**.agents/skills/qa-report/references/bug_report_templates.mdis excluded by!**/*.md,!.agents/**.agents/skills/qa-report/references/figma_validation.mdis excluded by!**/*.md,!.agents/**.agents/skills/qa-report/references/regression_testing.mdis excluded by!**/*.md,!.agents/**.agents/skills/qa-report/references/test_case_templates.mdis excluded by!**/*.md,!.agents/**.agents/skills/qa-report/scripts/create_bug_report.shis excluded by!.agents/**.agents/skills/qa-report/scripts/generate_test_cases.shis excluded by!.agents/**.agents/skills/tui-glamorous/SKILL.mdis excluded by!**/*.md,!.agents/**.agents/skills/tui-glamorous/references/PROMPTS.mdis excluded by!**/*.md,!.agents/**.agents/skills/tui-glamorous/references/QUICK-REFERENCE.mdis excluded by!**/*.md,!.agents/**.agents/skills/tui-glamorous/references/advanced-patterns.mdis excluded by!**/*.md,!.agents/**.agents/skills/tui-glamorous/references/component-catalog.mdis excluded by!**/*.md,!.agents/**.agents/skills/tui-glamorous/references/go-tui.mdis excluded by!**/*.md,!.agents/**.agents/skills/tui-glamorous/references/infrastructure.mdis excluded by!**/*.md,!.agents/**.agents/skills/tui-glamorous/references/production-architecture.mdis excluded by!**/*.md,!.agents/**.agents/skills/tui-glamorous/references/shell-scripts.mdis excluded by!**/*.md,!.agents/**.codex/ledger/2026-04-17-MEMORY-task-runtime-overrides.mdis excluded by!**/*.md.codex/ledger/2026-04-17-MEMORY-tasks-props-qa.mdis excluded by!**/*.mdREADME.mdis excluded by!**/*.mdai-docs/reviews-pr-93/_summary.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/001-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/002-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/003-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/004-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/005-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/006-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/007-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/008-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/issue_009.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/issue_010.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/issue_011.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/issue_012.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/issue_013.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/issue_014.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/issue_015.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/issue_016.mdis excluded by!**/*.mdai-docs/reviews-pr-93/issues/issue_017.mdis excluded by!**/*.mdai-docs/reviews-pr-93/outside/003-outside_01_internal_setup_reusable_agents.go.mdis excluded by!**/*.mddocs/extensibility/hook-reference.mdis excluded by!**/*.mdskills-lock.jsonis excluded by!**/*.jsonskills/compozy/references/config-reference.mdis excluded by!**/*.md
📒 Files selected for processing (48)
internal/cli/commands.gointernal/cli/form.gointernal/cli/form_test.gointernal/cli/root_test.gointernal/cli/state.gointernal/cli/task_runtime_flag.gointernal/cli/task_runtime_flag_test.gointernal/cli/task_runtime_form.gointernal/cli/testdata/exec_help.goldeninternal/cli/testdata/start_help.goldeninternal/cli/workspace_config.gointernal/cli/workspace_config_test.gointernal/core/api.gointernal/core/extension/manager.gointernal/core/extension/manifest.gointernal/core/model/hooks.gointernal/core/model/model_test.gointernal/core/model/preparation.gointernal/core/model/runtime_config.gointernal/core/model/task_runtime.gointernal/core/plan/prepare.gointernal/core/plan/prepare_test.gointernal/core/run/executor/execution.gointernal/core/run/executor/execution_acp_test.gointernal/core/run/executor/hooks.gointernal/core/run/executor/lifecycle.gointernal/core/run/executor/result.gointernal/core/run/executor/runner.gointernal/core/run/executor/runtime_guard_test.gointernal/core/run/internal/acpshared/command_io.gointernal/core/run/internal/acpshared/command_io_test.gointernal/core/run/internal/runshared/config.gointernal/core/run/ui/model.gointernal/core/run/ui/timeline.gointernal/core/run/ui/types.gointernal/core/run/ui/update.gointernal/core/run/ui/view_test.gointernal/core/workspace/config_merge.gointernal/core/workspace/config_test.gointernal/core/workspace/config_types.gointernal/core/workspace/config_validate.gopkg/compozy/events/event_test.gopkg/compozy/events/kinds/job.gosdk/extension/compat_test.gosdk/extension/handlers.gosdk/extension/hooks.gosdk/extension/smoke_test.gosdk/extension/types.go
Summary by CodeRabbit