Repository navigation
refactor: improve packages - #70
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughReorganizes CLI into a command-state layer with per-command constructors, adds async update-check + upgrade command, splits and reshapes model/agent/migration/reviews/contentconv packages, refactors run→exec APIs, and introduces many test updates across the codebase. Changes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 19
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/core/plan/journal.go (1)
19-21:⚠️ Potential issue | 🟠 MajorReplace
context.Background()fallback withcontext.TODO().Line 20 uses
context.Background()in production code. Per coding guidelines, avoidcontext.Background()outsidemainand focused tests. Usecontext.TODO()instead to signal incomplete context setup.Suggested fix
closeCtx := ctx if closeCtx == nil { - closeCtx = context.Background() + closeCtx = context.TODO() }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/plan/journal.go` around lines 19 - 21, Replace the use of context.Background() fallback with context.TODO() in the journal close path: where the variable closeCtx is being defaulted (the block containing "if closeCtx == nil { closeCtx = context.Background() }"), change it to use context.TODO() so the code signals an incomplete context setup; update the assignment referencing closeCtx in the journal.go function/method that performs the close logic accordingly.
🧹 Nitpick comments (11)
internal/core/agent/tool_call_name.go (4)
134-143: Remove duplicate title trimming logic.The
ToolKindOthercase and the code after the switch both check for a trimmed title. TheToolKindOthercase can be removed since the post-switch logic handles it.♻️ Proposed fix
case acp.ToolKindFetch: if looksLikeWebSearchInput(input) { return toolNameWebSearch } return toolNameWebFetch - case acp.ToolKindOther: - if trimmed := strings.TrimSpace(title); trimmed != "" { - return trimmed - } } if trimmed := strings.TrimSpace(title); trimmed != "" { return trimmed } return ""🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/agent/tool_call_name.go` around lines 134 - 143, Remove the duplicate trimming logic by deleting the acp.ToolKindOther case block inside the switch in the GetToolCallName (or equivalent) function so that the post-switch fallback that trims and returns title handles that kind; ensure you leave the final post-switch check that uses strings.TrimSpace(title) and returns trimmed or "" so other cases still short-circuit as intended and no duplicate trimmed variable declarations remain.
91-93: Consider adding parentheses for clarity.The condition relies on Go's operator precedence, but explicit parentheses would improve readability.
✨ Suggested improvement
- if token == "update_todo" || input != nil && input["todos"] != nil { + if token == "update_todo" || (input != nil && input["todos"] != nil) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/agent/tool_call_name.go` around lines 91 - 93, The conditional combining token and input checks is correct but unclear; update the if in tool_call_name.go to use explicit parentheses around the alternatives—e.g., parenthesize (token == "update_todo") and (input != nil && input["todos"] != nil) when returning toolNameTodoWrite—so the intent is immediately obvious when reading the token, input, and toolNameTodoWrite logic.
227-236: Shallow clone may share nested references.This performs a shallow copy, so nested maps or slices within
srcwill be shared between the original and clone. Verify this is the intended behavior for your use case, or consider a deep clone if mutation isolation is required.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/agent/tool_call_name.go` around lines 227 - 236, The cloneMap function currently does a shallow copy and will share nested map/slice references; either document this behavior or change cloneMap to perform a deep copy by recursively handling nested types: when iterating in cloneMap, if value is map[string]any call cloneMap(value) recursively, if value is []any make a new slice and deep-clone each element (recursing for nested maps/slices), and for other types copy as-is; update function name/signature or add helper deepCloneValue/deepCloneMap helpers as needed to keep code clear.
28-60: Consider using constants consistently for tool names.The map uses a mix of constant references (
toolNameFind,toolNameGrep) and string literals ("Edit","Write","Glob"). For maintainability, consider defining constants for all tool names used here and using them consistently throughout.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/agent/tool_call_name.go` around lines 28 - 60, The map commonToolTitleAliases mixes string literals and existing constants (e.g., toolNameFind); define constants for the repeated tool title strings (e.g., Edit, Write, Glob, Grep, Read, Task, etc.), replace the string literals in commonToolTitleAliases with those new constants, and update any other places that use the same literal values to use the constants for consistency; keep existing constant names like toolNameFind unchanged and ensure the new constants are declared near other toolName* constants for discoverability.internal/core/agent/acp_convert.go (1)
436-449: Consider returning the error instead of silently returning nil.The marshaling error is silently swallowed, which could mask issues. Per coding guidelines, errors should be handled explicitly. Consider returning the error or logging it.
♻️ Proposed fix
-func marshalRawJSON(value any) json.RawMessage { +func marshalRawJSON(value any) (json.RawMessage, error) { if value == nil { - return nil + return nil, nil } if raw, ok := value.(json.RawMessage); ok { - return append(json.RawMessage(nil), raw...) + return append(json.RawMessage(nil), raw...), nil } payload, err := json.Marshal(value) if err != nil { - return nil + return nil, fmt.Errorf("marshal raw JSON: %w", err) } - return payload + return payload, nil }As per coding guidelines: "Never ignore errors with
_in Go — every error must be handled or have a written justification."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/agent/acp_convert.go` around lines 436 - 449, marshalRawJSON currently swallows json.Marshal errors and returns nil; change its signature to return (json.RawMessage, error) and propagate the marshal error instead of returning nil, updating callers to handle the error (or alternatively log the error where appropriate); specifically modify function marshalRawJSON to return both payload and error and update any call sites that use marshalRawJSON to check and handle the returned error.internal/core/model/constants.go (1)
15-22: Keep provider defaults out of package constants.
Default*ModelandDefaultActivityTimeoutare runtime configuration, so every provider rename, deprecation, or policy change now becomes a code change. Prefer loading these defaults from TOML or injecting them via options instead of freezing them ininternal/core/model.As per coding guidelines, "Never hardcode configuration in Go code — use TOML config or functional options."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/model/constants.go` around lines 15 - 22, The constants DefaultCodexModel, DefaultClaudeModel, DefaultCursorModel, DefaultOpenCodeModel, DefaultPiModel, DefaultGeminiModel, DefaultCopilotModel and DefaultActivityTimeout are hardcoded runtime configuration and should be moved out of package constants; instead remove these values from internal/core/model/constants.go and load them from a TOML config or provide them via functional options/constructor parameters (e.g., pass defaults into the model/provider constructors or a Config struct) so provider renames/deprecations and timeouts can be changed without code edits; update any usages of these symbols to read from the injected Config or options and add sensible fallback logic where the config is missing.internal/cli/kernel_dispatch_test.go (1)
248-249: Remove the unnecessary loop variable capture pattern since the project targets Go 1.26.1.The
tt := ttpattern was required before Go 1.22 to capture loop variables for closures, but is redundant in Go 1.22+ due to improved loop variable scoping. Delete line 249 to simplify the code.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cli/kernel_dispatch_test.go` around lines 248 - 249, Remove the redundant per-iteration capture assignment by deleting the inner "tt := tt" in the test loop that iterates over "tests" (the loop variable "tt" is already correctly scoped in Go 1.26.1), so simply iterate with "for _, tt := range tests { ... }" and update any closures to reference "tt" directly without the extra reassignment.cmd/compozy/main.go (1)
61-66: Channel read blocks until closed - verify this is intentional.
waitForUpdateResultblocks on<-result, which will wait until either a value is sent or the channel is closed. SincestartUpdateCheckalways closes the channel in its defer, this is safe. However, ifCheckForUpdatetakes a long time, the CLI will block after command execution completes.Consider adding a timeout or making this non-blocking if rapid CLI exit is important:
♻️ Optional: Add timeout to avoid blocking on slow update checks
func waitForUpdateResult(result <-chan *update.ReleaseInfo) *update.ReleaseInfo { if result == nil { return nil } - return <-result + select { + case release := <-result: + return release + case <-time.After(500 * time.Millisecond): + return nil + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmd/compozy/main.go` around lines 61 - 66, waitForUpdateResult currently blocks on <-result and can hang the CLI if CheckForUpdate is slow; change waitForUpdateResult to use a select that waits on the result channel and a timeout (e.g., time.After with a short duration) and return nil on timeout so the CLI can exit promptly; reference the result channel returned by startUpdateCheck/CheckForUpdate and ensure you import time and handle the nil result case as before.internal/core/kernel/deps_test.go (1)
580-581: Consider removing redundant loop variable capture.In Go 1.22+, the loop variable semantics changed, making
tt := ttunnecessary. However, this is a minor cleanup that can be deferred.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/kernel/deps_test.go` around lines 580 - 581, Remove the redundant loop-variable shadowing line (`tt := tt`) inside the for loop over `tests`; simply iterate `for _, tt := range tests {` and use `tt` directly (or pass `tt` into the subtest closure via `t.Run(tt.name, func(t *testing.T) { tc := tt; ... })` if you prefer explicit capture), so eliminate the unnecessary `tt := tt` statement in the test loop in deps_test.go.internal/cli/workspace_config_test.go (1)
234-308: Use table-drivent.Run("Should ...")subtests for the new coercion helper coverage.These two tests exercise several independent type/parsing branches in one body, so the first failure hides which contract actually regressed. A small table of
Should ...cases would make the failures more precise and keep future coercion cases cheap to add.As per coding guidelines, "Use table-driven tests with subtests (
t.Run) as the default test pattern" and "MUST use t.Run("Should...") pattern for ALL test cases".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cli/workspace_config_test.go` around lines 234 - 308, The two monolithic tests TestApplyConfigHandlesSupportedTypes and TestApplyInputHandlesSupportedTypes should be refactored into table-driven subtests using t.Run("Should ...") so each coercion/parsing branch is exercised and reported independently; for each test create a slice of cases (each with a name like "Should apply string config", target flag/key, input value, helper function reference such as applyConfig/applyInput, and expected result) and iterate calling t.Run(case.name, func(t *testing.T){ t.Parallel(); ... assert expectations }), keeping use of helpers passThroughInput, parseIntInput, parseStringSliceInput and the clone function for slices, and preserve current assertions but split them into per-case checks so failures identify the exact contract that regressed.internal/core/kernel/core_adapters_test.go (1)
98-221: Consolidate these adapter cases into one table-drivenShould ...test.The four new tests all repeat the same dispatcher setup, global stub swap, and result-shape assertions. Folding them into a single table with
t.Run("Should ...")cases will remove boilerplate and make future adapter additions much cheaper.As per coding guidelines, "Use table-driven tests with subtests (
t.Run) as the default pattern for Go tests" and "MUST use t.Run("Should...") pattern for ALL test cases".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/kernel/core_adapters_test.go` around lines 98 - 221, The four near-duplicate tests (TestDispatchFetchReviewsAdapterBuildsReviewsFetchCommand, TestDispatchMigrateAdapterBuildsWorkspaceMigrateCommand, TestDispatchSyncAdapterBuildsWorkflowSyncCommand, TestDispatchArchiveAdapterBuildsWorkflowArchiveCommand) should be merged into a single table-driven test that iterates cases and uses t.Run("Should ...") subtests; create a cases slice where each entry specifies the adapter function to call (dispatchFetchReviewsAdapter, dispatchMigrateAdapter, dispatchSyncAdapter, dispatchArchiveAdapter), the input config, the expected result assertions, and the capture handler type/result to register, then inside the loop set up NewDispatcher(), Register(dispatcher, handler), stub coreAdapterDispatcherFn to return the dispatcher, call the adapter function, and assert result shape and handler.got fields; keep using t.Cleanup to restore coreAdapterDispatcherFn and preserve the same assertions per-case but eliminated boilerplate by centralizing common setup and teardown.
🤖 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/run.go`:
- Around line 95-103: Replace the ignored return values from the fmt.Fprintf
call that writes the summary (the call using cmd.OutOrStdout() with
result.Total/result.Provider/result.PR/result.ReviewsDir/result.Round) with
proper error handling: capture the returned n, err := fmt.Fprintf(...), check if
err != nil and return or wrap that error (or log and return) so the function
does not silently succeed when stdout is closed or the write fails.
In `@internal/cli/state.go`:
- Around line 379-423: The logic currently omits non-empty stdin from
sourceCount, allowing two prompt sources; fix by calling
readPromptFromCommandInput(cmd.InOrStdin()) before computing/validating
sourceCount, store stdinPrompt and hasStdinPrompt, increment sourceCount if
hasStdinPrompt, then perform the existing exclusivity check and use the stored
stdinPrompt/hasStdinPrompt in the switch; ensure you don't re-read stdin later
(use the saved stdinPrompt and set s.readPromptStdin = true /
s.resolvedPromptText = stdinPrompt when selecting the stdin case).
In `@internal/contentblock/engine.go`:
- Around line 51-60: MarshalEnvelopeJSON currently returns any non-empty data
verbatim without validating it's well-formed JSON or that an embedded "type"
matches blockType; update MarshalEnvelopeJSON(blockType, data) to first ensure
json.Valid(data) (return an error if not), then unmarshal data into a small
container (e.g. map[string]json.RawMessage or struct with a Type field) and, if
a "type" field is present, verify it equals blockType (return an error if it
disagrees); if validation passes, return the original data unchanged and include
clear contextual error messages mentioning blockType.
In `@internal/core/agent/registry_launch.go`:
- Around line 73-79: EnsureAvailable currently calls resolveLaunchCommand with
spec.DefaultModel, ignoring any user-supplied cfg.Model; change it to use the
effective model selection logic (use cfg.Model when set, otherwise fall back to
spec.DefaultModel) and pass that effective model into resolveLaunchCommand so
availability probing uses the same model choice as runtime. Update the call in
EnsureAvailable to compute effectiveModel := cfg.Model; if effectiveModel == ""
then effectiveModel = spec.DefaultModel, then call resolveLaunchCommand(spec,
effectiveModel, cfg.ReasoningEffort, cfg.AddDirs, cfg.AccessMode, true).
- Around line 306-324: joinAvailabilityErrors currently flattens multiple errors
into a single string losing typed AvailabilityError details; update the function
(joinAvailabilityErrors) to preserve error types by returning a chained/joined
error instead of strings.Join — e.g., when len(errs) > 1 return
errors.Join(errs...) (or wrap with fmt.Errorf("%w", err) per project style) so
callers can use errors.As to recover AvailabilityError fields; keep the existing
zero-case that returns an AvailabilityError constructed from Spec and the
single-error case returning errs[0].
- Around line 180-186: formatShellArg currently uses strconv.Quote, which
produces Go string literals but allows shell expansions; replace that with
shell-safe quoting: implement a shell quoting routine (used by formatShellArg)
that wraps the arg in single quotes and escapes any internal single quotes by
replacing ' with '\'' (i.e., close single quote, insert escaped single quote,
reopen single quote) so the resulting string is safe to paste into a POSIX shell
and will not undergo $ or ` expansion; update formatShellArg to call this
shell-safe function whenever strings.ContainsAny detects characters requiring
quoting.
In `@internal/core/agent/registry_specs.go`:
- Around line 55-60: Update the RuntimeRegistry interface so EnsureAvailable
accepts context.Context as its first parameter (change signature to
EnsureAvailable(ctx context.Context, cfg *model.RuntimeConfig) error), then
update all concrete implementations of RuntimeRegistry to match that signature
and propagate the incoming ctx through their external process calls; finally,
replace any uses that call EnsureAvailable (notably verifyLauncher) to pass the
caller's ctx instead of using context.Background(), ensuring context is threaded
through to allow cancellation/timeouts.
In `@internal/core/agent/registry_validate.go`:
- Around line 145-156: The runtimePromptSourceCount function currently counts
PromptText, PromptFile, and ReadPromptStdin but omits ResolvedPromptText,
causing valid requests normalized into ResolvedPromptText to fail validation;
update runtimePromptSourceCount(cfg *model.RuntimeConfig) to also check
strings.TrimSpace(cfg.ResolvedPromptText) != "" and increment sources when
present (treat it as a fallback prompt source), so the function aligns with
resolveExecPromptText which prefers ResolvedPromptText.
- Around line 12-18: ValidateRuntimeConfig currently dereferences cfg (cfg.Mode)
and will panic when called with nil; add an explicit nil check at the start of
ValidateRuntimeConfig to return a descriptive error (e.g., "runtime config is
nil") instead of panicking, then proceed to call validateRuntimeMode and
lookupAgentSpec as before; reference the ValidateRuntimeConfig function and
ensure any callers still get the same semantic errors from
validateRuntimeMode/lookupAgentSpec when cfg is non-nil.
In `@internal/core/agent/tool_call_name.go`:
- Around line 161-171: The condition inside the ref_id branch redundantly tests
refID != "" again (refID was already checked), causing the OpenURL branch to
always fire when pattern is empty; in the block starting with refID :=
extractString(input, "ref_id", "refId") check, change the final if to only test
presence of "url" (i.e., if extractString(input, "url") != "" ) before returning
"OpenURL", leaving the Click and toolNameFind checks intact so OpenURL is only
returned when a url key exists.
In `@internal/core/migration/migrate.go`:
- Around line 166-194: The three functions appendTaskMigration,
appendReviewMigration, and recordRoundMeta currently swallow all errors from
inspectTaskArtifact/inspectReviewArtifact/inspectRoundMeta by calling
s.recordInvalid(path) and returning nil; instead, preserve and return the
original errors wrapped with context (e.g., fmt.Errorf("inspectTaskArtifact %s:
%w", path, err)) so I/O/permission/parse failures remain visible to callers.
Concretely: in appendTaskMigration and appendReviewMigration, on error do not
just call s.recordInvalid(path) and return nil — return a wrapped error; if you
have a specific sentinel error for “invalid artifact” (e.g.,
ErrInvalidArtifact), you may still call s.recordInvalid(path) for that case but
still return the wrapped sentinel error. Apply the same change to
recordRoundMeta (wrap and return inspectRoundMeta errors instead of silently
recording invalid).
- Around line 81-88: The write loop over pending artifacts currently always
writes every file; change it to honor the migration context by checking
ctx.Err() or using a select on ctx.Done() before each os.WriteFile call and
aborting early (returning ctx.Err() or a wrapped context.Canceled error) so no
further files are mutated after cancellation; update the loop that iterates over
pending (the slice sorted with sort.Slice) to perform this context check before
attempting each write and avoid proceeding to os.WriteFile when the context is
cancelled.
In `@internal/core/migration/workflow_target.go`:
- Around line 30-31: Reject workflow names that can escape the workflow root by
validating cfg.name before joining it to rootDir: ensure cfg.name is non-empty
and does not contain path separators ('/' or '\\'), dot-segments ('.' or '..'),
or an absolute path prefix; if any of these are present return an error (fail
fast). Update the check around strings.TrimSpace(cfg.name) != "" (and the
similar check at the other occurrence around lines 61-63) to perform this
validation and use the same validation helper wherever cfg.name is used to form
a path to prevent directory traversal/escape.
In `@internal/core/model/artifacts.go`:
- Around line 74-79: The JobArtifacts helper trusts the caller's safeName and
can be tricked into creating paths outside the intended jobs directory; update
RunArtifacts.JobArtifacts to sanitize/normalize safeName (same approach used in
NewRunArtifacts) before joining: reject empty or names containing path
separators or "..", or else replace with filepath.Base / strings.ReplaceAll to
strip directory components and disallow traversal, and then use that sanitized
name to build PromptPath, OutLogPath and ErrLogPath so artifact writes remain
confined to the flat jobs namespace.
In `@internal/core/model/content_test.go`:
- Around line 217-248: Wrap the existing test body of
TestContentBlockMarshalUsesCamelCaseJSONTags into a t.Run subtest with a
"Should..." name (e.g., t.Run("Should use camelCase JSON tags", func(t
*testing.T) { ... })), moving t.Parallel() into the subtest if parallelism is
desired, and keep all existing setup and assertions (NewContentBlock,
json.Marshal, checks against encoded for required/forbidden fields) unchanged
inside that subtest so the test follows the repository's t.Run naming pattern.
In `@internal/core/model/preparation.go`:
- Around line 35-53: SetJournal currently overwrites an existing JournalHandle
without releasing it and CloseJournal clears JournalHandle before Close(ctx)
completes; update SetJournal and CloseJournal on SolvePreparation to protect
ownership: in SetJournal, if p.JournalHandle != nil then refuse to replace
(no-op) or return without dropping the old owner (i.e., do not call
journal.NewOwner when an owner already exists) to avoid orphaning the previous
handle; in CloseJournal, capture the current handle, call handle.Close(ctx)
first and only set p.JournalHandle = nil after Close returns successfully
(propagate/return the error if Close fails) so the previous owner remains
reachable on failure; reference methods: SetJournal, CloseJournal,
JournalHandle, journal.NewOwner, and handle.Close.
In `@internal/core/prompt/prompt_test.go`:
- Around line 288-294: Update TestSafeFileName to a table-driven subtest using
t.Run and verify both the sanitized prefix and the hash suffix behavior of
SafeFileName: for each case assert the returned string starts with the expected
sanitized prefix and then either (a) verify the suffix matches the expected
6-character alphanumeric fingerprint shape (e.g., regex [A-Za-z0-9]{6} after a
hyphen) or (b) include a pair of inputs that sanitize to the same base and
assert SafeFileName yields different full names for them (ensuring a
collision-resistant suffix); reference the SafeFileName function and the
TestSafeFileName test name when updating the assertions.
In `@internal/core/reviews/parser_test.go`:
- Around line 9-96: The tests in TestReviewParsingHelpers,
TestExtractIssueNumber, and TestWrapParseErrorProvidesMigrationGuidance must be
refactored into table-driven subtests using t.Run("Should ...") so each scenario
is isolated; create a slice of test cases (with name, input, expected) for the
ParseReviewContext/IsReviewResolved/legacy parse/extract flows and for
ExtractIssueNumber and WrapParseError, iterate over cases and call t.Run("Should
<behavior>", func(t *testing.T){ t.Parallel(); ... assertions ... }), moving
each assertion into its matching subtest and invoking the existing helper
functions (ParseReviewContext, IsReviewResolved, ParseLegacyReviewContext,
ExtractLegacyReviewBody, LooksLikeLegacyReviewFile, ExtractIssueNumber,
WrapParseError) inside the subtests; ensure error expectations use errors.Is
where appropriate and keep test behaviour identical while adopting the required
"Should..." subtest naming and table-driven pattern.
In `@internal/core/reviews/parser.go`:
- Around line 144-156: The extractXMLTag function currently searches the whole
file and must be limited to the <review_context> block: first locate the
"<review_context>" open and "</review_context>" close (return "" if either is
missing), slice content to just that inner block, then perform the existing tag
extraction logic on that slice; update references to extractXMLTag to continue
accepting the full file string (the function itself will do the scoping), and
keep the function name extractXMLTag and tag parameter unchanged.
---
Outside diff comments:
In `@internal/core/plan/journal.go`:
- Around line 19-21: Replace the use of context.Background() fallback with
context.TODO() in the journal close path: where the variable closeCtx is being
defaulted (the block containing "if closeCtx == nil { closeCtx =
context.Background() }"), change it to use context.TODO() so the code signals an
incomplete context setup; update the assignment referencing closeCtx in the
journal.go function/method that performs the close logic accordingly.
---
Nitpick comments:
In `@cmd/compozy/main.go`:
- Around line 61-66: waitForUpdateResult currently blocks on <-result and can
hang the CLI if CheckForUpdate is slow; change waitForUpdateResult to use a
select that waits on the result channel and a timeout (e.g., time.After with a
short duration) and return nil on timeout so the CLI can exit promptly;
reference the result channel returned by startUpdateCheck/CheckForUpdate and
ensure you import time and handle the nil result case as before.
In `@internal/cli/kernel_dispatch_test.go`:
- Around line 248-249: Remove the redundant per-iteration capture assignment by
deleting the inner "tt := tt" in the test loop that iterates over "tests" (the
loop variable "tt" is already correctly scoped in Go 1.26.1), so simply iterate
with "for _, tt := range tests { ... }" and update any closures to reference
"tt" directly without the extra reassignment.
In `@internal/cli/workspace_config_test.go`:
- Around line 234-308: The two monolithic tests
TestApplyConfigHandlesSupportedTypes and TestApplyInputHandlesSupportedTypes
should be refactored into table-driven subtests using t.Run("Should ...") so
each coercion/parsing branch is exercised and reported independently; for each
test create a slice of cases (each with a name like "Should apply string
config", target flag/key, input value, helper function reference such as
applyConfig/applyInput, and expected result) and iterate calling
t.Run(case.name, func(t *testing.T){ t.Parallel(); ... assert expectations }),
keeping use of helpers passThroughInput, parseIntInput, parseStringSliceInput
and the clone function for slices, and preserve current assertions but split
them into per-case checks so failures identify the exact contract that
regressed.
In `@internal/core/agent/acp_convert.go`:
- Around line 436-449: marshalRawJSON currently swallows json.Marshal errors and
returns nil; change its signature to return (json.RawMessage, error) and
propagate the marshal error instead of returning nil, updating callers to handle
the error (or alternatively log the error where appropriate); specifically
modify function marshalRawJSON to return both payload and error and update any
call sites that use marshalRawJSON to check and handle the returned error.
In `@internal/core/agent/tool_call_name.go`:
- Around line 134-143: Remove the duplicate trimming logic by deleting the
acp.ToolKindOther case block inside the switch in the GetToolCallName (or
equivalent) function so that the post-switch fallback that trims and returns
title handles that kind; ensure you leave the final post-switch check that uses
strings.TrimSpace(title) and returns trimmed or "" so other cases still
short-circuit as intended and no duplicate trimmed variable declarations remain.
- Around line 91-93: The conditional combining token and input checks is correct
but unclear; update the if in tool_call_name.go to use explicit parentheses
around the alternatives—e.g., parenthesize (token == "update_todo") and (input
!= nil && input["todos"] != nil) when returning toolNameTodoWrite—so the intent
is immediately obvious when reading the token, input, and toolNameTodoWrite
logic.
- Around line 227-236: The cloneMap function currently does a shallow copy and
will share nested map/slice references; either document this behavior or change
cloneMap to perform a deep copy by recursively handling nested types: when
iterating in cloneMap, if value is map[string]any call cloneMap(value)
recursively, if value is []any make a new slice and deep-clone each element
(recursing for nested maps/slices), and for other types copy as-is; update
function name/signature or add helper deepCloneValue/deepCloneMap helpers as
needed to keep code clear.
- Around line 28-60: The map commonToolTitleAliases mixes string literals and
existing constants (e.g., toolNameFind); define constants for the repeated tool
title strings (e.g., Edit, Write, Glob, Grep, Read, Task, etc.), replace the
string literals in commonToolTitleAliases with those new constants, and update
any other places that use the same literal values to use the constants for
consistency; keep existing constant names like toolNameFind unchanged and ensure
the new constants are declared near other toolName* constants for
discoverability.
In `@internal/core/kernel/core_adapters_test.go`:
- Around line 98-221: The four near-duplicate tests
(TestDispatchFetchReviewsAdapterBuildsReviewsFetchCommand,
TestDispatchMigrateAdapterBuildsWorkspaceMigrateCommand,
TestDispatchSyncAdapterBuildsWorkflowSyncCommand,
TestDispatchArchiveAdapterBuildsWorkflowArchiveCommand) should be merged into a
single table-driven test that iterates cases and uses t.Run("Should ...")
subtests; create a cases slice where each entry specifies the adapter function
to call (dispatchFetchReviewsAdapter, dispatchMigrateAdapter,
dispatchSyncAdapter, dispatchArchiveAdapter), the input config, the expected
result assertions, and the capture handler type/result to register, then inside
the loop set up NewDispatcher(), Register(dispatcher, handler), stub
coreAdapterDispatcherFn to return the dispatcher, call the adapter function, and
assert result shape and handler.got fields; keep using t.Cleanup to restore
coreAdapterDispatcherFn and preserve the same assertions per-case but eliminated
boilerplate by centralizing common setup and teardown.
In `@internal/core/kernel/deps_test.go`:
- Around line 580-581: Remove the redundant loop-variable shadowing line (`tt :=
tt`) inside the for loop over `tests`; simply iterate `for _, tt := range tests
{` and use `tt` directly (or pass `tt` into the subtest closure via
`t.Run(tt.name, func(t *testing.T) { tc := tt; ... })` if you prefer explicit
capture), so eliminate the unnecessary `tt := tt` statement in the test loop in
deps_test.go.
In `@internal/core/model/constants.go`:
- Around line 15-22: The constants DefaultCodexModel, DefaultClaudeModel,
DefaultCursorModel, DefaultOpenCodeModel, DefaultPiModel, DefaultGeminiModel,
DefaultCopilotModel and DefaultActivityTimeout are hardcoded runtime
configuration and should be moved out of package constants; instead remove these
values from internal/core/model/constants.go and load them from a TOML config or
provide them via functional options/constructor parameters (e.g., pass defaults
into the model/provider constructors or a Config struct) so provider
renames/deprecations and timeouts can be changed without code edits; update any
usages of these symbols to read from the injected Config or options and add
sensible fallback logic where the config is missing.
🪄 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: 19813922-699f-440e-8333-6acace34e7fd
⛔ Files ignored due to path filters (69)
.agents/skills/refactoring-analysis/SKILL.mdis excluded by!**/*.md,!.agents/**.agents/skills/refactoring-analysis/assets/refactoring-report-template.mdis excluded by!**/*.md,!.agents/**.agents/skills/refactoring-analysis/checklists/analysis-checklist.mdis excluded by!**/*.md,!.agents/**.agents/skills/refactoring-analysis/references/code-smells-catalog.mdis excluded by!**/*.md,!.agents/**.agents/skills/refactoring-analysis/references/refactoring-techniques.mdis excluded by!**/*.md,!.agents/**.agents/skills/refactoring-analysis/references/solid-ddd-context.mdis excluded by!**/*.md,!.agents/**.agents/skills/refactoring-analysis/scripts/validate-metadata.pyis excluded by!.agents/**.claude/commands/refac.mdis excluded by!**/*.md,!.claude/**.codex/ledger/2026-04-06-MEMORY-dry-generics.mdis excluded by!**/*.md.codex/ledger/2026-04-06-MEMORY-package-splits.mdis excluded by!**/*.md.codex/ledger/2026-04-07-MEMORY-acp-cockpit-hang.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/_meta.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/_tasks.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/_techspec.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/adrs/adr-001.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/adrs/adr-002.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/adrs/adr-003.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/adrs/adr-004.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/adrs/adr-005.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/adrs/adr-006.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/memory/MEMORY.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/memory/task_01.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/memory/task_02.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/memory/task_03.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/memory/task_04.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/memory/task_05.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/memory/task_06.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/memory/task_07.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/memory/task_08.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/task_01.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/task_02.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/task_03.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/task_04.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/task_05.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/task_06.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/task_07.mdis excluded by!**/*.md.compozy/tasks/_archived/20260406-231057-engine-kernel/task_08.mdis excluded by!**/*.md.compozy/tasks/auto-update/_meta.mdis excluded by!**/*.md.compozy/tasks/auto-update/_tasks.mdis excluded by!**/*.md.compozy/tasks/auto-update/_techspec.mdis excluded by!**/*.md.compozy/tasks/auto-update/adrs/adr-001.mdis excluded by!**/*.md.compozy/tasks/auto-update/adrs/adr-002.mdis excluded by!**/*.md.compozy/tasks/auto-update/adrs/adr-003.mdis excluded by!**/*.md.compozy/tasks/auto-update/memory/MEMORY.mdis excluded by!**/*.md.compozy/tasks/auto-update/memory/task_01.mdis excluded by!**/*.md.compozy/tasks/auto-update/task_01.mdis excluded by!**/*.md.compozy/tasks/refac/20260406-agent-run.mdis excluded by!**/*.md.compozy/tasks/refac/20260406-cli-entry.mdis excluded by!**/*.md.compozy/tasks/refac/20260406-core-foundation.mdis excluded by!**/*.md.compozy/tasks/refac/20260406-plan-domain.mdis excluded by!**/*.md.compozy/tasks/refac/20260406-provider-public.mdis excluded by!**/*.md.compozy/tasks/refac/20260406-summary.mdis excluded by!**/*.md.compozy/tasks/refac/20260406-verification.mdis excluded by!**/*.md.compozy/tasks/refac/_meta.mdis excluded by!**/*.md.compozy/tasks/refac/_tasks.mdis excluded by!**/*.md.compozy/tasks/refac/_techspec.mdis excluded by!**/*.md.compozy/tasks/refac/memory/MEMORY.mdis excluded by!**/*.md.compozy/tasks/refac/memory/task_01.mdis excluded by!**/*.md.compozy/tasks/refac/memory/task_02.mdis excluded by!**/*.md.compozy/tasks/refac/memory/task_03.mdis excluded by!**/*.md.compozy/tasks/refac/memory/task_04.mdis excluded by!**/*.md.compozy/tasks/refac/memory/task_05.mdis excluded by!**/*.md.compozy/tasks/refac/task_01.mdis excluded by!**/*.md.compozy/tasks/refac/task_02.mdis excluded by!**/*.md.compozy/tasks/refac/task_03.mdis excluded by!**/*.md.compozy/tasks/refac/task_04.mdis excluded by!**/*.md.compozy/tasks/refac/task_05.mdis excluded by!**/*.mdgo.sumis excluded by!**/*.sumskills-lock.jsonis excluded by!**/*.json
📒 Files selected for processing (167)
cmd/compozy/main.gogo.modinternal/cli/commands.gointernal/cli/commands_simple.gointernal/cli/dispatch_adapters.gointernal/cli/form.gointernal/cli/kernel_dispatch_test.gointernal/cli/root.gointernal/cli/root_test.gointernal/cli/run.gointernal/cli/state.gointernal/cli/upgrade.gointernal/cli/workspace_config.gointernal/cli/workspace_config_test.gointernal/contentblock/engine.gointernal/core/agent/acp_convert.gointernal/core/agent/registry.gointernal/core/agent/registry_launch.gointernal/core/agent/registry_specs.gointernal/core/agent/registry_validate.gointernal/core/agent/session.gointernal/core/agent/tool_call_input.gointernal/core/agent/tool_call_name.gointernal/core/api.gointernal/core/archive.gointernal/core/contentconv/contentconv.gointernal/core/contentconv/contentconv_test.gointernal/core/fetch.gointernal/core/kernel/commands/commands_test.gointernal/core/kernel/commands/reviews_fetch.gointernal/core/kernel/commands/run_start.gointernal/core/kernel/commands/runtime_config.gointernal/core/kernel/commands/runtime_fields.gointernal/core/kernel/commands/workflow_archive.gointernal/core/kernel/commands/workflow_prepare.gointernal/core/kernel/commands/workflow_sync.gointernal/core/kernel/commands/workspace_migrate.gointernal/core/kernel/core_adapters.gointernal/core/kernel/core_adapters_test.gointernal/core/kernel/deps_test.gointernal/core/kernel/handlers.gointernal/core/migrate.gointernal/core/migration/migrate.gointernal/core/migration/migrate_test.gointernal/core/migration/workflow_target.gointernal/core/model/artifacts.gointernal/core/model/constants.gointernal/core/model/content.gointernal/core/model/content_test.gointernal/core/model/model.gointernal/core/model/preparation.gointernal/core/model/runtime_config.gointernal/core/model/task_review.gointernal/core/model/workflow_ops.gointernal/core/model/workspace_paths.gointernal/core/plan/input.gointernal/core/plan/journal.gointernal/core/plan/prepare.gointernal/core/plan/prepare_test.gointernal/core/prompt/common.gointernal/core/prompt/prd.gointernal/core/prompt/prompt_test.gointernal/core/prompt/templates.gointernal/core/providerdefaults/defaults.gointernal/core/reviews/parser.gointernal/core/reviews/parser_test.gointernal/core/reviews/store.gointernal/core/reviews/store_test.gointernal/core/run/command_io.gointernal/core/run/exec/aliases.gointernal/core/run/exec/exec.gointernal/core/run/exec/exec_integration_test.gointernal/core/run/exec/exec_test.gointernal/core/run/exec/test_helpers_test.gointernal/core/run/exec_facade.gointernal/core/run/execution.gointernal/core/run/executor/aliases.gointernal/core/run/executor/execution.gointernal/core/run/executor/execution_acp_integration_test.gointernal/core/run/executor/execution_acp_test.gointernal/core/run/executor/execution_test.gointernal/core/run/executor/execution_ui_test.gointernal/core/run/executor/format_helpers.gointernal/core/run/executor/lifecycle.gointernal/core/run/executor/result.gointernal/core/run/executor/result_test.gointernal/core/run/executor/review_hooks.gointernal/core/run/executor/runner.gointernal/core/run/executor/shutdown.gointernal/core/run/executor/test_helpers_test.gointernal/core/run/internal/acpshared/aliases.gointernal/core/run/internal/acpshared/command_io.gointernal/core/run/internal/acpshared/command_io_test.gointernal/core/run/internal/acpshared/compat_test.gointernal/core/run/internal/acpshared/session_exec.gointernal/core/run/internal/acpshared/session_handler.gointernal/core/run/internal/acpshared/session_handler_test.gointernal/core/run/internal/runshared/buffers.gointernal/core/run/internal/runshared/config.gointernal/core/run/internal/runshared/config_test.gointernal/core/run/internal/runshared/logging.gointernal/core/run/internal/runshared/shutdown.gointernal/core/run/internal/runshared/types.gointernal/core/run/internal/runtimeevents/events.gointernal/core/run/logging.gointernal/core/run/preflight.gointernal/core/run/preflight/preflight.gointernal/core/run/preflight/preflight_test.gointernal/core/run/preflight/preflight_test_helpers_test.gointernal/core/run/run.gointernal/core/run/run_test.gointernal/core/run/runtime_logger.gointernal/core/run/transcript/compat_test.gointernal/core/run/transcript/model.gointernal/core/run/transcript/model_test.gointernal/core/run/transcript/render.gointernal/core/run/transcript/tool_use_summary.gointernal/core/run/types.gointernal/core/run/ui/adapter_test.gointernal/core/run/ui/aliases.gointernal/core/run/ui/layout.gointernal/core/run/ui/model.gointernal/core/run/ui/model_test.gointernal/core/run/ui/sidebar.gointernal/core/run/ui/styles.gointernal/core/run/ui/summary.gointernal/core/run/ui/timeline.gointernal/core/run/ui/types.gointernal/core/run/ui/update.gointernal/core/run/ui/update_test.gointernal/core/run/ui/validation_form.gointernal/core/run/ui/validation_form_test.gointernal/core/run/ui/view.gointernal/core/run/ui/view_test.gointernal/core/run/ui_view.gointernal/core/sync.gointernal/core/tasks/parser.gointernal/core/tasks/parser_test.gointernal/core/tasks/store.gointernal/core/tasks/validate.gointernal/core/tasks/walker.gointernal/core/tasks/walker_test.gointernal/core/workflow_target.gointernal/core/workflow_target_test.gointernal/core/workspace/config.gointernal/core/workspace/config_types.gointernal/core/workspace/config_validate.gointernal/setup/agents.gointernal/setup/agents_test.gointernal/setup/install.gointernal/setup/select.gointernal/update/check.gointernal/update/check_test.gointernal/update/install.gointernal/update/install_test.gointernal/update/state.gointernal/update/state_test.gopkg/compozy/events/event_test.gopkg/compozy/events/kinds/content_block.gopkg/compozy/events/kinds/job.gopkg/compozy/events/kinds/payload_compat_test.gopkg/compozy/events/kinds/session.gopkg/compozy/events/kinds/session_test.gopkg/compozy/events/kinds/shutdown.gopkg/compozy/runs/replay.gopkg/compozy/runs/run.gopkg/compozy/runs/status.go
💤 Files with no reviewable changes (6)
- internal/core/agent/session.go
- internal/core/run/command_io.go
- internal/core/agent/tool_call_input.go
- internal/core/kernel/commands/runtime_fields.go
- internal/core/model/model.go
- internal/core/agent/registry.go
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (7)
internal/cli/root_test.go (1)
1619-1628: Consider returning the underlying error instead ofio.ErrClosedPipeas fallback.When
w.errisnil, the writer returnsio.ErrClosedPipe. This fallback is fine for current tests but could mask issues if someone accidentally uses the struct without setting the error.Minor improvement
type failOnWriteWriter struct { err error } func (w *failOnWriteWriter) Write(_ []byte) (int, error) { - if w.err != nil { - return 0, w.err - } - return 0, io.ErrClosedPipe + if w.err == nil { + return 0, io.ErrClosedPipe + } + return 0, w.err }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cli/root_test.go` around lines 1619 - 1628, The failOnWriteWriter.Write fallback returns io.ErrClosedPipe when w.err is nil, which can mask real underlying errors; modify the Write method on the failOnWriteWriter type to return a more informative default by returning the underlying w.err when present and otherwise return a distinct sentinel (e.g., nil or a new explicit error) that clearly signals "no underlying error set" so callers/tests won't be misled—update the Write method on failOnWriteWriter (referenced by type name failOnWriteWriter and its Write method) to return w.err when non-nil and a clearer fallback error when nil.internal/cli/run.go (1)
13-67: Consider extracting shared setup logic betweenrunandexec.Both methods share identical patterns: signal context setup, workspace defaults, config building, persisted exec config, validation, and
runPreparedcall. The only difference isexeccallsresolveExecPromptSourceinstead ofmaybeCollectInteractiveParams.This duplication increases maintenance burden if the common flow needs modification.
Potential extraction (optional)
func (s *commandState) prepareAndRun(cmd *cobra.Command, setupFn func() error) error { ctx, stop := signalCommandContext(cmd) defer stop() if err := s.applyWorkspaceDefaults(ctx, cmd); err != nil { return s.handleExecError(cmd, fmt.Errorf("apply workspace defaults for %s: %w", cmd.Name(), err)) } if err := setupFn(); err != nil { return s.handleExecError(cmd, err) } cfg, err := s.buildConfig() if err != nil { return s.handleExecError(cmd, err) } // ... rest of common flow }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cli/run.go` around lines 13 - 67, Extract the shared setup flow from run and exec into a helper (e.g., prepareAndRun) that performs signalCommandContext, applyWorkspaceDefaults, buildConfig, applyPersistedExecConfig, cfg.Validate and runPrepared, and accept a small setupFn parameter so callers can inject the differing step (call maybeCollectInteractiveParams for run and resolveExecPromptSource for exec); ensure the helper uses the same error-wrapping/handling via handleExecError and preserves the special applyWorkspaceDefaults error message formatting used in exec, then replace the bodies of run and exec to call this new prepareAndRun helper.internal/cli/state.go (2)
107-134:withFallbacksmodifies receiver but returns value - consider consistency.The method modifies
defaultsfields in-place (e.g.,defaults.isInteractive = builtin.isInteractive) and then returnsdefaults. This works becausecommandStateDefaultsis passed by value, but the pattern could be confusing. Consider either:
- Making it purely functional (create new struct), or
- Making it a pointer receiver that modifies in place and returns nothing
Current code is correct but the hybrid approach is unusual.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cli/state.go` around lines 107 - 134, The method withFallbacks currently mutates its value receiver (commandStateDefaults) and returns it, which is confusing; either convert it to an in-place pointer receiver or make it purely functional. To fix: choose one approach — (A) change the receiver to *commandStateDefaults (withFallbacks) and remove the return value so the method mutates the struct in place, updating all call sites to rely on mutation; or (B) keep a value receiver but avoid mutating the parameter by creating a new local variable (e.g., result := defaults), set fallbacks on result using defaultCommandStateDefaults(), and return result; then keep existing call sites that expect a returned value. Ensure you update all references to withFallbacks accordingly.
225-249: Redundant nil checks for callbacks that are always set bywithFallbacks.Lines 230-233 and 241-244 check if
isInteractiveandcollectFormare nil, butnewCommandStateWithDefaultsalways callswithFallbacks()which guarantees these are set. The nil checks are defensive but potentially misleading about the contract.Consider removing redundant nil checks or documenting the defensiveness
If
withFallbacks()is always called during construction, these checks are unreachable. Either:
- Remove them to clarify the invariant, or
- Add a comment explaining they're defensive for direct struct construction
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cli/state.go` around lines 225 - 249, The nil checks for isInteractive and collectForm in maybeCollectInteractiveParams are redundant because newCommandStateWithDefaults calls withFallbacks() which guarantees those callbacks are set; remove the conditional assignments and directly call s.isInteractive() and s.collectForm(cmd, s) (or, if you prefer to keep the defensive pattern, add a short comment above the checks referencing newCommandStateWithDefaults and withFallbacks to explain they are only defensive for non-standard construction). Update maybeCollectInteractiveParams to use the guaranteed callbacks (isInteractive and collectForm) and simplify the error/flow accordingly, referencing the methods/fields: maybeCollectInteractiveParams, isInteractive, collectForm, newCommandStateWithDefaults, and withFallbacks.internal/core/model/preparation.go (2)
53-54: Wrap close errors with operation context.Returning the raw error here loses call-site context; wrapping makes failures easier to diagnose.
Suggested change
import ( "context" + "fmt" "github.com/compozy/compozy/internal/core/run/journal" ) @@ handle := p.JournalHandle if err := handle.Close(ctx); err != nil { - return err + return fmt.Errorf("close preparation journal: %w", err) }As per coding guidelines, "Prefer explicit error returns with wrapped context using
fmt.Errorf(\"context: %w\", err)".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/model/preparation.go` around lines 53 - 54, The current return of the raw error from handle.Close(ctx) should be wrapped with operation context; change the error return in the function that calls handle.Close(ctx) to return fmt.Errorf("closing handle: %w", err) (or another clear contextual string) and add a fmt import if missing so the caller receives a wrapped error with call-site context.
9-12: Add compile-time interface conformance assertion.Since
SetJournalrelies onjournal.NewOwner, add an explicit compile-time check to prevent future interface drift.Suggested addition
type JournalHandle interface { Journal() *journal.Journal Close(context.Context) error } + +var _ JournalHandle = (*journal.Owner)(nil)As per coding guidelines, "Use compile-time interface verification in Go:
var _ Interface = (*Type)(nil)".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/model/preparation.go` around lines 9 - 12, Add a compile-time interface conformance check to ensure the concrete type produced by journal.NewOwner implements the JournalHandle interface used by SetJournal: add a package-level var declaration asserting JournalHandle is satisfied by the concrete owner type returned from journal.NewOwner (use the concrete type name from the journal package, e.g. the Owner type) so future interface drift causes a compile error.internal/core/agent/client.go (1)
537-545: Consider propagating the caller's context instead ofcontext.Background().Using
context.Background()in production code prevents callers from cancelling or timing out launch command resolution. The callerensureStartedreceives actxparameter that could be propagated here.If the decoupling from caller cancellation is intentional (e.g., to ensure resolution completes once started), consider documenting this design decision with a comment explaining why the caller's context is not used.
As per coding guidelines: "Pass
context.Contextas the first argument to all functions crossing runtime boundaries; avoidcontext.Background()outsidemainand focused tests."💡 Option to propagate context
func (c *clientImpl) resolveStartCommand(req SessionRequest) (string, []string, error) { +// Note: This method would need ctx as a parameter if propagation is desired requestedModel := resolveModel(c.spec, firstNonEmpty(req.Model, c.cfg.Model)) startModel := c.spec.DefaultModel if c.spec.UsesBootstrapModel { startModel = requestedModel } command, err := resolveLaunchCommand( - context.Background(), + ctx, // propagated from caller c.spec,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/agent/client.go` around lines 537 - 545, The call to resolveLaunchCommand uses context.Background() which prevents caller cancellation; update ensureStarted to pass its incoming ctx through to resolveLaunchCommand (replace context.Background() with the ensureStarted ctx) so resolution honors timeouts/cancellation, referencing resolveLaunchCommand and ensureStarted and the existing parameters (c.spec, startModel, c.cfg.ReasoningEffort, c.cfg.AddDirs, c.cfg.AccessMode); if the behavior of decoupling is intentional, instead add a brief comment above the call explaining why a detached context is required and the decision rationale.
🤖 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/contentblock/engine.go`:
- Around line 86-87: The code calls validate(envelope.Type, data) without
checking if validate is nil and returns the validator error without context; add
a nil-check for the validate function pointer and return a clear wrapped error
if it's missing (e.g., return Envelope[T]{}, fmt.Errorf("no validator for type
%s", envelope.Type)), and when validate returns an error wrap it with context
including envelope.Type using fmt.Errorf("validate %s: %w", envelope.Type, err);
ensure fmt is imported and update the call site around validate(...) and the
Envelope[T] return accordingly.
- Around line 100-111: The code calls the blockType callback without checking
for nil (unlike normalize); add a nil guard before invoking blockType in the
decode function: verify blockType != nil and return a descriptive error (e.g.,
"decode <expected> block: missing blockType callback") if it's nil, then proceed
to call blockType(block) and compare to expected; reference the blockType and
normalize parameters and the existing error-return pattern used after
json.Unmarshal to keep behavior consistent.
In `@internal/core/agent/registry_launch.go`:
- Around line 63-87: EnsureAvailable may dereference a nil cfg (cfg.DryRun)
before the ctx nil-check; add an explicit nil check for cfg at the start of
EnsureAvailable (e.g., if cfg == nil { return fmt.Errorf("nil RuntimeConfig") })
and return a clear error, preserving the existing ctx nil-default logic and
subsequent calls to lookupAgentSpec and resolveLaunchCommand; reference
EnsureAvailable and cfg.DryRun when making the change.
In `@internal/core/migration/workflow_target_test.go`:
- Around line 39-40: Replace string-based error checks by introducing a sentinel
error (e.g., ErrInvalidWorkflowName) in the package, have normalizeWorkflowName
wrap/return that sentinel (use fmt.Errorf("%w: %s", ErrInvalidWorkflowName,
details) or similar) so callers can detect the failure, and update the test in
workflow_target_test.go to assert with errors.Is(err, ErrInvalidWorkflowName)
instead of strings.Contains(err.Error(), ...); apply the same
replace-for-string-match change in the other tests noted in migrate_test.go that
check the same validation.
In `@internal/core/model/preparation_test.go`:
- Around line 13-62: Split the two top-level tests
TestSolvePreparationSetJournalPreservesExistingOwnership and
TestSolvePreparationCloseJournalPreservesHandleOnFailure into t.Run subtests
(preferably table-driven): create a table of cases describing the scenario and
expected outcome and for each case call t.Run("Should <description>", func(t
*testing.T){ ... }); move the existing setup/assert logic into the subtest body
and preserve use of SolvePreparation.SetJournal, SolvePreparation.Journal(),
CloseJournal, and the stubJournalHandle (err) to drive the failure case; ensure
each subtest calls t.Parallel() where appropriate and that the original
assertions remain unchanged.
- Around line 55-58: The test currently only checks err != nil for
prep.CloseJournal; instead define or reuse a sentinel error (e.g.,
ErrCloseJournalFailed) that the CloseJournal implementation returns on the
failure path, then update the test to assert the exact error using
errors.Is(err, ErrCloseJournalFailed) (or testing helper
require.ErrorIs/Assert.ErrorIs) against prep.CloseJournal; ensure the sentinel
is exported/visible to the test or placed in a shared test-only package and that
CloseJournal returns that sentinel in the failure scenario.
---
Nitpick comments:
In `@internal/cli/root_test.go`:
- Around line 1619-1628: The failOnWriteWriter.Write fallback returns
io.ErrClosedPipe when w.err is nil, which can mask real underlying errors;
modify the Write method on the failOnWriteWriter type to return a more
informative default by returning the underlying w.err when present and otherwise
return a distinct sentinel (e.g., nil or a new explicit error) that clearly
signals "no underlying error set" so callers/tests won't be misled—update the
Write method on failOnWriteWriter (referenced by type name failOnWriteWriter and
its Write method) to return w.err when non-nil and a clearer fallback error when
nil.
In `@internal/cli/run.go`:
- Around line 13-67: Extract the shared setup flow from run and exec into a
helper (e.g., prepareAndRun) that performs signalCommandContext,
applyWorkspaceDefaults, buildConfig, applyPersistedExecConfig, cfg.Validate and
runPrepared, and accept a small setupFn parameter so callers can inject the
differing step (call maybeCollectInteractiveParams for run and
resolveExecPromptSource for exec); ensure the helper uses the same
error-wrapping/handling via handleExecError and preserves the special
applyWorkspaceDefaults error message formatting used in exec, then replace the
bodies of run and exec to call this new prepareAndRun helper.
In `@internal/cli/state.go`:
- Around line 107-134: The method withFallbacks currently mutates its value
receiver (commandStateDefaults) and returns it, which is confusing; either
convert it to an in-place pointer receiver or make it purely functional. To fix:
choose one approach — (A) change the receiver to *commandStateDefaults
(withFallbacks) and remove the return value so the method mutates the struct in
place, updating all call sites to rely on mutation; or (B) keep a value receiver
but avoid mutating the parameter by creating a new local variable (e.g., result
:= defaults), set fallbacks on result using defaultCommandStateDefaults(), and
return result; then keep existing call sites that expect a returned value.
Ensure you update all references to withFallbacks accordingly.
- Around line 225-249: The nil checks for isInteractive and collectForm in
maybeCollectInteractiveParams are redundant because newCommandStateWithDefaults
calls withFallbacks() which guarantees those callbacks are set; remove the
conditional assignments and directly call s.isInteractive() and
s.collectForm(cmd, s) (or, if you prefer to keep the defensive pattern, add a
short comment above the checks referencing newCommandStateWithDefaults and
withFallbacks to explain they are only defensive for non-standard construction).
Update maybeCollectInteractiveParams to use the guaranteed callbacks
(isInteractive and collectForm) and simplify the error/flow accordingly,
referencing the methods/fields: maybeCollectInteractiveParams, isInteractive,
collectForm, newCommandStateWithDefaults, and withFallbacks.
In `@internal/core/agent/client.go`:
- Around line 537-545: The call to resolveLaunchCommand uses
context.Background() which prevents caller cancellation; update ensureStarted to
pass its incoming ctx through to resolveLaunchCommand (replace
context.Background() with the ensureStarted ctx) so resolution honors
timeouts/cancellation, referencing resolveLaunchCommand and ensureStarted and
the existing parameters (c.spec, startModel, c.cfg.ReasoningEffort,
c.cfg.AddDirs, c.cfg.AccessMode); if the behavior of decoupling is intentional,
instead add a brief comment above the call explaining why a detached context is
required and the decision rationale.
In `@internal/core/model/preparation.go`:
- Around line 53-54: The current return of the raw error from handle.Close(ctx)
should be wrapped with operation context; change the error return in the
function that calls handle.Close(ctx) to return fmt.Errorf("closing handle: %w",
err) (or another clear contextual string) and add a fmt import if missing so the
caller receives a wrapped error with call-site context.
- Around line 9-12: Add a compile-time interface conformance check to ensure the
concrete type produced by journal.NewOwner implements the JournalHandle
interface used by SetJournal: add a package-level var declaration asserting
JournalHandle is satisfied by the concrete owner type returned from
journal.NewOwner (use the concrete type name from the journal package, e.g. the
Owner type) so future interface drift causes a compile error.
🪄 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: 56010523-90d4-4256-a16a-88fcaf3dcc2a
⛔ Files ignored due to path filters (20)
.compozy/tasks/refac/reviews-001/_meta.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_001.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_002.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_003.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_004.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_005.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_006.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_007.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_008.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_009.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_010.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_011.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_012.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_013.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_014.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_015.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_016.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_017.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_018.mdis excluded by!**/*.md.compozy/tasks/refac/reviews-001/issue_019.mdis excluded by!**/*.md
📒 Files selected for processing (25)
internal/cli/root_test.gointernal/cli/run.gointernal/cli/state.gointernal/contentblock/engine.gointernal/contentblock/engine_test.gointernal/core/agent/client.gointernal/core/agent/registry_launch.gointernal/core/agent/registry_specs.gointernal/core/agent/registry_test.gointernal/core/agent/registry_validate.gointernal/core/agent/session_helpers_test.gointernal/core/migration/migrate.gointernal/core/migration/migrate_test.gointernal/core/migration/workflow_target.gointernal/core/migration/workflow_target_test.gointernal/core/model/artifacts.gointernal/core/model/content_test.gointernal/core/model/model_test.gointernal/core/model/preparation.gointernal/core/model/preparation_test.gointernal/core/plan/prepare.gointernal/core/prompt/prompt_test.gointernal/core/reviews/parser.gointernal/core/reviews/parser_test.gointernal/core/run/exec/exec.go
✅ Files skipped from review due to trivial changes (3)
- internal/core/model/content_test.go
- internal/core/migration/workflow_target.go
- internal/core/migration/migrate.go
🚧 Files skipped from review as they are similar to previous changes (2)
- internal/core/migration/migrate_test.go
- internal/core/agent/registry_validate.go
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
.gitignore (1)
16-16: Use a directory-specific ignore pattern for clarity.At Line 16,
ai-docsalso ignores a file namedai-docs. If the intent is directory-only, preferai-docs/to avoid accidental overmatching.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.gitignore at line 16, Change the .gitignore entry "ai-docs" to a directory-specific pattern "ai-docs/" so the rule only ignores the ai-docs directory (not a file named ai-docs); update the entry referenced as "ai-docs" in the .gitignore accordingly.
🤖 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/core/agent/registry_test.go`:
- Around line 587-695: These tests violate the project's t.Run("Should...")
subtest pattern—refactor each standalone test
(TestValidateRuntimeConfigRejectsNilConfig, TestEnsureAvailableRejectsNilConfig,
TestValidateRuntimeConfigAcceptsResolvedPromptTextAsExecPromptSource,
TestBuildShellCommandStringShellEscapesInterpolatedArguments,
TestEnsureAvailableHonorsCallerContext,
TestJoinAvailabilityErrorsPreservesAvailabilityErrorTypes) to use the required
subtest pattern: either convert them into a single table-driven test with
multiple t.Run("Should ...", func(t *testing.T){ ... }) entries or wrap the
existing body inside a t.Run("Should <behavior>", func(t *testing.T){ ... })
call; keep the existing assertions and setup (e.g., calls to
ValidateRuntimeConfig, EnsureAvailable, BuildShellCommandString,
joinAvailabilityErrors, registerTestSpec) unchanged but move them into the
corresponding subtest functions and preserve t.Parallel where appropriate inside
each subtest.
- Around line 590-603: Add a package-level sentinel error (e.g., var
ErrRuntimeConfigNil = errors.New("runtime config is nil")) and replace the
string-based error checks and string-returned errors with that sentinel: change
the error returns in the functions that currently return the "runtime config is
nil" message (refer to the error returns in registry_validate.go and
registry_launch.go) to return ErrRuntimeConfigNil, and update the tests that
call ValidateRuntimeConfig and EnsureAvailable to use errors.Is(err,
ErrRuntimeConfigNil) (or errors.As where appropriate) instead of
strings.Contains(err.Error(), ...); ensure imports include "errors" where added.
---
Nitpick comments:
In @.gitignore:
- Line 16: Change the .gitignore entry "ai-docs" to a directory-specific pattern
"ai-docs/" so the rule only ignores the ai-docs directory (not a file named
ai-docs); update the entry referenced as "ai-docs" in the .gitignore
accordingly.
🪄 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: 62449c06-d6a4-41ea-b6b1-52272ad8e4eb
⛔ Files ignored due to path filters (35)
ai-docs/reviews-pr-60/_summary.mdis excluded by!**/*.mdai-docs/reviews-pr-60/issues/001-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-60/issues/002-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-60/issues/003-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-60/issues/004-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-60/issues/005-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-60/issues/006-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-60/issues/007-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-60/issues/008-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-60/issues/009-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-60/issues/010-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-60/issues/011-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-60/outside/002-outside_01_internal_core_run_execution.go.mdis excluded by!**/*.mdai-docs/reviews-pr-60/outside/004-outside_01_internal_core_workspace_config.go.mdis excluded by!**/*.mdai-docs/reviews-pr-68/_manual-triage.mdis excluded by!**/*.mdai-docs/reviews-pr-68/_summary.mdis excluded by!**/*.mdai-docs/reviews-pr-68/issues/001-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-68/issues/002-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-68/issues/003-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-68/issues/004-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-68/issues/005-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-68/issues/006-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-68/issues/007-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-68/issues/008-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-68/outside/002-outside_01_internal_core_agent_session.go.mdis excluded by!**/*.mdai-docs/reviews-pr-68/outside/002-outside_02_internal_core_run_logging.go.mdis excluded by!**/*.mdai-docs/reviews-pr-68/outside/003-outside_01_internal_core_run_execution_ui_test.go.mdis excluded by!**/*.mdai-docs/reviews-pr-70/_summary.mdis excluded by!**/*.mdai-docs/reviews-pr-70/issues/001-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-70/issues/002-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-70/issues/003-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-70/issues/004-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-70/issues/005-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-70/issues/006-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-70/outside/002-outside_01_internal_core_plan_journal.go.mdis excluded by!**/*.md
📒 Files selected for processing (9)
.gitignoreinternal/contentblock/engine.gointernal/contentblock/engine_test.gointernal/core/agent/registry_launch.gointernal/core/agent/registry_test.gointernal/core/migration/workflow_target.gointernal/core/migration/workflow_target_test.gointernal/core/model/preparation_test.gointernal/core/plan/journal.go
🚧 Files skipped from review as they are similar to previous changes (2)
- internal/contentblock/engine.go
- internal/core/agent/registry_launch.go
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
internal/core/agent/tool_call_name.go (1)
169-179:⚠️ Potential issue | 🟡 MinorRedundant condition on line 176 always evaluates to true.
Within this block,
refID != ""is already guaranteed true from the check on line 169. The conditionextractString(input, "url") != "" || refID != ""on line 176 will always be true if the pattern check on line 173 fails, causingOpenURLto be returned unconditionally.If the intent is to return
OpenURLonly when aurlkey is present, simplify the logic:🐛 Proposed fix
if refID := extractString(input, "ref_id", "refId"); refID != "" { if _, ok := extractInt(input, "id"); ok { return toolNameClick } if extractString(input, "pattern") != "" { return toolNameFind } - if extractString(input, "url") != "" || refID != "" { - return toolNameOpenURL - } + return toolNameOpenURL }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/agent/tool_call_name.go` around lines 169 - 179, The branch in the tool name resolver incorrectly uses "extractString(input, \"url\") != \"\" || refID != \"\"" which is redundant because refID is already known non-empty (set by the outer if), causing toolNameOpenURL to be returned unconditionally; update the final conditional in the block that references refID (the one after extractInt and extractString("pattern")) to check only for a non-empty URL (i.e., use extractString(input, "url") != "") so OpenURL is returned only when a url key is present; keep the existing extractInt, extractString("pattern") checks and the outer refID check intact and ensure you reference extractString, extractInt, refID, toolNameClick, toolNameFind, and toolNameOpenURL when making the change.
🧹 Nitpick comments (5)
internal/core/agent/acp_convert.go (1)
466-471: Consider handling edge cases in diff rendering.The
renderDiffTextfunction generates unified-diff-style output but doesn't escape special characters inpathor content. If paths contain newlines or the content doesn't end with a newline, the output may be malformed. This is likely acceptable for display purposes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/agent/acp_convert.go` around lines 466 - 471, The renderDiffText function can produce malformed diffs when path contains newlines or when newText/oldText don't end with a newline; update renderDiffText to sanitize the path (e.g., replace or escape newline characters and other control characters in the path parameter) and normalize content by ensuring newText and *oldText end with a trailing newline (append "\n" if missing) before formatting the diff; reference the renderDiffText(path string, newText string, oldText *string) function and apply these transformations to path, newText, and oldText prior to the fmt.Sprintf calls.internal/core/agent/tool_call_name.go (1)
219-229:looksLikeWebSearchInputmay trigger false positives.The presence of a
urlkey alone triggers web-search detection, buturlis also used forWebFetchandOpenURLtools. This could cause incorrect categorization innormalizeToolNameByKind(line 116-118) for fetch-like inputs.The current ordering in
inferToolNameFromInputShape(line 166 calls this before theurl-only check on line 180) mitigates this for that path. However,normalizeToolNameByKindat line 116 may still misclassify a pure fetch URL as web search when kind isToolKindSearch.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/agent/tool_call_name.go` around lines 219 - 229, looksLikeWebSearchInput currently treats any input with a "url" key as a web search which causes false positives for fetch-like tools; update the logic so a lone "url" does not trigger web-search detection — either remove "url" from the top-level key list in looksLikeWebSearchInput or change the check to require "url" plus a query-like key (e.g., "query", "queries", "search_query", "action_query") before returning true; also ensure normalizeToolNameByKind (used when kind == ToolKindSearch) continues to prefer WebFetch/OpenURL for url-only inputs if you choose the list change, so inferToolNameFromInputShape, normalizeToolNameByKind, and looksLikeWebSearchInput remain consistent.cmd/compozy/main_test.go (1)
10-56: Convert this into a table-driven test.The three subtests all exercise the same helper and mostly vary by channel setup and expected result, so a small table would remove duplicated harness code and make the next case easier to add.
As per coding guidelines,
Use table-driven tests with subtests (t.Run) as the default pattern for Go tests.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmd/compozy/main_test.go` around lines 10 - 56, Refactor TestWaitForUpdateResult into a table-driven test that iterates over cases describing the channel setup and expected result; keep using subtests via t.Run for each case, and exercise waitForUpdateResult with inputs such as (1) a buffered result channel pre-filled with &update.ReleaseInfo{Version:"v1.2.3"} expecting that pointer, (2) a closed unbuffered channel expecting nil, and (3) a never-sent channel with a goroutine reading waitForUpdateResult and a timeout using updateResultWaitTimeout expecting nil; preserve parallelization (t.Parallel()) appropriately and reference TestWaitForUpdateResult, waitForUpdateResult, update.ReleaseInfo and updateResultWaitTimeout when locating the code to change.cmd/compozy/main.go (1)
41-61: Accept a caller-owned context and explicit completion signal here.
startUpdateCheckcreates its owncontext.Background()and only returnscancel, so the caller cannot tie this goroutine to the root CLI context or wait for it to finish. Sinceupdate.CheckForUpdatedoes real state-file and network I/O, that leaves shutdown implicit.As per coding guidelines,
Pass context.Context as the first argument to all functions crossing runtime boundaries; avoid context.Background() outside main and focused tests,Every goroutine must have explicit ownership and shutdown via context.Context cancellation, andNo fire-and-forget goroutines; track all goroutines with sync.WaitGroup or equivalent.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmd/compozy/main.go` around lines 41 - 61, startUpdateCheck currently creates its own background context and spawns a fire-and-forget goroutine; change its signature to accept a caller-owned context (e.g., startUpdateCheck(ctx context.Context, currentVersion string) ...) and create a derived ctx via context.WithCancel(ctx), pass that ctx into update.CheckForUpdate, and return the cancel function. Also add an explicit completion signal (e.g., return a done channel or WaitGroup notification) that the goroutine closes/signals when it finishes so callers can wait for shutdown; keep usage of update.StateFilePath and update.CheckForUpdate unchanged except for using the provided ctx and ensure the goroutine closes the result channel and the done signal on all exit paths.internal/cli/workspace_config_test.go (1)
426-432: Rename these subtests to theShould...form.This is the only new case table in the file that does not follow the repo’s subtest naming convention.
As per coding guidelines,
MUST use t.Run("Should...") pattern for ALL test cases.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cli/workspace_config_test.go` around lines 426 - 432, The subtest names in the cases table (variable cases containing entries with name and base referencing migrateState.simpleCommandBase, syncState.simpleCommandBase, archiveState.simpleCommandBase) must be renamed to the "Should..." form; update each name string (e.g., change "migrate", "sync", "archive") to descriptive "Should ..." phrases (e.g., "Should migrate workspace", "Should sync workspace", "Should archive workspace") so that t.Run uses the required t.Run("Should...") pattern for all entries using the cases slice and simpleCommandBase references.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cmd/compozy/main.go`:
- Around line 31-33: The code currently discards the error from fmt.Fprintln
when writing the update notification; change it to capture and handle the error
from fmt.Fprintln(cmd.ErrOrStderr(), renderUpdateNotification(version.Version,
release)) (called after waitForUpdateResult and renderUpdateNotification) —
assign the return error to a variable, check if err != nil, and then handle it
(for example, write a descriptive error message to stderr via cmd.ErrOrStderr()
or propagate/return the error from main), optionally ignoring benign
broken-pipe/EPIPE errors if appropriate.
In `@internal/cli/state.go`:
- Around line 255-261: The code currently accepts zero or negative durations
from s.timeout because time.ParseDuration allows them; update the parsing logic
in the function that builds the core.Config (where s.timeout is parsed into
timeoutDuration) to validate parsed > 0 and return an error when parsed <= 0,
e.g., after calling time.ParseDuration(s.timeout) check the parsed value and
return fmt.Errorf("invalid timeout: must be > 0") (or similar) rather than using
a non-positive timeoutDuration; keep the variable names timeoutDuration,
s.timeout and the time.ParseDuration call so the change is localized.
- Around line 226-252: The interactivity gate in maybeCollectInteractiveParams
blocks the exec command when a positional prompt exists because
cmd.Flags().NFlag() only counts flags; update prepareAndRun/exec flow so
resolveExecPromptSource is called (or positional prompt presence is checked)
before calling maybeCollectInteractiveParams, or alter
maybeCollectInteractiveParams to skip the interactive-terminal check when
resolveExecPromptSource (or a positional prompt) indicates a provided prompt;
specifically locate prepareAndRun and the exec path that calls
resolveExecPromptSource and reorder or add a conditional that if
resolveExecPromptSource returns a valid prompt (or if positional args exist)
then do not enforce the isInteractive() check that uses cmd.Flags().NFlag().
- Around line 371-423: The method resolveExecPromptSource leaves s.promptFile
from a prior run which can make the exclusivity check or behavior incorrect;
ensure s.promptFile is cleared unless the current chosen source is the
prompt-file. Specifically, after computing local promptFile :=
strings.TrimSpace(s.promptFile) (and before the sourceCount logic) or
immediately when taking the positionalPrompt or stdin branch, set s.promptFile =
"" so stale state is removed; when the prompt-file branch wins keep the existing
assignment s.promptFile = promptFile as currently done. This guarantees
s.promptFile only reflects the active source and prevents false positives in the
exclusivity check.
---
Duplicate comments:
In `@internal/core/agent/tool_call_name.go`:
- Around line 169-179: The branch in the tool name resolver incorrectly uses
"extractString(input, \"url\") != \"\" || refID != \"\"" which is redundant
because refID is already known non-empty (set by the outer if), causing
toolNameOpenURL to be returned unconditionally; update the final conditional in
the block that references refID (the one after extractInt and
extractString("pattern")) to check only for a non-empty URL (i.e., use
extractString(input, "url") != "") so OpenURL is returned only when a url key is
present; keep the existing extractInt, extractString("pattern") checks and the
outer refID check intact and ensure you reference extractString, extractInt,
refID, toolNameClick, toolNameFind, and toolNameOpenURL when making the change.
---
Nitpick comments:
In `@cmd/compozy/main_test.go`:
- Around line 10-56: Refactor TestWaitForUpdateResult into a table-driven test
that iterates over cases describing the channel setup and expected result; keep
using subtests via t.Run for each case, and exercise waitForUpdateResult with
inputs such as (1) a buffered result channel pre-filled with
&update.ReleaseInfo{Version:"v1.2.3"} expecting that pointer, (2) a closed
unbuffered channel expecting nil, and (3) a never-sent channel with a goroutine
reading waitForUpdateResult and a timeout using updateResultWaitTimeout
expecting nil; preserve parallelization (t.Parallel()) appropriately and
reference TestWaitForUpdateResult, waitForUpdateResult, update.ReleaseInfo and
updateResultWaitTimeout when locating the code to change.
In `@cmd/compozy/main.go`:
- Around line 41-61: startUpdateCheck currently creates its own background
context and spawns a fire-and-forget goroutine; change its signature to accept a
caller-owned context (e.g., startUpdateCheck(ctx context.Context, currentVersion
string) ...) and create a derived ctx via context.WithCancel(ctx), pass that ctx
into update.CheckForUpdate, and return the cancel function. Also add an explicit
completion signal (e.g., return a done channel or WaitGroup notification) that
the goroutine closes/signals when it finishes so callers can wait for shutdown;
keep usage of update.StateFilePath and update.CheckForUpdate unchanged except
for using the provided ctx and ensure the goroutine closes the result channel
and the done signal on all exit paths.
In `@internal/cli/workspace_config_test.go`:
- Around line 426-432: The subtest names in the cases table (variable cases
containing entries with name and base referencing
migrateState.simpleCommandBase, syncState.simpleCommandBase,
archiveState.simpleCommandBase) must be renamed to the "Should..." form; update
each name string (e.g., change "migrate", "sync", "archive") to descriptive
"Should ..." phrases (e.g., "Should migrate workspace", "Should sync workspace",
"Should archive workspace") so that t.Run uses the required t.Run("Should...")
pattern for all entries using the cases slice and simpleCommandBase references.
In `@internal/core/agent/acp_convert.go`:
- Around line 466-471: The renderDiffText function can produce malformed diffs
when path contains newlines or when newText/oldText don't end with a newline;
update renderDiffText to sanitize the path (e.g., replace or escape newline
characters and other control characters in the path parameter) and normalize
content by ensuring newText and *oldText end with a trailing newline (append
"\n" if missing) before formatting the diff; reference the renderDiffText(path
string, newText string, oldText *string) function and apply these
transformations to path, newText, and oldText prior to the fmt.Sprintf calls.
In `@internal/core/agent/tool_call_name.go`:
- Around line 219-229: looksLikeWebSearchInput currently treats any input with a
"url" key as a web search which causes false positives for fetch-like tools;
update the logic so a lone "url" does not trigger web-search detection — either
remove "url" from the top-level key list in looksLikeWebSearchInput or change
the check to require "url" plus a query-like key (e.g., "query", "queries",
"search_query", "action_query") before returning true; also ensure
normalizeToolNameByKind (used when kind == ToolKindSearch) continues to prefer
WebFetch/OpenURL for url-only inputs if you choose the list change, so
inferToolNameFromInputShape, normalizeToolNameByKind, and
looksLikeWebSearchInput remain consistent.
🪄 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: 43459a78-dad3-463c-bbd8-729d097922b5
⛔ Files ignored due to path filters (1)
.codex/ledger/2026-04-07-MEMORY-review-nitpicks.mdis excluded by!**/*.md
📒 Files selected for processing (15)
cmd/compozy/main.gocmd/compozy/main_test.gointernal/cli/kernel_dispatch_test.gointernal/cli/root_test.gointernal/cli/run.gointernal/cli/state.gointernal/cli/workspace_config_test.gointernal/core/agent/acp_convert.gointernal/core/agent/client.gointernal/core/agent/session_helpers_test.gointernal/core/agent/tool_call_input.gointernal/core/agent/tool_call_name.gointernal/core/kernel/core_adapters_test.gointernal/core/kernel/deps_test.gointernal/core/model/preparation.go
🚧 Files skipped from review as they are similar to previous changes (4)
- internal/core/agent/client.go
- internal/core/agent/session_helpers_test.go
- internal/cli/root_test.go
- internal/cli/run.go
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
internal/core/agent/registry_launch.go (1)
74-85:⚠️ Potential issue | 🟠 MajorStill use the effective model for availability probes.
Line 81 still hardcodes
spec.DefaultModel, soEnsureAvailablenever validates a caller-suppliedcfg.Model. If bootstrap args depend on the model, the probe can pass and the real launch can still fail later.💡 Suggested fix
spec, err := lookupAgentSpec(cfg.IDE) if err != nil { return err } + effectiveModel := resolveModel(spec, cfg.Model) if _, err := resolveLaunchCommand( ctx, spec, - spec.DefaultModel, + effectiveModel, cfg.ReasoningEffort, cfg.AddDirs, cfg.AccessMode, true,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/agent/registry_launch.go` around lines 74 - 85, Ensure the availability probe uses the caller-specified effective model instead of always passing spec.DefaultModel: inside the EnsureAvailable / registry launch flow replace the hardcoded spec.DefaultModel argument to resolveLaunchCommand with the effective model from the config (e.g., cfg.Model or a computed effectiveModel derived from cfg and spec) so that resolveLaunchCommand (called in EnsureAvailable) validates the same model the real launch will use.
🧹 Nitpick comments (3)
internal/core/agent/registry_launch.go (1)
141-149: Wrap theLookPathfailure instead of replacing it.Line 148 drops the original
exec.LookPatherror and hardcodes a “not found” message, so callers loseerrors.Is/Asagainst the real cause and can also get a misleading message for non-PATH failures.💡 Suggested fix
if _, err := exec.LookPath(command[0]); err != nil { return &AvailabilityError{ IDE: spec.ID, DisplayName: spec.DisplayName, Command: command, DocsURL: spec.DocsURL, InstallHint: spec.InstallHint, - Cause: fmt.Errorf("command %q was not found on PATH", command[0]), + Cause: fmt.Errorf("resolving command %q on PATH: %w", command[0], err), } }As per coding guidelines, "Prefer explicit error returns with wrapped context using
fmt.Errorf("context: %w", err). Useerrors.Is()anderrors.As()for error matching; do not compare error strings."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/agent/registry_launch.go` around lines 141 - 149, The current LookPath error is being replaced with a hardcoded message which loses the original error context; update the code in registry_launch.go (the exec.LookPath check) to wrap the original err when constructing the AvailabilityError Cause (use fmt.Errorf("command %q: %w", command[0], err) or equivalent) so callers can use errors.Is/errors.As and see the actual underlying failure while preserving the descriptive "command X" context in the AvailabilityError returned by the function that constructs AvailabilityError.internal/core/agent/acp_convert.go (1)
15-20: Wrap helper failures with branch context.These early returns pass helper errors through unchanged, so a failure loses whether it came from message conversion or tool-lifecycle conversion.
Suggested change
func convertACPUpdate(driverID string, update acp.SessionUpdate) (model.SessionUpdate, error) { if converted, ok, err := convertACPMessageUpdate(update); ok || err != nil { - return converted, err + if err != nil { + return model.SessionUpdate{}, fmt.Errorf("convert ACP message update: %w", err) + } + return converted, nil } if converted, ok, err := convertACPToolLifecycleUpdate(driverID, update); ok || err != nil { - return converted, err + if err != nil { + return model.SessionUpdate{}, fmt.Errorf("convert ACP tool lifecycle update: %w", err) + } + return converted, nil }As per coding guidelines, "Prefer explicit error returns with wrapped context using
fmt.Errorf("context: %w", err)in Go code".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/agent/acp_convert.go` around lines 15 - 20, The helper errors from convertACPMessageUpdate and convertACPToolLifecycleUpdate must be returned with contextual wrapping instead of passed through; change the early-return logic to first check ok and return the converted value when ok is true, and if err != nil return nil, fmt.Errorf("message conversion failed: %w", err) for convertACPMessageUpdate (and similarly fmt.Errorf("tool-lifecycle conversion failed: %w", err) for convertACPToolLifecycleUpdate), ensuring you import fmt if needed and preserve the original converted return when ok is true.internal/cli/state.go (1)
311-314: Wrap persisted-run load failures with local context.The bare
return errat line 313 drops which--run-idwas being resumed, making CLI errors harder to diagnose. The file already usesfmt.Errorfconsistently elsewhere for error context.🩹 Suggested change
record, err := coreRun.LoadPersistedExecRun(s.workspaceRoot, s.runID) if err != nil { - return err + return fmt.Errorf("load persisted exec run %q: %w", s.runID, err) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cli/state.go` around lines 311 - 314, Wrap the error returned from coreRun.LoadPersistedExecRun with contextual information about which run was being resumed: replace the bare return err after calling coreRun.LoadPersistedExecRun(s.workspaceRoot, s.runID) with a fmt.Errorf that includes s.runID (and optionally s.workspaceRoot) and uses %w to wrap the original err so callers retain the original error while gaining context.
🤖 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/root_test.go`:
- Around line 836-848: Replace the string-based error check with an errors.Is
assertion against a sentinel error: introduce a package-level sentinel variable
(e.g., var brokenStdout = errors.New("broken stdout")), use that sentinel when
constructing the failOnWriteWriter in the test (failOnWriteWriter{err:
brokenStdout}), call state.fetchReviews(cmd, nil) as before, then assert
errors.Is(err, brokenStdout) instead of checking err.Error() for "write fetch
summary"; keep the existing check for the wrapper message if you still want to
validate the outer text but ensure the underlying writer error is asserted via
errors.Is.
In `@internal/core/agent/registry_launch.go`:
- Around line 186-192: The shell-escaping logic in formatShellArg incorrectly
substitutes embedded single quotes with '\"'\"' which is not valid POSIX syntax;
update the replacement so embedded "'" are escaped using the correct pattern
'"'\'"'" (i.e. replace occurrences of "'" with `'"'"'`) so the function returns
a valid single-quoted string like "'" + strings.ReplaceAll(arg, "'", `'"'"'`) +
"'" and ensures inputs like O'Connor produce a well-formed quoted argument.
---
Duplicate comments:
In `@internal/core/agent/registry_launch.go`:
- Around line 74-85: Ensure the availability probe uses the caller-specified
effective model instead of always passing spec.DefaultModel: inside the
EnsureAvailable / registry launch flow replace the hardcoded spec.DefaultModel
argument to resolveLaunchCommand with the effective model from the config (e.g.,
cfg.Model or a computed effectiveModel derived from cfg and spec) so that
resolveLaunchCommand (called in EnsureAvailable) validates the same model the
real launch will use.
---
Nitpick comments:
In `@internal/cli/state.go`:
- Around line 311-314: Wrap the error returned from coreRun.LoadPersistedExecRun
with contextual information about which run was being resumed: replace the bare
return err after calling coreRun.LoadPersistedExecRun(s.workspaceRoot, s.runID)
with a fmt.Errorf that includes s.runID (and optionally s.workspaceRoot) and
uses %w to wrap the original err so callers retain the original error while
gaining context.
In `@internal/core/agent/acp_convert.go`:
- Around line 15-20: The helper errors from convertACPMessageUpdate and
convertACPToolLifecycleUpdate must be returned with contextual wrapping instead
of passed through; change the early-return logic to first check ok and return
the converted value when ok is true, and if err != nil return nil,
fmt.Errorf("message conversion failed: %w", err) for convertACPMessageUpdate
(and similarly fmt.Errorf("tool-lifecycle conversion failed: %w", err) for
convertACPToolLifecycleUpdate), ensuring you import fmt if needed and preserve
the original converted return when ok is true.
In `@internal/core/agent/registry_launch.go`:
- Around line 141-149: The current LookPath error is being replaced with a
hardcoded message which loses the original error context; update the code in
registry_launch.go (the exec.LookPath check) to wrap the original err when
constructing the AvailabilityError Cause (use fmt.Errorf("command %q: %w",
command[0], err) or equivalent) so callers can use errors.Is/errors.As and see
the actual underlying failure while preserving the descriptive "command X"
context in the AvailabilityError returned by the function that constructs
AvailabilityError.
🪄 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: f654462e-86da-4fd0-88fe-61e9cd484e44
⛔ Files ignored due to path filters (15)
.agents/skills/fix-coderabbit-review/SKILL.mdis excluded by!**/*.md,!.agents/**.agents/skills/fix-coderabbit-review/scripts/pr-review.tsis excluded by!.agents/**.agents/skills/fix-coderabbit-review/scripts/pr_review.pyis excluded by!.agents/**.agents/skills/fix-coderabbit-review/scripts/resolve_pr_issues.pyis excluded by!.agents/**.agents/skills/fix-coderabbit-review/scripts/resolve_pr_issues.shis excluded by!.agents/**.codex/ledger/2026-04-07-MEMORY-pr70-coderabbit.mdis excluded by!**/*.md.codex/ledger/2026-04-07-MEMORY-review-fixes.mdis excluded by!**/*.mdai-docs/reviews-pr-70/_summary.mdis excluded by!**/*.mdai-docs/reviews-pr-70/issues/001-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-70/issues/002-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-70/issues/003-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-70/issues/004-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-70/issues/005-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-70/issues/006-issue.mdis excluded by!**/*.mdai-docs/reviews-pr-70/outside/002-outside_01_internal_core_plan_journal.go.mdis excluded by!**/*.md
📒 Files selected for processing (12)
cmd/compozy/main.gocmd/compozy/main_test.gointernal/cli/root_test.gointernal/cli/state.gointernal/cli/workspace_config_test.gointernal/core/agent/acp_convert.gointernal/core/agent/registry_launch.gointernal/core/agent/registry_test.gointernal/core/agent/registry_validate.gointernal/core/agent/session_helpers_test.gointernal/core/agent/tool_call_name.gointernal/core/agent/tool_call_name_test.go
✅ Files skipped from review due to trivial changes (1)
- cmd/compozy/main.go
🚧 Files skipped from review as they are similar to previous changes (2)
- cmd/compozy/main_test.go
- internal/cli/workspace_config_test.go
| cmd := newTestCommand(state) | ||
| cmd.SetOut(&failOnWriteWriter{err: errors.New("broken stdout")}) | ||
| if err := cmd.Flags().Set("dry-run", "true"); err != nil { | ||
| t.Fatalf("set dry-run flag: %v", err) | ||
| } | ||
|
|
||
| err := state.fetchReviews(cmd, nil) | ||
| if err == nil { | ||
| t.Fatal("expected fetchReviews to return a write error") | ||
| } | ||
| if !strings.Contains(err.Error(), "write fetch summary") { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python - <<'PY'
from pathlib import Path
text = Path("internal/cli/root_test.go").read_text()
start = text.index("func TestFetchReviewsReturnsWriterErrors")
end = text.index("func TestFormInputsApplyForFetchWorkflow")
block = text[start:end]
print(block)
print("\nasserts wrapped cause:", "errors.Is(" in block or "errors.As(" in block)
PYRepository: compozy/compozy
Length of output: 900
Assert the wrapped writer error using errors.Is().
The test validates only the wrapper message text but misses the underlying error. This means the test would still pass if fetchReviews stops wrapping the original write failure, defeating the purpose of the test.
🧪 Suggested change
- cmd.SetOut(&failOnWriteWriter{err: errors.New("broken stdout")})
+ writeErr := errors.New("broken stdout")
+ cmd.SetOut(&failOnWriteWriter{err: writeErr})
if err := cmd.Flags().Set("dry-run", "true"); err != nil {
t.Fatalf("set dry-run flag: %v", err)
}
err := state.fetchReviews(cmd, nil)
if err == nil {
t.Fatal("expected fetchReviews to return a write error")
}
+ if !errors.Is(err, writeErr) {
+ t.Fatalf("expected wrapped writer error, got %v", err)
+ }
if !strings.Contains(err.Error(), "write fetch summary") {
t.Fatalf("unexpected error: %v", err)
}Per guidelines, use errors.Is() for error matching instead of string comparison, and test code must include specific error assertions.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| cmd := newTestCommand(state) | |
| cmd.SetOut(&failOnWriteWriter{err: errors.New("broken stdout")}) | |
| if err := cmd.Flags().Set("dry-run", "true"); err != nil { | |
| t.Fatalf("set dry-run flag: %v", err) | |
| } | |
| err := state.fetchReviews(cmd, nil) | |
| if err == nil { | |
| t.Fatal("expected fetchReviews to return a write error") | |
| } | |
| if !strings.Contains(err.Error(), "write fetch summary") { | |
| t.Fatalf("unexpected error: %v", err) | |
| } | |
| cmd := newTestCommand(state) | |
| writeErr := errors.New("broken stdout") | |
| cmd.SetOut(&failOnWriteWriter{err: writeErr}) | |
| if err := cmd.Flags().Set("dry-run", "true"); err != nil { | |
| t.Fatalf("set dry-run flag: %v", err) | |
| } | |
| err := state.fetchReviews(cmd, nil) | |
| if err == nil { | |
| t.Fatal("expected fetchReviews to return a write error") | |
| } | |
| if !errors.Is(err, writeErr) { | |
| t.Fatalf("expected wrapped writer error, got %v", err) | |
| } | |
| if !strings.Contains(err.Error(), "write fetch summary") { | |
| t.Fatalf("unexpected error: %v", err) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/cli/root_test.go` around lines 836 - 848, Replace the string-based
error check with an errors.Is assertion against a sentinel error: introduce a
package-level sentinel variable (e.g., var brokenStdout = errors.New("broken
stdout")), use that sentinel when constructing the failOnWriteWriter in the test
(failOnWriteWriter{err: brokenStdout}), call state.fetchReviews(cmd, nil) as
before, then assert errors.Is(err, brokenStdout) instead of checking err.Error()
for "write fetch summary"; keep the existing check for the wrapper message if
you still want to validate the outer text but ensure the underlying writer error
is asserted via errors.Is.
| func formatShellArg(arg string) string { | ||
| if arg == "" { | ||
| return `''` | ||
| } | ||
| if strings.ContainsAny(arg, " \t\n\"'\\$`|&;<>*?[]{}()") { | ||
| return "'" + strings.ReplaceAll(arg, "'", `'\"'\"'`) + "'" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 <<'PY'
import subprocess
arg = "O'Connor"
quoted = "'" + arg.replace("'", r"'\"'\"'") + "'"
print("generated:", quoted)
proc = subprocess.run(
["sh", "-n", "-c", f"printf '%s\\n' {quoted}"],
capture_output=True,
text=True,
)
print("status:", proc.returncode)
print("stderr:", proc.stderr.strip())
PYRepository: compozy/compozy
Length of output: 152
🏁 Script executed:
fd -t f registry_launch.goRepository: compozy/compozy
Length of output: 97
🏁 Script executed:
cat -n internal/core/agent/registry_launch.go | sed -n '180,200p'Repository: compozy/compozy
Length of output: 774
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 <<'PY'
import subprocess
# Test the current (broken) implementation
arg = "O'Connor"
quoted_current = "'" + arg.replace("'", r"'\"'\"'") + "'"
print("Current implementation:")
print(f" generated: {quoted_current}")
proc = subprocess.run(
["sh", "-n", "-c", f"printf '%s\\n' {quoted_current}"],
capture_output=True,
text=True,
)
print(f" status: {proc.returncode}")
if proc.stderr:
print(f" stderr: {proc.stderr.strip()}")
# Test the suggested fix
quoted_fixed = "'" + arg.replace("'", '\'"\'"\'') + "'"
print("\nSuggested fix (using '\"'\"'):")
print(f" generated: {quoted_fixed}")
proc = subprocess.run(
["sh", "-n", "-c", f"printf '%s\\n' {quoted_fixed}"],
capture_output=True,
text=True,
)
print(f" status: {proc.returncode}")
if proc.stderr:
print(f" stderr: {proc.stderr.strip()}")
# Also verify the actual output for the fixed version
proc = subprocess.run(
["sh", "-c", f"printf '%s\\n' {quoted_fixed}"],
capture_output=True,
text=True,
)
print(f" output: {proc.stdout.strip()}")
PYRepository: compozy/compozy
Length of output: 271
Fix single-quote escaping in shell previews.
Line 191 emits '\"'\"' for embedded single quotes, which is not valid POSIX shell syntax. Inputs like O'Connor produce an unterminated quoted string error, making the preview command unusable.
The correct escaping pattern is '"'"', which terminates the single-quoted string, adds an escaped single quote in double quotes, then resumes the single-quoted string.
Suggested fix
if strings.ContainsAny(arg, " \t\n\"'\\$`|&;<>*?[]{}()") {
- return "'" + strings.ReplaceAll(arg, "'", `'\"'\"'`) + "'"
+ return "'" + strings.ReplaceAll(arg, "'", `'"'"'`) + "'"
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func formatShellArg(arg string) string { | |
| if arg == "" { | |
| return `''` | |
| } | |
| if strings.ContainsAny(arg, " \t\n\"'\\$`|&;<>*?[]{}()") { | |
| return "'" + strings.ReplaceAll(arg, "'", `'\"'\"'`) + "'" | |
| } | |
| func formatShellArg(arg string) string { | |
| if arg == "" { | |
| return `''` | |
| } | |
| if strings.ContainsAny(arg, " \t\n\"'\\$`|&;<>*?[]{}()") { | |
| return "'" + strings.ReplaceAll(arg, "'", `'"'"'`) + "'" | |
| } | |
| return arg | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/core/agent/registry_launch.go` around lines 186 - 192, The
shell-escaping logic in formatShellArg incorrectly substitutes embedded single
quotes with '\"'\"' which is not valid POSIX syntax; update the replacement so
embedded "'" are escaped using the correct pattern '"'\'"'" (i.e. replace
occurrences of "'" with `'"'"'`) so the function returns a valid single-quoted
string like "'" + strings.ReplaceAll(arg, "'", `'"'"'`) + "'" and ensures inputs
like O'Connor produce a well-formed quoted argument.
Summary by CodeRabbit
New Features
Improvements
Tests