Skip to content

feat!: migrate to daemon - #112

Merged
pedronauck merged 32 commits into
mainfrom
pn/daemon
Apr 18, 2026
Merged

pedronauck merged 32 commits into
mainfrom
pn/daemon

Conversation

@pedronauck

@pedronauck pedronauck commented Apr 18, 2026 •

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added daemon management commands: daemon start, status, stop.
    • Tasks run, sync, and archive now use the local daemon (daemon-backed execution).
    • CLI supports JSON/text output via new --format flag for operator/daemon commands.
    • Improved workspace, review, run, and exec operations to run through the local daemon API.
  • Chores

    • Updated Go dependency set (performance, JSON/HTTP stacks and related libraries).

@pedronauck pedronauck self-assigned this Apr 18, 2026
@coderabbitai

coderabbitai Bot commented Apr 18, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (2)
  • internal/cli/daemon_exec_test_helpers_test.go
  • internal/cli/root_command_execution_test.go
⛔ Files ignored due to path filters (6)
  • .codex/ledger/2026-04-18-MEMORY-daemon-node-e2e.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-cases/TC-INT-004.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-plans/daemon-regression.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-plans/daemon-test-plan.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/verification-report.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_19.md is excluded by !**/*.md
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 48bc582e-43cb-4d87-8d6a-7c5c8dee830d

📥 Commits

Reviewing files that changed from the base of the PR and between f71398e and 70a614e.

⛔ Files ignored due to path filters (6)
  • .codex/ledger/2026-04-18-MEMORY-daemon-node-e2e.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-cases/TC-INT-004.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-plans/daemon-regression.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-plans/daemon-test-plan.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/verification-report.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_19.md is excluded by !**/*.md
📒 Files selected for processing (2)
  • internal/cli/daemon_exec_test_helpers_test.go
  • internal/cli/root_command_execution_test.go

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Adds a localhost HTTP + UDS daemon API, a matching Go client, SSE run streaming, core transport handlers/middleware/errors, CLI daemon commands and daemon-backed task/exec workflows, conditional update-check suppression, many tests, and supporting utilities and dependency updates.

Changes

Cohort / File(s) Summary
Update check
cmd/compozy/main.go, cmd/compozy/main_test.go
Thread context into run/update-check, make update-check conditional on CLI args, add unit tests for context propagation and suppression rules.
Repo placeholders
.compozy/tasks/daemon/qa/issues/.gitkeep, .compozy/tasks/daemon/qa/screenshots/.gitkeep
Add .gitkeep files to preserve QA directories in VCS.
API client
internal/api/client/...
internal/api/client/client.go, .../operator.go, .../reviews_exec.go, .../runs.go, .../reviews_exec_test.go
New daemon API client: Target (UDS/HTTP), request helpers, workspace/task/review/exec/run endpoints, SSE run-stream client, RemoteError envelope, and unit tests for review/exec/run behaviors.
Core transport contracts & errors
internal/api/core/...
interfaces.go, errors.go, middleware.go
Add handler interfaces, transport payload types, Problem/TransportError model, request-id and error middleware, and helper mappings.
Core handlers, SSE, and routing
internal/api/core/...
handlers.go, routes.go, sse.go, sse_test.go
Implement Gin handlers for daemon/workspaces/tasks/reviews/runs/sync/exec, SSE utilities (cursor, PrepareSSE/WriteSSE), StreamRun loop with heartbeat/overflow, and tests.
Core handler tests
internal/api/core/*_test.go
handlers_test.go, handlers_smoke_test.go, handlers_error_paths_test.go, handlers_service_errors_test.go, internal_helpers_test.go, internal_helpers_test.go
Comprehensive tests covering validation, service errors, SSE semantics, smoke scenarios, helper utilities and error-path mapping.
HTTP & UDS servers + integration tests
internal/api/httpapi/*, internal/api/udsapi/*, internal/api/httpapi/transport_integration_test.go
Localhost HTTP server with dynamic port persistence and shutdown; UDS server with socket lifecycle/permissions; extensive transport integration tests for parity, SSE resumption, and lifecycle semantics.
CLI daemon and daemon-backed commands
internal/cli/*
daemon.go, daemon_commands.go, daemon_launch_unix.go, daemon_launch_other.go, daemon_*_test.go
Add daemon subcommands (start/status/stop), bootstrap for daemon discovery/auto-start, daemon-backed tasks run/exec/review wiring, Unix process-group launch, and many CLI tests/helpers.
CLI command refactor & helpers
internal/cli/commands.go, commands_simple.go, dispatch_adapters.go, operator_output.go, tests...
Switch several commands to daemon-backed flows, add operator output format (text/json), dispatcher-provider adapters, archive/sync remote orchestration, and related test updates.
Archive/sync CLI integration tests
internal/cli/archive_command_integration_test.go, internal/cli/operator_commands_integration_test.go
Add integration tests for archive and operator flows, archive artifact naming checks, and workspace/workflow lifecycle validations.
go.mod
go.mod
Add/update many indirect dependencies (gin, alternate JSON/YAML encoders, quic, mongo-driver v2, modernc libs, golang.org/x/* updates).

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI (tasks run)
    participant Bootstrap as Daemon Bootstrap
    participant Client as API Client
    participant Daemon as Daemon Process

    CLI->>Bootstrap: probe daemon health
    alt daemon not running
        Bootstrap->>Daemon: launch process
        Daemon->>Daemon: initialize services
        Bootstrap->>Daemon: poll health
        Daemon-->>Bootstrap: ready
    end
    Bootstrap-->>CLI: return API client

    CLI->>Client: POST /api/tasks/{slug}/runs
    Client->>Daemon: HTTP/UDS request
    Daemon-->>Client: run created/accepted
    Client-->>CLI: apicore.Run
    alt attach=UI
        CLI->>Client: GET /api/runs/{id}/snapshot
        Client->>Daemon: request
        Daemon-->>Client: snapshot
    else attach=stream
        CLI->>Client: GET /api/runs/{id}/stream (SSE)
        Daemon-->>Client: SSE events (heartbeat, events, overflow, completed)
        Client-->>CLI: parsed frames
    end
Loading
sequenceDiagram
    participant Transport as HTTP/UDS
    participant Middleware as Middleware (RequestID/Error)
    participant Handler as Core Handler
    participant Service as Backend Service

    Transport->>Middleware: incoming request
    Middleware->>Handler: request with context (request id)
    Handler->>Service: invoke service method
    alt success
        Service-->>Handler: result
        Handler->>Transport: JSON 2xx + X-Request-Id
    else error
        Service-->>Handler: error
        Handler->>Handler: map to Problem / TransportError
        Handler->>Transport: JSON error envelope + status
    end
    opt streaming
        Handler->>Transport: PrepareSSE + WriteSSE loop
        Service-->>Handler: stream items/errors
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat!: migrate to daemon' clearly summarizes the primary objective of this changeset, which introduces daemon-backed execution and API infrastructure as the main feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pn/daemon

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

🧹 Nitpick comments (6)
internal/cli/commands.go (1)

13-13: Remove the ignored dispatcher parameter from these private builders.

Each helper now hardwires the daemon runner and never uses *kernel.Dispatcher. Keeping it in the signature preserves dead API surface and makes the migration look partial. Drop the parameter from the WithDefaults helpers and from the wrappers that only thread it through.

Also applies to: 43-43, 143-143

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cli/commands.go` at line 13, The helper functions like
newFetchReviewsCommandWithDefaults currently accept an unused *kernel.Dispatcher
parameter; remove that parameter from their signatures (e.g.,
newFetchReviewsCommandWithDefaults, other private builders at the referenced
locations) and update any thin wrapper functions that only threaded the
dispatcher through to call the helpers directly without the dispatcher argument;
then update all call sites to stop passing a *kernel.Dispatcher. Ensure compiler
errors are resolved by renaming and adjusting any wrapper functions that
previously forwarded the dispatcher so the daemon runner remains hardwired and
no unused parameter remains.
internal/api/client/reviews_exec_test.go (1)

217-247: Avoid string-matching the guard errors.

These assertions are tied to the exact wording of the error, so a harmless message rewrite will fail the tests even if the behavior is still correct. Exposing sentinel or typed validation errors here would let the tests assert with errors.Is/errors.As instead.

As per coding guidelines, "Use errors.Is() and errors.As() for error matching; do not compare error strings" and "*_test.go: MUST have specific error assertions (ErrorContains, ErrorAs)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/client/reviews_exec_test.go` around lines 217 - 247, Replace
fragile string-based error assertions with sentinel or typed error checks using
errors.Is or errors.As (or test helpers that wrap them) for the calls
StartExecRun, FetchReview on nilClient and GetLatestReview, GetReviewRound,
ListReviewIssues, StartReviewRun on client; update the code to assert the
expected exported sentinel/typed validation errors (e.g.,
ErrDaemonClientRequired, ErrWorkflowSlugRequired) via errors.Is/require.ErrorIs
or errors.As/require.ErrorAs instead of strings.Contains on err.Error(),
ensuring tests import and reference the actual sentinel/typed error values
returned by the functions.
internal/cli/daemon_commands_test.go (1)

414-452: Complex ref-counting lock pattern for global test overrides.

This implements a ref-counting scheme to serialize tests that modify global state (newCLIDaemonBootstrap). The pattern:

  1. Check if this test already has refs (reentrant case)
  2. If not, acquire the global mutex cliTestGlobalOverrideMu
  3. Track refs per test name
  4. Release global mutex only when last ref is released

The nested locking is correct but complex. Consider adding a brief comment explaining why ref-counting is needed (likely for subtests that install multiple overrides).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cli/daemon_commands_test.go` around lines 414 - 452, Add a short
explanatory comment above acquireCLITestGlobalOverride and
releaseCLITestGlobalOverride that states why the ref-counting and the
cliTestGlobalOverrideMu global mutex are required (to serialize tests that
modify global state like newCLIDaemonBootstrap and to allow reentrant calls from
subtests that need multiple overrides), and describe the lifecycle: first caller
acquires the global mutex, subsequent reentrant callers just increment refs, and
the mutex is unlocked only when the last ref is released; reference the function
names acquireCLITestGlobalOverride, releaseCLITestGlobalOverride,
cliTestGlobalOverrides.refs, and cliTestGlobalOverrideMu so future readers
understand the locking/ref-counting coordination.
internal/api/core/internal_helpers_test.go (1)

16-138: Comprehensive but monolithic test for Problem and error helpers.

This test covers many aspects of the error handling system:

  • Problem.Error() formatting and fallbacks
  • errors.Is() wrapping behavior
  • Nil-pointer safety
  • Status/code/message mapping for various error types
  • Helper constructors (invalidJSONProblem, serviceUnavailableProblem)
  • Schema error details extraction
  • Context cancellation detection

Consider splitting into focused subtests using t.Run to improve test failure diagnostics and align with the table-driven test pattern guideline.

As per coding guidelines: "Table-driven tests with subtests (t.Run) as the default pattern".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/core/internal_helpers_test.go` around lines 16 - 138, Split the
monolithic TestProblemAndHelperFunctions into focused subtests using t.Run
(e.g., "ErrorFormatting", "NilSafety", "StatusCodeMapping",
"ConstructorHelpers", "SchemaDetails", "ContextCancellation") and/or
table-driven subtests; move each assertion group that references symbols like
NewProblem, (*Problem).Error, errors.Is, statusForError, defaultCodeForStatus,
messageForError, invalidJSONProblem, serviceUnavailableProblem, codeForError,
detailsForError, and requestCanceled into its own t.Run so failures are isolated
and use table-driven loops where multiple inputs are tested (e.g.,
defaultCodeForStatus and schema errors) while keeping existing assertions and
expected values unchanged.
internal/api/core/sse_test.go (1)

37-66: Missing t.Parallel() for test independence.

This test function doesn't call t.Parallel(), unlike the other tests in this file. Adding it would allow this test to run concurrently with others.

As per coding guidelines: "Use t.Parallel() for independent subtests".

♻️ Proposed fix
 func TestWriteSSEFormatsFramesWithCanonicalCursor(t *testing.T) {
+	t.Parallel()
+
 	timestamp := time.Date(2026, 4, 17, 12, 0, 0, 123456789, time.UTC)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/core/sse_test.go` around lines 37 - 66, Add t.Parallel() at the
start of the TestWriteSSEFormatsFramesWithCanonicalCursor test to make it run
concurrently with other independent tests; open the function
TestWriteSSEFormatsFramesWithCanonicalCursor and insert a t.Parallel() call
immediately after the func declaration (before using timestamp, cursor, writer
or calling core.WriteSSE) so the test remains independent of other tests that
exercise core.WriteSSE and flushBuffer.
internal/api/httpapi/transport_integration_test.go (1)

487-539: Rename these subtests to the required Should... form.

"invalid" and "stale" make failures harder to scan, and they don't match the repo's required subtest pattern.

Suggested fix
 	{
-		name:       "invalid",
+		name:       "Should reject an invalid cursor",
 		lastEvent:  "bad",
 		wantCode:   http.StatusUnprocessableEntity,
 		wantReason: "invalid_cursor",
 	},
 	{
-		name:       "stale",
+		name:       "Should reject a stale cursor",
 		lastEvent:  staleCursor,
 		wantCode:   http.StatusUnprocessableEntity,
 		wantReason: "stale_cursor",
 	},

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/api/httpapi/transport_integration_test.go` around lines 487 - 539,
Rename the subtest names in the testCases slice from "invalid" and "stale" to
the required t.Run "Should..." pattern so failures are easier to scan; update
the name fields in the testCases declaration (used by the t.Run(...) call) to
descriptive names such as "ShouldReturnUnprocessableEntityForInvalidCursor" and
"ShouldReturnUnprocessableEntityForStaleCursor" (or any "Should..." phrasing
that describes the expectation) while leaving the rest of the test logic
(request creation, header Last-Event-ID, assertions on response.StatusCode and
payload.Code) unchanged.
🤖 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 67-77: The current loop in shouldStartUpdateCheck returns true on
the first non-flag token, which wrongly opts in for cases like "compozy tasks
help"; instead, iterate through all non-flag tokens (the args -> value loop) and
track whether any non-flag token exists, while immediately returning false if
any token matches the help/version/completion whitelist ("help", "version",
"completion", "__complete", "__completeNoDesc"); after the loop, return true
only if at least one non-flag token was seen and none were in the whitelist.
This keeps the whitelist check authoritative and delays opting in until the
entire command path is scanned.

In `@internal/api/client/client.go`:
- Around line 150-167: The slug variable is not URL-escaped before being
embedded into the request path, so pass the slug through url.PathEscape and use
that escaped value when building path (replace the concatenation that creates
path := "/api/tasks/"+slug+"/runs" with a version that uses
url.PathEscape(slug)); ensure the net/url import is present and then call
c.doJSON as before with the escaped path so requests with slashes, spaces, or
special chars route correctly.

In `@internal/api/client/runs.go`:
- Around line 452-458: sendItem currently treats a full s.items channel as a
terminal error which kills the SSE session; instead, make sendItem wait for
space and only return an error when the stream is actually canceled. Replace the
non-blocking send in sendItem with a blocking select that sends item to s.items
or returns only if the stream context/channel is closed (e.g. check s.ctx.Done()
or the stream's close channel), so normal backpressure blocks the sender rather
than terminating the stream; keep references to sendItem, s.items and the read()
consumer so the change preserves expected lifetime semantics.

In `@internal/api/core/handlers_error_paths_test.go`:
- Around line 15-17: The test TestSharedHandlersValidationAndServiceErrors calls
gin.SetMode which mutates global Gin state; add a t.Cleanup that captures the
current mode via old := gin.Mode() before calling gin.SetMode(gin.TestMode) and
restore it in cleanup with gin.SetMode(old) so subsequent tests are not affected
(reference gin.SetMode, gin.Mode, and
TestSharedHandlersValidationAndServiceErrors).

In `@internal/api/core/handlers_test.go`:
- Around line 107-115: The test currently uses time.Sleep inside the goroutine
that sends a core.RunStreamOverflow to stream.events, which races with the
heartbeat; replace the sleep with an explicit synchronization primitive: add a
channel or sync.WaitGroup (e.g., readyCh) that the main test signals once the
heartbeat has been processed, then have the goroutine block on that signal
before sending the overflow frame to stream.events and closing
stream.events/stream.errors; update the test to wait for the ready signal rather
than sleeping so newFakeRunStream, stream.events, and the overflow send are
deterministically ordered.

In `@internal/api/core/handlers.go`:
- Around line 768-775: The handlers currently accept any positive integer from
parsePositiveInt into the local variable limit; clamp limit to a safe maximum
before calling the run service (e.g., define a MAX_LIMIT constant and set limit
= min(limit, MAX_LIMIT) or return an error if limit > MAX_LIMIT), and apply the
same change in both ListRuns and ListRunEvents so oversized requests are
prevented; update the code paths that call the run service with this clamped
limit (reference symbols: parsePositiveInt, limit variable, ListRuns,
ListRunEvents, and the run service call).
- Around line 630-644: The Fetch review handler currently forwards body.Round
unchecked into h.Reviews.Fetch; add a validation after requireWorkspaceRef and
before constructing ReviewFetchRequest that rejects non-positive rounds
(body.Round <= 0) and returns an HTTP 400 Bad Request (use the existing error
helper such as h.errorBadRequest or the project's standard response helper) with
a clear message like "round must be a positive integer"; keep existing flow
using reviewFetchBody, h.bindJSON, requireWorkspaceRef, and ReviewFetchRequest
and only proceed to call h.Reviews.Fetch when the round is valid.
- Around line 883-885: The defer currently swallows the error from
stream.Close(); change it to capture and log the error with the run id instead
of assigning to `_`. Replace the defer func that calls `_ = stream.Close()` with
code like: defer func() { if err := stream.Close(); err != nil { /* use the
existing logger in scope (e.g., logger/processLogger) */
logger.Errorf("stream.Close failed for run %s: %v", runID, err) } }() so the
Close error for stream.Close() is not ignored and includes the run id for
diagnostics.

In `@internal/api/httpapi/server.go`:
- Around line 212-214: The shared mutation of core.Handlers via
s.handlers.SetStreamDone in the HTTP server Start is unsafe because
internal/api/udsapi/server.go also writes the same streamDone slot; change Start
so it does not mutate a shared handlers instance. Fix by giving each transport
its own handler instance or by adding transport-scoped stream cancellation
(e.g., create a clone/copy of core.Handlers for HTTP before calling
SetStreamDone, or add a per-transport SetStreamDone variant such as
handlers.ForTransport("http").SetStreamDone), and leave SetHTTPPort usage as-is;
update the HTTP server Start (where s.mu.Lock() and s.handlers.SetStreamDone are
called) and the UDS Start in internal/api/udsapi/server.go to use
transport-specific handler instances or APIs instead of writing the same shared
slot.
- Around line 179-184: The Start method currently checks s.started while holding
s.mu but releases the lock before setting it, allowing two concurrent Start
calls to both proceed; fix by setting s.started = true while still holding s.mu
(i.e., inside the s.mu.Lock() / Unlock() section) so the second caller
immediately sees started; also ensure that on every subsequent error path (any
failure before launching the serve goroutine that sets
s.httpServer/s.listener/s.serveDone) you perform the same rollback (clear
s.started and any partially set fields) while holding s.mu so the server state
remains consistent and Shutdown will clean up correctly; reference s.mu,
s.started, s.httpServer, s.listener, s.serveDone and the serve goroutine launch
when applying the fix.

In `@internal/api/udsapi/server.go`:
- Around line 139-144: The start routine currently checks s.started under s.mu,
unlocks, then proceeds to create listeners allowing a race where two callers
both create listeners; fix by holding s.mu, set s.started = true before
performing the listen/accept setup (so the first caller wins), then release the
lock and continue creating the listener. Update the code around the
s.mu/s.started checks in server.start (or the method containing the shown
snippet) to set s.started = true while still holding s.mu, and only then
unlock—keeping the same error return path if already started.
- Around line 67-73: The constructor New currently calls
compozyconfig.ResolveHomePaths() before applying provided options, causing
New(WithSocketPath(...)) to fail when ResolveHomePaths() errors; change New to
first instantiate Server (zero-value), apply all Option functions (e.g.,
WithSocketPath) to that server, and only call ResolveHomePaths() as a fallback
when server.socketPath is still empty; update references in New, Option
handling, and Server.socketPath accordingly so explicit options override the
home-path resolution.

In `@internal/cli/archive_command_integration_test.go`:
- Around line 12-13: The test currently only sets HOME using homeDir :=
newShortCLITestHomeDir(t) and t.Setenv("HOME", homeDir); update it to fully
isolate environment like the daemon-backed tests: create an xdgConfigHome (e.g.
via t.TempDir()), then call t.Setenv("XDG_CONFIG_HOME", xdgConfigHome) and also
set testCLIDaemonHomeEnv to homeDir and testCLIXDGHomeEnv to xdgConfigHome
(using t.Setenv) so operator commands running behind the daemon use the isolated
HOME/XDG and test-specific envs.

In `@internal/cli/commands_simple.go`:
- Around line 316-322: The current logic silently drops errors from
absoluteWorkflowPath when s.rootDir is provided, which can hide
misconfiguration; update the block that sets archiveRootBase (using
model.TasksBaseDirForWorkspace, s.workspaceRoot, s.rootDir) to surface failures
from absoluteWorkflowPath: either return the error up to the caller or log a
clear warning including the failed s.rootDir and the error message so users see
invalid path problems instead of silently falling back to the default; ensure
you modify the surrounding function signature/return path if you choose to
propagate the error.

In `@internal/cli/daemon_exec_test_helpers_test.go`:
- Around line 253-317: The bridge goroutine in newInProcessClientRunStream can
block forever on the unbuffered items channel when consumers exit early; add a
shutdown signal by adding a done channel to the inProcessClientRunStream struct,
have the goroutine include a case <-done in its select loop to break and stop
forwarding, and update Close() on inProcessClientRunStream to close or signal
the done channel before calling the upstream stream.Close so the bridge exits
immediately and does not leak; make sure items and errors channels are still
closed by the goroutine when shutting down.

In `@internal/cli/daemon.go`:
- Around line 48-54: The RunE handler currently creates a context with
signal.NotifyContext(context.Background(), ...), losing any
cancellation/deadline from Cobra; replace that logic by calling the existing
helper signalCommandContext(cmd) to derive the context (i.e. use ctx :=
signalCommandContext(cmd) and defer any stop returned by that helper if
applicable) and pass that ctx into daemon.Run in RunE so the command respects
injected contexts and tests; remove the manual signal.NotifyContext usage and
keep the daemon.Run invocation and RunOptions.Version as-is.
- Around line 100-123: The probe and daemon RPC calls in daemonStatusState.run
and daemonStopState.run use context.Background(), which prevents signal
propagation; replace each context.Background() passed to daemon.QueryStatus,
client.DaemonStatus, client.Health and client.StopDaemon with the command
context (cmd.Context()) so cancellation and Ctrl+C propagate correctly — locate
the calls in the methods daemonStatusState.run and daemonStopState.run and swap
the context argument to cmd.Context().

---

Nitpick comments:
In `@internal/api/client/reviews_exec_test.go`:
- Around line 217-247: Replace fragile string-based error assertions with
sentinel or typed error checks using errors.Is or errors.As (or test helpers
that wrap them) for the calls StartExecRun, FetchReview on nilClient and
GetLatestReview, GetReviewRound, ListReviewIssues, StartReviewRun on client;
update the code to assert the expected exported sentinel/typed validation errors
(e.g., ErrDaemonClientRequired, ErrWorkflowSlugRequired) via
errors.Is/require.ErrorIs or errors.As/require.ErrorAs instead of
strings.Contains on err.Error(), ensuring tests import and reference the actual
sentinel/typed error values returned by the functions.

In `@internal/api/core/internal_helpers_test.go`:
- Around line 16-138: Split the monolithic TestProblemAndHelperFunctions into
focused subtests using t.Run (e.g., "ErrorFormatting", "NilSafety",
"StatusCodeMapping", "ConstructorHelpers", "SchemaDetails",
"ContextCancellation") and/or table-driven subtests; move each assertion group
that references symbols like NewProblem, (*Problem).Error, errors.Is,
statusForError, defaultCodeForStatus, messageForError, invalidJSONProblem,
serviceUnavailableProblem, codeForError, detailsForError, and requestCanceled
into its own t.Run so failures are isolated and use table-driven loops where
multiple inputs are tested (e.g., defaultCodeForStatus and schema errors) while
keeping existing assertions and expected values unchanged.

In `@internal/api/core/sse_test.go`:
- Around line 37-66: Add t.Parallel() at the start of the
TestWriteSSEFormatsFramesWithCanonicalCursor test to make it run concurrently
with other independent tests; open the function
TestWriteSSEFormatsFramesWithCanonicalCursor and insert a t.Parallel() call
immediately after the func declaration (before using timestamp, cursor, writer
or calling core.WriteSSE) so the test remains independent of other tests that
exercise core.WriteSSE and flushBuffer.

In `@internal/api/httpapi/transport_integration_test.go`:
- Around line 487-539: Rename the subtest names in the testCases slice from
"invalid" and "stale" to the required t.Run "Should..." pattern so failures are
easier to scan; update the name fields in the testCases declaration (used by the
t.Run(...) call) to descriptive names such as
"ShouldReturnUnprocessableEntityForInvalidCursor" and
"ShouldReturnUnprocessableEntityForStaleCursor" (or any "Should..." phrasing
that describes the expectation) while leaving the rest of the test logic
(request creation, header Last-Event-ID, assertions on response.StatusCode and
payload.Code) unchanged.

In `@internal/cli/commands.go`:
- Line 13: The helper functions like newFetchReviewsCommandWithDefaults
currently accept an unused *kernel.Dispatcher parameter; remove that parameter
from their signatures (e.g., newFetchReviewsCommandWithDefaults, other private
builders at the referenced locations) and update any thin wrapper functions that
only threaded the dispatcher through to call the helpers directly without the
dispatcher argument; then update all call sites to stop passing a
*kernel.Dispatcher. Ensure compiler errors are resolved by renaming and
adjusting any wrapper functions that previously forwarded the dispatcher so the
daemon runner remains hardwired and no unused parameter remains.

In `@internal/cli/daemon_commands_test.go`:
- Around line 414-452: Add a short explanatory comment above
acquireCLITestGlobalOverride and releaseCLITestGlobalOverride that states why
the ref-counting and the cliTestGlobalOverrideMu global mutex are required (to
serialize tests that modify global state like newCLIDaemonBootstrap and to allow
reentrant calls from subtests that need multiple overrides), and describe the
lifecycle: first caller acquires the global mutex, subsequent reentrant callers
just increment refs, and the mutex is unlocked only when the last ref is
released; reference the function names acquireCLITestGlobalOverride,
releaseCLITestGlobalOverride, cliTestGlobalOverrides.refs, and
cliTestGlobalOverrideMu so future readers understand the locking/ref-counting
coordination.
🪄 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: d7f41a6e-1480-45ad-afdb-35c5bc944895

📥 Commits

Reviewing files that changed from the base of the PR and between ac705bb and ee2d024.

⛔ Files ignored due to path filters (179)
  • .agents/skills/compozy/references/config-reference.md is excluded by !**/*.md, !.agents/**
  • .codex/ledger/2026-04-17-MEMORY-active-run-watchers.md is excluded by !**/*.md
  • .codex/ledger/2026-04-17-MEMORY-archive-db-state.md is excluded by !**/*.md
  • .codex/ledger/2026-04-17-MEMORY-cli-daemon-client.md is excluded by !**/*.md
  • .codex/ledger/2026-04-17-MEMORY-daemon-architecture.md is excluded by !**/*.md
  • .codex/ledger/2026-04-17-MEMORY-daemon-qa-tasks.md is excluded by !**/*.md
  • .codex/ledger/2026-04-17-MEMORY-daemon-run-manager.md is excluded by !**/*.md
  • .codex/ledger/2026-04-17-MEMORY-extension-runtime-daemon.md is excluded by !**/*.md
  • .codex/ledger/2026-04-17-MEMORY-global-db-registry.md is excluded by !**/*.md
  • .codex/ledger/2026-04-17-MEMORY-shared-transport-core.md is excluded by !**/*.md
  • .codex/ledger/2026-04-17-MEMORY-sync-persistence-rewrite.md is excluded by !**/*.md
  • .codex/ledger/2026-04-17-MEMORY-tui-remote-attach.md is excluded by !**/*.md
  • .codex/ledger/2026-04-18-MEMORY-daemon-command-surface.md is excluded by !**/*.md
  • .codex/ledger/2026-04-18-MEMORY-daemon-memory-research.md is excluded by !**/*.md
  • .codex/ledger/2026-04-18-MEMORY-daemon-perf.md is excluded by !**/*.md
  • .codex/ledger/2026-04-18-MEMORY-daemon-qa-execution.md is excluded by !**/*.md
  • .codex/ledger/2026-04-18-MEMORY-daemon-qa-plan.md is excluded by !**/*.md
  • .codex/ledger/2026-04-18-MEMORY-regression-cleanup.md is excluded by !**/*.md
  • .codex/ledger/2026-04-18-MEMORY-reviews-exec-migration.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/_prompt.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/qa/issue_001.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/qa/issue_002.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/qa/issue_003.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/qa/issue_004.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/_meta.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_001.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_002.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_003.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_004.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_005.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_006.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_007.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_008.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_009.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_010.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_011.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_012.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_013.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_014.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_015.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_016.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_017.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_018.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_019.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_020.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_021.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_022.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_023.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_024.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_025.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_026.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_027.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_028.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-001/issue_029.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-002/_meta.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-002/issue_001.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-002/issue_002.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-002/issue_003.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-002/issue_004.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-002/issue_005.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-002/issue_006.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-002/issue_007.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-002/issue_008.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-002/issue_009.md is excluded by !**/*.md
  • .compozy/tasks/_archived/ext-imp/reviews-002/issue_010.md is excluded by !**/*.md
  • .compozy/tasks/_archived/tasks-props/reviews-001/_meta.md is excluded by !**/*.md
  • .compozy/tasks/_archived/tasks-props/reviews-001/issue_001.md is excluded by !**/*.md
  • .compozy/tasks/_archived/tasks-props/reviews-001/issue_002.md is excluded by !**/*.md
  • .compozy/tasks/_archived/tasks-props/reviews-001/issue_003.md is excluded by !**/*.md
  • .compozy/tasks/_archived/tasks-props/reviews-001/issue_004.md is excluded by !**/*.md
  • .compozy/tasks/_archived/tasks-props/reviews-001/issue_005.md is excluded by !**/*.md
  • .compozy/tasks/_archived/tasks-props/reviews-001/issue_006.md is excluded by !**/*.md
  • .compozy/tasks/_archived/tasks-props/reviews-001/issue_007.md is excluded by !**/*.md
  • .compozy/tasks/_archived/tasks-props/reviews-001/issue_008.md is excluded by !**/*.md
  • .compozy/tasks/_archived/tasks-props/reviews-001/issue_009.md is excluded by !**/*.md
  • .compozy/tasks/_archived/tasks-props/reviews-001/issue_010.md is excluded by !**/*.md
  • .compozy/tasks/_archived/tasks-props/reviews-001/issue_011.md is excluded by !**/*.md
  • .compozy/tasks/_archived/tasks-props/reviews-001/issue_012.md is excluded by !**/*.md
  • .compozy/tasks/_archived/tasks-props/reviews-001/issue_013.md is excluded by !**/*.md
  • .compozy/tasks/daemon/_meta.md is excluded by !**/*.md
  • .compozy/tasks/daemon/_tasks.md is excluded by !**/*.md
  • .compozy/tasks/daemon/_techspec.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/MEMORY.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_01.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_02.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_03.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_04.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_05.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_06.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_07.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_08.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_09.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_10.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_11.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_12.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_13.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_14.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_15.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_16.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_17.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_18.md is excluded by !**/*.md
  • .compozy/tasks/daemon/memory/task_19.md is excluded by !**/*.md
  • .compozy/tasks/daemon/perf_analysis/SUMMARY.md is excluded by !**/*.md
  • .compozy/tasks/daemon/perf_analysis/analysis_cli_startup.md is excluded by !**/*.md
  • .compozy/tasks/daemon/perf_analysis/analysis_daemon_runtime.md is excluded by !**/*.md
  • .compozy/tasks/daemon/perf_analysis/analysis_event_journal.md is excluded by !**/*.md
  • .compozy/tasks/daemon/perf_analysis/analysis_run_executor.md is excluded by !**/*.md
  • .compozy/tasks/daemon/perf_analysis/analysis_storage_sqlite.md is excluded by !**/*.md
  • .compozy/tasks/daemon/perf_analysis/analysis_tui_rendering.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/issues/BUG-001.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/logs/baseline-make-verify.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/final-make-verify-clean.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/final-make-verify.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/full-support-api-core.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/full-support-core.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/full-support-daemon.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/full-support-runs-stream.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/perf-build.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/postclean-tc-func-002.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/postclean-tc-int-002.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/postgate-tc-func-002.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/postgate-tc-func-006.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/postgate-tc-int-002.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/tc-func-001.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/tc-func-002.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/tc-func-003.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/tc-func-004.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/tc-func-005.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/tc-func-006.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/tc-int-001.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/tc-int-002.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/tc-int-003.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/tc-perf-001-bench.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/tc-perf-001-hyperfine.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/tui-executor.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/logs/tui-remote.log is excluded by !**/*.log
  • .compozy/tasks/daemon/qa/test-cases/TC-FUNC-001.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-cases/TC-FUNC-002.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-cases/TC-FUNC-003.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-cases/TC-FUNC-004.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-cases/TC-FUNC-005.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-cases/TC-FUNC-006.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-cases/TC-INT-001.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-cases/TC-INT-002.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-cases/TC-INT-003.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-cases/TC-PERF-001.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-cases/TC-UI-001.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-plans/daemon-regression.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/test-plans/daemon-test-plan.md is excluded by !**/*.md
  • .compozy/tasks/daemon/qa/verification-report.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_01.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_02.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_03.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_04.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_05.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_06.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_07.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_08.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_09.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_10.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_11.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_12.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_13.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_14.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_15.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_16.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_17.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_18.md is excluded by !**/*.md
  • .compozy/tasks/daemon/task_19.md is excluded by !**/*.md
  • README.md is excluded by !**/*.md
  • docs/events.md is excluded by !**/*.md
  • docs/extensibility/architecture.md is excluded by !**/*.md
  • docs/extensibility/getting-started.md is excluded by !**/*.md
  • docs/extensibility/hello-world-go.md is excluded by !**/*.md
  • docs/extensibility/hello-world-ts.md is excluded by !**/*.md
  • docs/extensibility/host-api-reference.md is excluded by !**/*.md
  • docs/extensibility/trust-and-enablement.md is excluded by !**/*.md
  • docs/reader-library.md is excluded by !**/*.md
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (121)
  • .compozy/tasks/daemon/qa/issues/.gitkeep
  • .compozy/tasks/daemon/qa/screenshots/.gitkeep
  • cmd/compozy/main.go
  • cmd/compozy/main_test.go
  • go.mod
  • internal/api/client/client.go
  • internal/api/client/operator.go
  • internal/api/client/reviews_exec.go
  • internal/api/client/reviews_exec_test.go
  • internal/api/client/runs.go
  • internal/api/core/errors.go
  • internal/api/core/handlers.go
  • internal/api/core/handlers_error_paths_test.go
  • internal/api/core/handlers_service_errors_test.go
  • internal/api/core/handlers_smoke_test.go
  • internal/api/core/handlers_test.go
  • internal/api/core/interfaces.go
  • internal/api/core/internal_helpers_test.go
  • internal/api/core/middleware.go
  • internal/api/core/routes.go
  • internal/api/core/sse.go
  • internal/api/core/sse_test.go
  • internal/api/httpapi/routes.go
  • internal/api/httpapi/server.go
  • internal/api/httpapi/transport_integration_test.go
  • internal/api/udsapi/routes.go
  • internal/api/udsapi/server.go
  • internal/cli/agents_commands_test.go
  • internal/cli/archive_command_integration_test.go
  • internal/cli/commands.go
  • internal/cli/commands_simple.go
  • internal/cli/commands_test.go
  • internal/cli/daemon.go
  • internal/cli/daemon_commands.go
  • internal/cli/daemon_commands_test.go
  • internal/cli/daemon_exec_test_helpers_test.go
  • internal/cli/daemon_launch_other.go
  • internal/cli/daemon_launch_unix.go
  • internal/cli/dispatch_adapters.go
  • internal/cli/extensions_bootstrap_test.go
  • internal/cli/form_test.go
  • internal/cli/kernel_dispatch_test.go
  • internal/cli/operator_commands_integration_test.go
  • internal/cli/operator_output.go
  • internal/cli/reusable_agents_doc_examples_test.go
  • internal/cli/reviews_exec_daemon.go
  • internal/cli/reviews_exec_daemon_additional_test.go
  • internal/cli/root.go
  • internal/cli/root_command_execution_test.go
  • internal/cli/root_test.go
  • internal/cli/run.go
  • internal/cli/run_observe.go
  • internal/cli/runs.go
  • internal/cli/skills_preflight_test.go
  • internal/cli/state.go
  • internal/cli/testdata/exec_help.golden
  • internal/cli/testdata/tasks_run_help.golden
  • internal/cli/validate_tasks_test.go
  • internal/cli/workspace_commands.go
  • internal/cli/workspace_config.go
  • internal/cli/workspace_config_test.go
  • internal/config/home.go
  • internal/config/home_test.go
  • internal/core/archive.go
  • internal/core/archive_test.go
  • internal/core/extension/audit.go
  • internal/core/extension/daemon_bridge.go
  • internal/core/extension/dispatcher.go
  • internal/core/extension/dispatcher_test.go
  • internal/core/extension/host_api_errors.go
  • internal/core/extension/host_helpers.go
  • internal/core/extension/host_helpers_test.go
  • internal/core/extension/host_writes.go
  • internal/core/extension/host_writes_test.go
  • internal/core/extension/manager_shutdown.go
  • internal/core/extension/manager_spawn.go
  • internal/core/extension/manager_test.go
  • internal/core/extension/review_provider_runtime.go
  • internal/core/extension/review_provider_runtime_test.go
  • internal/core/extension/runtime.go
  • internal/core/extension/testdata/mock_extension/main.go
  • internal/core/fetch.go
  • internal/core/kernel/commands/workflow_sync.go
  • internal/core/kernel/handlers.go
  • internal/core/kernel/run_scope_integration_test.go
  • internal/core/migration/migrate_test.go
  • internal/core/model/artifacts.go
  • internal/core/model/model_test.go
  • internal/core/model/run_scope.go
  • internal/core/model/runtime_config.go
  • internal/core/model/workflow_ops.go
  • internal/core/model/workspace_paths.go
  • internal/core/plan/prepare.go
  • internal/core/plan/prepare_test.go
  • internal/core/provider/overlay.go
  • internal/core/reviews/store.go
  • internal/core/reviews/store_test.go
  • internal/core/run/exec/exec.go
  • internal/core/run/exec/exec_integration_test.go
  • internal/core/run/exec/exec_test.go
  • internal/core/run/executor/event_stream.go
  • internal/core/run/executor/execution.go
  • internal/core/run/executor/execution_acp_integration_test.go
  • internal/core/run/executor/execution_test.go
  • internal/core/run/executor/lifecycle.go
  • internal/core/run/executor/review_hooks.go
  • internal/core/run/internal/acpshared/command_io.go
  • internal/core/run/internal/acpshared/command_io_test.go
  • internal/core/run/internal/acpshared/session_handler.go
  • internal/core/run/internal/acpshared/session_handler_test.go
  • internal/core/run/internal/runshared/config.go
  • internal/core/run/internal/runtimeevents/events.go
  • internal/core/run/journal/journal.go
  • internal/core/run/journal/journal_test.go
  • internal/core/run/transcript/model.go
  • internal/core/run/transcript/model_test.go
  • internal/core/run/ui/adapter_test.go
  • internal/core/run/ui/model.go
  • internal/core/run/ui/remote.go
  • internal/core/run/ui/remote_test.go
  • internal/core/run/ui/sidebar.go

Comment thread cmd/compozy/main.go
Comment thread internal/api/client/client.go
Comment thread internal/api/client/runs.go
Comment thread internal/api/core/handlers_error_paths_test.go
Comment thread internal/api/core/handlers_test.go
Comment thread internal/cli/archive_command_integration_test.go
Comment thread internal/cli/commands_simple.go
Comment thread internal/cli/daemon_exec_test_helpers_test.go
Comment thread internal/cli/daemon.go Outdated
Comment thread internal/cli/daemon.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
internal/api/client/client.go (2)

246-249: Add a comment justifying the ignored error from response.Body.Close().

Per coding guidelines, errors should not be ignored with _ without written justification. While ignoring Close() errors on fully-read response bodies is common practice (the body is already consumed and errors are rarely actionable), the guidelines require an explicit comment.

♻️ Suggested fix
 	defer func() {
-		_ = response.Body.Close()
+		// Body is fully consumed; Close errors are not actionable.
+		_ = response.Body.Close()
 	}()

As per coding guidelines, "NEVER ignore errors with _ — 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/api/client/client.go` around lines 246 - 249, The deferred anonymous
function currently discards the error from response.Body.Close() (defer func() {
_ = response.Body.Close() }()), which violates the "never ignore errors"
guideline; update that deferred close in client.go (the defer around
response.Body.Close()) to either handle/log the error or add a concise inline
comment justifying why it’s safe to ignore (e.g., the response body is fully
consumed and Close errors are not actionable in this context), e.g., replace the
blank assignment with a brief comment explaining the rationale or call
processLogger.Debug/Warning to record non-fatal close errors.

284-290: Consider using exact path match for health endpoint special-casing.

strings.HasSuffix(path, "/daemon/health") would also match unexpected paths like /api/v2/daemon/health or /api/foo/daemon/health. An exact match is safer for this special-case handling.

♻️ Suggested fix
 	if statusCode == http.StatusServiceUnavailable &&
-		strings.HasSuffix(path, "/daemon/health") &&
+		path == "/api/daemon/health" &&
 		responseBody != nil {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/client/client.go` around lines 284 - 290, The health-endpoint
special-case currently uses strings.HasSuffix(path, "/daemon/health") which can
match unintended routes; update the conditional in the function handling the
HTTP response to require an exact path match (e.g., path == "/daemon/health")
when checking statusCode == http.StatusServiceUnavailable and responseBody !=
nil, then proceed to json.Unmarshal(payload, responseBody) as before so only the
intended /daemon/health endpoint triggers the special-case.
internal/api/core/internal_helpers_test.go (2)

149-202: Refactor to use t.Run("Should...") subtests for better isolation and clarity.

This test covers multiple distinct behaviors (transport name, time sourcing, stream-done channel, Clone, SetHTTPPort) in a single flat function without subtests. Per coding guidelines, all test cases must use the t.Run("Should...") pattern for organization and t.Parallel() for independent subtests.

♻️ Suggested structure
 func TestHandlerInternalHelpers(t *testing.T) {
-	handlers := NewHandlers(&HandlerConfig{})
-	if got := handlers.transportName(); got != "api" {
-		t.Fatalf("transportName() = %q, want api", got)
-	}
-	// ... rest of flat test ...
+	t.Parallel()
+
+	t.Run("Should return default transport name when empty", func(t *testing.T) {
+		t.Parallel()
+		handlers := NewHandlers(&HandlerConfig{})
+		if got := handlers.transportName(); got != "api" {
+			t.Fatalf("transportName() = %q, want api", got)
+		}
+	})
+
+	t.Run("Should return configured transport name when set", func(t *testing.T) {
+		t.Parallel()
+		handlers := NewHandlers(&HandlerConfig{})
+		handlers.TransportName = "http"
+		if got := handlers.transportName(); got != "http" {
+			t.Fatalf("transportName(non-empty) = %q, want http", got)
+		}
+	})
+
+	// ... continue for other behaviors: now(), SetStreamDone, Clone, SetHTTPPort ...
 }

As per coding guidelines, "MUST use t.Run("Should...") pattern for ALL test cases" and "Use t.Parallel() for independent subtests".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/core/internal_helpers_test.go` around lines 149 - 202, The
TestHandlerInternalHelpers function bundles many independent checks; refactor it
into t.Run subtests (use names like t.Run("Should return default transport
name", ...), "Should respect TransportName override", "Should return non-zero
time from now", "Should handle nil Now", "Should set and return stream done
channel", "Should clone handlers and provide independent stream channel",
"Should no-op on nil handlers streamDoneChannel", "Should set HTTP port and
reject invalid sets") and call t.Parallel() inside each subtest where safe; keep
the same assertions and use the existing symbols (NewHandlers,
handlers.transportName, handlers.Now/now,
handlers.SetStreamDone/streamDoneChannel, handlers.Clone,
handlers.SetHTTPPort/httpPort.Load, nilHandlers.SetHTTPPort) so behavior remains
identical but tests are isolated and parallelizable.

246-248: The loop variable capture tt := tt is no longer needed in Go 1.22+.

Go 1.22 changed loop variable semantics so each iteration gets its own copy. This pattern is now unnecessary but harmless.

♻️ Suggested cleanup
 	for _, tt := range testCases {
-		tt := tt
 		t.Run(tt.name, func(t *testing.T) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/core/internal_helpers_test.go` around lines 246 - 248, Remove
the redundant loop-variable shadowing inside the test loop: delete the line "tt
:= tt" so the for-range variable tt is used directly in the t.Run closure (the
t.Run call and testCases loop remain unchanged); this relies on Go 1.22+
per-iteration variable semantics and simplifies the test in
internal_helpers_test.go by eliminating unnecessary shadowing of tt.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/api/client/client.go`:
- Around line 246-249: The deferred anonymous function currently discards the
error from response.Body.Close() (defer func() { _ = response.Body.Close() }()),
which violates the "never ignore errors" guideline; update that deferred close
in client.go (the defer around response.Body.Close()) to either handle/log the
error or add a concise inline comment justifying why it’s safe to ignore (e.g.,
the response body is fully consumed and Close errors are not actionable in this
context), e.g., replace the blank assignment with a brief comment explaining the
rationale or call processLogger.Debug/Warning to record non-fatal close errors.
- Around line 284-290: The health-endpoint special-case currently uses
strings.HasSuffix(path, "/daemon/health") which can match unintended routes;
update the conditional in the function handling the HTTP response to require an
exact path match (e.g., path == "/daemon/health") when checking statusCode ==
http.StatusServiceUnavailable and responseBody != nil, then proceed to
json.Unmarshal(payload, responseBody) as before so only the intended
/daemon/health endpoint triggers the special-case.

In `@internal/api/core/internal_helpers_test.go`:
- Around line 149-202: The TestHandlerInternalHelpers function bundles many
independent checks; refactor it into t.Run subtests (use names like
t.Run("Should return default transport name", ...), "Should respect
TransportName override", "Should return non-zero time from now", "Should handle
nil Now", "Should set and return stream done channel", "Should clone handlers
and provide independent stream channel", "Should no-op on nil handlers
streamDoneChannel", "Should set HTTP port and reject invalid sets") and call
t.Parallel() inside each subtest where safe; keep the same assertions and use
the existing symbols (NewHandlers, handlers.transportName, handlers.Now/now,
handlers.SetStreamDone/streamDoneChannel, handlers.Clone,
handlers.SetHTTPPort/httpPort.Load, nilHandlers.SetHTTPPort) so behavior remains
identical but tests are isolated and parallelizable.
- Around line 246-248: Remove the redundant loop-variable shadowing inside the
test loop: delete the line "tt := tt" so the for-range variable tt is used
directly in the t.Run closure (the t.Run call and testCases loop remain
unchanged); this relies on Go 1.22+ per-iteration variable semantics and
simplifies the test in internal_helpers_test.go by eliminating unnecessary
shadowing of tt.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b13f40c7-5687-4e43-b4b8-4fe53c4d8530

📥 Commits

Reviewing files that changed from the base of the PR and between 87fc417 and f71398e.

⛔ Files ignored due to path filters (10)
  • .compozy/tasks/daemon/reviews-002/_meta.md is excluded by !**/*.md
  • .compozy/tasks/daemon/reviews-002/issue_001.md is excluded by !**/*.md
  • .compozy/tasks/daemon/reviews-002/issue_002.md is excluded by !**/*.md
  • .compozy/tasks/daemon/reviews-002/issue_003.md is excluded by !**/*.md
  • .compozy/tasks/daemon/reviews-002/issue_004.md is excluded by !**/*.md
  • .compozy/tasks/daemon/reviews-002/issue_005.md is excluded by !**/*.md
  • .compozy/tasks/daemon/reviews-002/issue_006.md is excluded by !**/*.md
  • .compozy/tasks/daemon/reviews-002/issue_007.md is excluded by !**/*.md
  • .compozy/tasks/daemon/reviews-002/issue_008.md is excluded by !**/*.md
  • .compozy/tasks/daemon/reviews-002/issue_009.md is excluded by !**/*.md
📒 Files selected for processing (10)
  • cmd/compozy/main.go
  • cmd/compozy/main_test.go
  • internal/api/client/client.go
  • internal/api/client/reviews_exec_test.go
  • internal/api/core/handlers_test.go
  • internal/api/core/internal_helpers_test.go
  • internal/api/core/sse.go
  • internal/api/core/sse_test.go
  • internal/cli/daemon.go
  • internal/cli/daemon_commands_test.go
✅ Files skipped from review due to trivial changes (3)
  • internal/api/core/handlers_test.go
  • internal/api/client/reviews_exec_test.go
  • internal/api/core/sse_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • cmd/compozy/main_test.go
  • cmd/compozy/main.go

@pedronauck
pedronauck merged commit ab0d26c into main Apr 18, 2026
4 of 5 checks passed
@pedronauck
pedronauck deleted the pn/daemon branch April 18, 2026 21:55
@coderabbitai coderabbitai Bot mentioned this pull request Apr 20, 2026
This was referenced Apr 21, 2026
This was referenced Apr 21, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Apr 28, 2026
This was referenced Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant