Repository navigation
refactor: daemon improvs - #121
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (7)
📒 Files selected for processing (3)
Disabled knowledge base sources:
WalkthroughAdds a canonical API contract (types, errors, timeouts, SSE cursors), migrates client and handlers to use the contract, implements run integrity persistence/metrics, introduces run modes and bounded shutdown contexts, a rotating daemon logger, refactors the run reader to use the API client, and adds extensive tests and QA fixtures. Changes
Sequence Diagram(s)sequenceDiagram
participant CLI as Client (apiclient)
participant API as API Server (Gin)
participant RM as RunManager
participant DB as RunDB
rect rgba(220,220,255,0.5)
CLI->>API: GET /api/runs/:id/snapshot or /stream (contract request)
API->>RM: Request snapshot or open stream (contract types)
RM->>DB: GetIntegrity / Read events & transcript
DB-->>RM: RunSnapshot + integrity state
RM-->>API: Assembled contract RunSnapshotResponse or SSE frames (heartbeat/overflow with formatted cursor)
API-->>CLI: JSON response or SSE frames
end
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: 14
🧹 Nitpick comments (17)
internal/store/sqlite_test.go (1)
11-71: Restructure these into table-driven subtests.This file is exercising one small outcome matrix for
CloseSQLiteDatabase, so a singleTestCloseSQLiteDatabasewitht.Run("Should ...")cases would fit the repo test pattern better and make the remaining branches easier to cover.As per coding guidelines,
**/*_test.go:Table-driven tests with subtests (t.Run) as the default pattern; andMUST 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/store/sqlite_test.go` around lines 11 - 71, Combine the two separate tests TestCloseSQLiteDatabaseCheckpointsBeforeClose and TestCloseSQLiteDatabaseStillClosesWhenCheckpointFails into a single table-driven test named TestCloseSQLiteDatabase that uses t.Run("Should ...") subtests; create a slice of test cases describing the scenario (e.g., success checkpoint vs failing checkpoint) and for each case set up the mocked checkpointSQLiteWAL and closeSQLiteHandle behaviors, run CloseSQLiteDatabase(context.Background(), &sql.DB{}), and assert the expected error and whether closeSQLiteHandle was invoked and the order of steps; ensure you restore original checkpointSQLiteWAL and closeSQLiteHandle via t.Cleanup and keep references to those symbols to locate the existing mocks.internal/store/rundb/close_test.go (1)
11-42: Wrap this scenario in at.Run("Should ...")subtest.The assertions are fine, but this file should still follow the repository's default subtest pattern so additional close-context cases can land without reshaping the test later.
As per coding guidelines,
**/*_test.go:Use table-driven tests with subtests (t.Run) as the default pattern; andMUST 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/store/rundb/close_test.go` around lines 11 - 42, Wrap the existing TestRunDBCloseContextDelegatesToSQLiteCloser body in a t.Run subtest named with the "Should ..." pattern (e.g. t.Run("Should delegate CloseContext to sqlite closer", func(t *testing.T) { ... })), moving all setup, the t.Cleanup restore of closeRunSQLiteDatabase, the stub assignment to closeRunSQLiteDatabase, creation of db and runDB, the ctx creation using runDBCloseContextKey("scope"), the call to runDB.CloseContext(ctx), and all assertions into that subtest closure so the test follows the repository's subtest pattern while preserving the original behavior and cleanup semantics.internal/core/run/journal/journal.go (1)
404-414: Consider consolidating duplicate terminal event classification.
isTerminalJournalEventduplicates the logic ofisTerminalEvent(lines 768-778). Both check the same four event kinds (RunCompleted,RunFailed,RunCancelled,RunCrashed). If terminal event kinds change, both functions must be updated in sync.Consider reusing
isTerminalEventinrecordDroppedSubmitto avoid drift:♻️ Proposed refactor
func (j *Journal) recordDroppedSubmit(req submitRequest) { if j == nil || req.kind != submitRequestEvent { return } - if isTerminalJournalEvent(req.event.Kind) { + if isTerminalEvent(req.event.Kind) { j.terminalDrops.Add(1) return } j.nonTerminalDrops.Add(1) } - -func isTerminalJournalEvent(kind events.EventKind) bool { - switch kind { - case events.EventKindRunCompleted, - events.EventKindRunFailed, - events.EventKindRunCancelled, - events.EventKindRunCrashed: - return true - default: - return false - } -}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/run/journal/journal.go` around lines 404 - 414, The function isTerminalJournalEvent duplicates the terminal-kind check already implemented by isTerminalEvent; update recordDroppedSubmit (and any other callers) to call isTerminalEvent instead of isTerminalJournalEvent, then remove or deprecate isTerminalJournalEvent to eliminate duplicated logic; ensure all references (e.g., in recordDroppedSubmit) are updated to use isTerminalEvent so terminal-kind changes remain centralized.internal/cli/daemon_exec_test_helpers_test.go (1)
68-72: Consider logging or commenting on ignored shutdown error.Per coding guidelines, errors should not be ignored with
_without justification. While test cleanup errors are often non-critical, a brief comment ort.Logfwould clarify intent:♻️ Proposed options
Option 1 - Add comment:
t.Cleanup(func() { shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 2*time.Second) defer shutdownCancel() - _ = manager.Shutdown(shutdownCtx, true) + // Best-effort cleanup; test result already determined + _ = manager.Shutdown(shutdownCtx, true) })Option 2 - Log on error:
t.Cleanup(func() { shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 2*time.Second) defer shutdownCancel() - _ = manager.Shutdown(shutdownCtx, true) + if err := manager.Shutdown(shutdownCtx, true); err != nil { + t.Logf("cleanup: manager.Shutdown error = %v", err) + } })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/cli/daemon_exec_test_helpers_test.go` around lines 68 - 72, The test currently ignores the error returned by manager.Shutdown inside the t.Cleanup closure; either handle the error or document why it's safe to ignore it. Update the t.Cleanup block around manager.Shutdown(shutdownCtx, true) to capture the returned error and call t.Logf or t.Fatalf on non-nil error (or add a short comment explaining why suppressing the error is acceptable for test cleanup), referencing the existing symbols shutdownCtx, shutdownCancel, manager.Shutdown and t.Cleanup so reviewers can locate and verify the change.internal/logger/logger.go (1)
283-289: Consider usingfilepath.Cleanfor path normalization.
normalizeFilePathonly trims whitespace but doesn't clean the path. Usingfilepath.Cleanwould normalize..,., and redundant separators, preventing potential path traversal issues and ensuring consistent paths for rotation.♻️ Proposed refactor
func normalizeFilePath(path string) (string, error) { cleanPath := strings.TrimSpace(path) if cleanPath == "" { return "", errors.New("logger: daemon log file path is required") } - return cleanPath, nil + return filepath.Clean(cleanPath), nil }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/logger/logger.go` around lines 283 - 289, normalizeFilePath currently only trims whitespace; update it to use filepath.Clean to normalize path components (collapse "..", ".", redundant separators) before returning. Specifically, after trimming in normalizeFilePath, call filepath.Clean(cleanPath) and return that cleaned value (still return the existing error when the trimmed string is empty); import "path/filepath" if not present. This ensures consistent, normalized paths for log rotation and reduces path traversal issues.internal/logger/logger_test.go (1)
12-12: Addt.Parallel()to enable concurrent test execution.These tests are independent (each uses
t.TempDir()for isolation) and should run in parallel per coding guidelines.♻️ Proposed fix
func TestInstallDaemonLoggerForegroundMirrorsToStderrAndFile(t *testing.T) { + t.Parallel() logPath := filepath.Join(t.TempDir(), "daemon.log")func TestInstallDaemonLoggerDetachedWritesOnlyFile(t *testing.T) { + t.Parallel() logPath := filepath.Join(t.TempDir(), "daemon.log")func TestOpenRotatingFileRotatesAtConfiguredSize(t *testing.T) { + t.Parallel() logPath := filepath.Join(t.TempDir(), "daemon.log")func TestValidateDaemonFilePathRejectsEmptyPath(t *testing.T) { + t.Parallel() if err := ValidateDaemonFilePath(" "); err == nil {As per coding guidelines: "Use
t.Parallel()for independent subtests".Also applies to: 47-47, 77-77, 107-107
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/logger/logger_test.go` at line 12, Add t.Parallel() as the first statement in each independent test function in this file to enable concurrent execution; specifically insert it at the top of TestInstallDaemonLoggerForegroundMirrorsToStderrAndFile and the other three peer tests in the same file so each test (which already uses t.TempDir() for isolation) runs in parallel. Ensure t.Parallel() is the immediate first action inside the test function block for TestInstallDaemonLoggerForegroundMirrorsToStderrAndFile and the other test functions to follow the coding guideline.internal/cli/reviews_exec_daemon_additional_test.go (1)
1389-1424: Align this new subtest with enforced test conventions.Please switch to the
Should...naming convention and mark it parallel if independence is intended.♻️ Suggested update
- t.Run("defaultWatchCLIRun waits for durable terminal snapshot after terminal event", func(t *testing.T) { + t.Run("Should wait for a durable terminal snapshot after terminal event in defaultWatchCLIRun", func(t *testing.T) { + t.Parallel() stream := newStaticClientRunStream() stream.items <- apiclient.RunStreamItem{ Event: &eventspkg.Event{As per coding guidelines
**/*_test.go:MUST use t.Run("Should...") pattern for ALL test casesandUse t.Parallel() for independent subtests.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cli/reviews_exec_daemon_additional_test.go` around lines 1389 - 1424, Rename the subtest title to follow the "Should..." pattern (e.g., t.Run("Should wait for durable terminal snapshot after terminal event", ...) instead of the current text) and make the subtest run in parallel by calling t.Parallel() at the start of the subtest body; update the t.Run invocation and insert t.Parallel() inside the anonymous func used with t.Run that exercises defaultWatchCLIRun (the test that uses newStaticClientRunStream, stubDaemonCommandClient and snapshotFunc).pkg/compozy/runs/run_test.go (1)
62-82: Wrap this case in aShould...subtest and run it in parallel.The assertions are good; this is just to keep test structure aligned with enforced project standards.
♻️ Suggested update
func TestAdaptRemoteRunSnapshotPreservesIncompleteReasons(t *testing.T) { - got := adaptRemoteRunSnapshot(apicore.RunSnapshot{ - Run: apicore.Run{Status: publicRunStatusFailed}, - Incomplete: true, - IncompleteReasons: []string{"event_gap", "transcript_gap"}, - NextCursor: &apicore.StreamCursor{ - Timestamp: time.Date(2026, 4, 21, 7, 0, 0, 0, time.UTC), - Sequence: 7, - }, - }) + t.Run("Should preserve incomplete reasons and next cursor", func(t *testing.T) { + t.Parallel() + got := adaptRemoteRunSnapshot(apicore.RunSnapshot{ + Run: apicore.Run{Status: publicRunStatusFailed}, + Incomplete: true, + IncompleteReasons: []string{"event_gap", "transcript_gap"}, + NextCursor: &apicore.StreamCursor{ + Timestamp: time.Date(2026, 4, 21, 7, 0, 0, 0, time.UTC), + Sequence: 7, + }, + }) - if !got.Incomplete { - t.Fatal("Incomplete = false, want true") - } - if want := []string{"event_gap", "transcript_gap"}; !reflect.DeepEqual(got.IncompleteReasons, want) { - t.Fatalf("IncompleteReasons = %#v, want %#v", got.IncompleteReasons, want) - } - if got.NextCursor == nil || got.NextCursor.Sequence != 7 { - t.Fatalf("NextCursor = %#v, want sequence 7", got.NextCursor) - } + if !got.Incomplete { + t.Fatal("Incomplete = false, want true") + } + if want := []string{"event_gap", "transcript_gap"}; !reflect.DeepEqual(got.IncompleteReasons, want) { + t.Fatalf("IncompleteReasons = %#v, want %#v", got.IncompleteReasons, want) + } + if got.NextCursor == nil || got.NextCursor.Sequence != 7 { + t.Fatalf("NextCursor = %#v, want sequence 7", got.NextCursor) + } + }) }As per coding guidelines
**/*_test.go:MUST use t.Run("Should...") pattern for ALL test casesandUse t.Parallel() for independent subtests.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/compozy/runs/run_test.go` around lines 62 - 82, Wrap the existing assertions in TestAdaptRemoteRunSnapshotPreservesIncompleteReasons inside a subtest using t.Run with a "Should..." description (e.g., t.Run("Should preserve incomplete reasons and next cursor", func(t *testing.T) { ... })), and call t.Parallel() at the start of that subtest; keep the call to adaptRemoteRunSnapshot(apicore.RunSnapshot{...}) and the current assertions unchanged but moved into the subtest body to satisfy the test naming and parallelization guidelines.internal/api/contract/compatibility.go (1)
3-8: Consider adding documentation comments for exported types.Exported types should have documentation comments explaining their purpose.
📝 Proposed documentation
package contract +// CompatibilityNote documents stable fields and breaking change categories +// for a contract surface used by downstream consumers. type CompatibilityNote struct { Surface string StableJSONFields []string AdapterRequiredFor []string Notes []string }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/api/contract/compatibility.go` around lines 3 - 8, Add a documentation comment for the exported CompatibilityNote type that explains its purpose and what each field represents; update the comment immediately above the type declaration for CompatibilityNote and describe Surface, StableJSONFields, AdapterRequiredFor, and Notes so external users understand when and how to use this struct.internal/cli/daemon_commands_test.go (2)
1087-1088: Missingt.Parallel()for independent test.Per coding guidelines, independent tests should use
t.Parallel()for parallel execution.♻️ Proposed fix
func TestDaemonStartCommandInternalChildUsesDetachedRunMode(t *testing.T) { + t.Parallel() + acquireCLITestGlobalOverride(t)🤖 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 1087 - 1088, The test function TestDaemonStartCommandInternalChildUsesDetachedRunMode is missing t.Parallel() so it can't run concurrently; open that function and add a call to t.Parallel() immediately after the test begins (after acquireCLITestGlobalOverride(t) if that setup must run first, or at the top of the function if safe) to mark it as parallelizable, ensuring it follows the project's parallel test conventions.
1132-1133: Missingt.Parallel()for independent test.This test uses
t.TempDir()for filesystem isolation and doesn't share state with other tests, so it should run in parallel.♻️ Proposed fix
func TestLaunchCLIDaemonProcessFailsWhenDaemonLogFileCannotBeOpened(t *testing.T) { + t.Parallel() + paths, err := compozyconfig.ResolveHomePathsFrom(t.TempDir())🤖 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 1132 - 1133, Add t.Parallel() at the start of TestLaunchCLIDaemonProcessFailsWhenDaemonLogFileCannotBeOpened to allow it to run concurrently; locate the test function declaration in internal/cli/daemon_commands_test.go (function name: TestLaunchCLIDaemonProcessFailsWhenDaemonLogFileCannotBeOpened) and insert a call to t.Parallel() immediately after the first line in the function body so the test uses its t.TempDir() isolation safely in parallel.internal/daemon/run_manager.go (1)
524-538: Consider error handling forpersistRuntimeIntegrityfailure.While the warning log is appropriate, consider whether a persistence failure should affect the snapshot response. The current behavior continues despite the error, which may be intentional for resilience but could mask issues.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/daemon/run_manager.go` around lines 524 - 538, The call to m.persistRuntimeIntegrity currently only logs a warning and proceeds; decide whether to fail fast or surface the persistence error in the snapshot: either (A) propagate the error by returning it after the call to m.persistRuntimeIntegrity (e.g., wrap and return the error from the enclosing function) so the snapshot creation stops on persistence failure, or (B) attach the persistence error to the returned RunSnapshot (add a RuntimeIntegrityError/RuntimeIntegrityStatus field) so callers can observe the failure while still returning the snapshot; implement the chosen approach at the m.persistRuntimeIntegrity call site (just before loadRunIntegrity) and ensure the log remains informative.internal/daemon/boot_integration_test.go (1)
514-523: Consider using a ticker instead oftime.Sleepfor consistency.The other polling helpers (
waitForHealthyDaemon,waitForManagedDaemonReady) usetime.NewTickerwith proper cleanup, but this helper usestime.Sleep. While acceptable in test code, using a consistent pattern would improve readability.♻️ Suggested refactor for consistency
func waitForDaemonState(t *testing.T, paths compozyconfig.HomePaths, want ReadyState) { t.Helper() deadline := time.Now().Add(10 * time.Second) + ticker := time.NewTicker(50 * time.Millisecond) + defer ticker.Stop() + for time.Now().Before(deadline) { status, err := QueryStatus(context.Background(), paths, ProbeOptions{}) if err == nil && status.State == want { return } - time.Sleep(50 * time.Millisecond) + <-ticker.C } t.Fatalf("daemon did not reach state %q within timeout", want) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/daemon/boot_integration_test.go` around lines 514 - 523, The polling loop that checks QueryStatus (using ProbeOptions{}) currently uses time.Sleep; replace it with a time.NewTicker-based loop with defer ticker.Stop() to match the pattern used by waitForHealthyDaemon/waitForManagedDaemonReady: create a ticker (e.g., 50ms), check status, break/return when status.State == want, and also check a deadline via time.Now().After(deadline) to t.Fatalf on timeout; reference QueryStatus, ProbeOptions, and the local want variable when updating the loop.internal/daemon/shutdown.go (1)
165-183: Consider documenting the null-byte separator convention.Using
\x00as a separator injoinRunManagerMetricKey/splitRunManagerMetricKeyis safe since mode and status values won't contain null bytes, but it's unconventional. A brief comment explaining this choice would improve maintainability.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/daemon/shutdown.go` around lines 165 - 183, Add a short comment above the joinRunManagerMetricKey and splitRunManagerMetricKey functions documenting that they use a null-byte ("\x00") as an internal separator, why that choice is safe (mode/status values will never contain null bytes), and that the functions trim inputs and treat empty parts as invalid; this clarifies the convention for future maintainers and prevents confusion about the unconventional delimiter.internal/daemon/run_integrity.go (1)
31-42: Wrap these integrity-store failures with run context.The bare returns here lose the run ID and phase (
acquire,get,upsert), which will make daemon-side integrity failures much harder to trace once they bubble into logs.As per coding guidelines, "Prefer explicit error returns with wrapped context using
fmt.Errorf("context: %w", err)".Also applies to: 70-89
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/daemon/run_integrity.go` around lines 31 - 42, Wrap the raw errors returned from acquireRunDB and the DB integrity operations with contextual run information including runID and phase names; e.g., when calling acquireRunDB(ctx, runID) return fmt.Errorf("acquire run DB for run %s: %w", runID, err) on error, and when lease.DB().UpsertIntegrity(ctx, update) (and the corresponding get/upsert calls on lines 70-89) return fmt.Errorf("upsert integrity for run %s: %w", runID, err) (or use "get integrity" for get calls) so every error includes the run ID and operation phase.internal/api/core/handlers_contract_test.go (1)
398-492: Extract the SSE frame parser into a shared test helper.This frame type and parser logic are now duplicated across multiple new test files. Keeping separate copies in sync will get brittle the next time the SSE contract changes.
As per coding guidelines, "Check for shared test utilities usage to avoid duplication".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/api/core/handlers_contract_test.go` around lines 398 - 492, The SSE frame type and parser (canonicalSSEFrame, readSSEFramesUntil, hasSSEFrame, firstSSEFrame and the helper newCanonicalHandlersEngine) are duplicated across tests; extract them into a single shared test helper (e.g., internal/api/testhelpers/sse_helpers_test.go) and replace the copies in all test files to import/use that helper; move the type and functions into the shared file, keep them unexported if intended for package-level use or export them if cross-package, and update any tests referencing the old local definitions to call the shared functions so there is a single canonical implementation to maintain.internal/api/contract/sse.go (1)
78-80: Split the parse failure from the zero-sequence validation.
err != nil || sequence == 0drops the underlyingParseUinterror, so callers lose the%wchain here. Keep the parse failure wrapped and handle0as a separate validation error.Suggested change
sequence, err := strconv.ParseUint(strings.TrimSpace(parts[1]), 10, 64) - if err != nil || sequence == 0 { + if err != nil { + return StreamCursor{}, fmt.Errorf("invalid cursor sequence %q: %w", parts[1], err) + } + if sequence == 0 { return StreamCursor{}, fmt.Errorf("invalid cursor sequence %q", parts[1]) }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/api/contract/sse.go` around lines 78 - 80, The parse-and-validate for the cursor sequence currently collapses the ParseUint error into a generic "invalid cursor sequence" return; change the logic in sse.go so you first call strconv.ParseUint and if err != nil return a wrapped error like fmt.Errorf("parsing cursor sequence %q: %w", parts[1], err), then separately validate sequence==0 and return the existing fmt.Errorf("invalid cursor sequence %q", parts[1]) (returning StreamCursor{} on each error) so the original ParseUint error is preserved and wrapped per guidelines.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-run-id.txt:
- Line 1: Remove the committed one-off QA run ID string
"tasks-node-health-8904e3-20260421-054130-000000000" from the repository and
stop committing this generated state: either delete the file content and add the
filename to .gitignore (or move it under an ignored runtime/ci artifacts
folder), or change the workflow that writes it so the run ID is emitted as a
CI/test artifact instead of being checked into the tree; update any references
to the file so code expecting it reads from the new ignored/CI artifact
location.
In `@internal/api/client/client_contract_test.go`:
- Around line 249-253: This test mutates the package-level variable
streamHeartbeatGapTolerance while also using t.Parallel(), risking races; remove
t.Parallel() from the test (or alternatively change the test to inject a
per-test configuration instead of modifying streamHeartbeatGapTolerance) and
keep the existing t.Cleanup that restores the previousGap to ensure isolation;
locate the mutation of streamHeartbeatGapTolerance and the t.Parallel() call in
the test and delete the parallelization (or refactor to accept a test-specific
timeout/config parameter rather than touching the package variable).
In `@internal/api/client/client_transport_test.go`:
- Around line 201-213: The test stub for the stop endpoint is too permissive and
doesn't assert the StopDaemon(force bool) encoding; update the Transport
roundTripFunc handler for the case handling "POST /api/daemon/stop" to validate
the request shape (either check req.URL.Query() for a force=true param or decode
the JSON body and assert {"force":true} as appropriate for StopDaemon), and only
return jsonResponse(http.StatusAccepted, `{"accepted":true}`) when that shape
matches; ensure the test fails if StopDaemon(true) omits or mis-encodes the
force flag so the transport contract for StopDaemon is actually enforced.
In `@internal/api/contract/contract_integration_test.go`:
- Around line 207-230: In the loop handling framesCh, avoid closing sendOverflow
on every heartbeat; change the heartbeat branch (where you currently call
close(sendOverflow)) to only close it once by checking if sendOverflow != nil
before closing and then set sendOverflow = nil (or use a boolean flag) so
subsequent heartbeat events won't attempt to close an already-closed channel;
update the code around the heartbeat handling in the test (where heartbeat,
overflow, framesCh, and sendOverflow are referenced) accordingly.
In `@internal/api/contract/types.go`:
- Around line 500-513: In RunEventPageResponse.Decode, add a fast-fail when the
server declares HasMore but doesn't provide a NextCursor: if r.HasMore &&
r.NextCursor == "" return an error (e.g. fmt.Errorf("decode events:
has_more=true but next_cursor missing")). Keep the ParseCursor call and its
error handling for non-empty cursors, and only set page.NextCursor when
ParseCursor succeeds and nextCursor.Sequence > 0; reference
RunEventPageResponse.Decode, RunEventPage, r.HasMore, r.NextCursor, and
ParseCursor.
- Around line 245-273: The nested session snapshot structs (SessionEntry,
SessionPlanState, SessionMetaState, SessionViewSnapshot) are missing JSON tags
so the marshalled fields use PascalCase instead of the expected snake_case;
update each struct's fields (e.g., SessionEntry.ID, SessionEntry.Kind,
SessionPlanState.Entries, SessionPlanState.PendingCount,
SessionMetaState.CurrentModeID,
SessionViewSnapshot.Revision/Entries/Plan/Session, etc.) to include explicit
`json:"..."` tags using the expected snake_case names (e.g., `id`, `kind`,
`title`, `preview`, `tool_call_id`, `tool_call_state`, `blocks`, `entries`,
`pending_count`, `running_count`, `done_count`, `current_mode_id`,
`available_commands`, `status`, `revision`, `plan`, `session`) so JSON
encoding/decoding matches the wire contract.
In `@internal/api/httpapi/transport_integration_test.go`:
- Around line 1789-1851: readSSEFramesUntil currently takes an io.Reader so when
we return early (stop(frames) == true) the scanner goroutine keeps blocking on
body and leaks; change the function to accept an io.ReadCloser (or add a
separate io.Closer param), and ensure you Close() the body when exiting early so
the scanner unblocks; update the goroutine/cleanup to still send scanner.Err()
to errCh and close linesCh as before, and call body.Close() (or defer a close on
normal return) immediately before returning from readSSEFramesUntil to ensure
the scanner goroutine terminates.
- Around line 1889-1893: The test helper currently ignores the error returned by
io.ReadAll when building the failure message for a non-OK response; update the
error path in the block that checks response.StatusCode (the code using
response, io.ReadAll and t.Fatalf) to properly handle the ReadAll error by
checking and including it in the failure output (e.g., if io.ReadAll returns an
error, include that error string in the t.Fatalf message alongside or instead of
the body content), ensure response.Body is deferred closed before reading, and
avoid using the blank identifier for the read error.
In `@internal/daemon/boot_test.go`:
- Around line 550-560: Replace the manual temp-dir creation and cleanup with
t.TempDir(): stop using os.TempDir()/os.MkdirTemp and the homeRoot variable plus
t.Cleanup + os.RemoveAll; instead call homeRoot := t.TempDir() where the current
MkdirTemp result is used and remove the explicit t.Cleanup/_ = os.RemoveAll
block so the test harness manages cleanup and no errors are ignored.
In `@internal/daemon/watchers.go`:
- Around line 253-261: The pre-sync reconcile clears state.pending and
state.refreshWatches before calling w.reconcileWatchState(watcher, state, ...)
so a failed reconcile drops queued changes; change the flow to preserve those
flags until reconcileWatchState succeeds — either move the clearing of
state.pending and state.refreshWatches to after a successful return from
reconcileWatchState, or if you must clear earlier, restore/re-queue them when
reconcileWatchState returns false; update the logic around reconcileWatchState,
state.pending and state.refreshWatches to ensure pending artifact changes are
not lost on transient reconcile failures.
In `@internal/store/globaldb/close_test.go`:
- Around line 11-42: Wrap the existing
TestGlobalDBCloseContextDelegatesToSQLiteCloser logic inside a named subtest
using t.Run with a "Should..." description (e.g., t.Run("Should propagate caller
context and close underlying sqlite DB", func(t *testing.T) { ... })), keeping
the existing setup that overrides closeGlobalSQLiteDatabase and assertions
against gotCtx, gotDB and global.closed; if you anticipate more scenarios,
convert to a table-driven loop and call t.Run for each case, but at minimum
enclose the current test body in a single t.Run("Should...") subtest to match
repository test conventions.
In `@internal/store/globaldb/global_db.go`:
- Around line 80-84: The code currently flips g.closed to true before calling
closeGlobalSQLiteDatabase, preventing retries on failure; change the logic so
g.closed is only set to true after closeGlobalSQLiteDatabase succeeds.
Specifically, acquire a single-attempt lock (either add a new atomic g.closing
used with CompareAndSwap(false,true) to guard concurrent closes, or keep the
existing CompareAndSwap but if you set g.closed to true only after err == nil),
call closeGlobalSQLiteDatabase(ctx, g.db), and if that call returns an error
ensure the in-progress flag is cleared (or do not set g.closed) so subsequent
calls can retry; finally set g.closed to true only on successful close.
In `@internal/store/rundb/run_db.go`:
- Around line 759-810: The read-modify-write race happens because GetIntegrity()
and the subsequent upsert (r.db.ExecContext with ON CONFLICT) are separate
operations; wrap the read + merge + upsert in a single database transaction to
make the merge atomic. Specifically, start a transaction (e.g., tx, err :=
r.db.BeginTx(ctx, nil)), call a transactional variant of GetIntegrity that
selects the singleton row inside the tx (using tx.QueryRowContext or SELECT ...
FOR UPDATE if supported), perform the same merge into RunIntegrityState (using
r.now() and encodeIntegrityReasons), then execute the INSERT ... ON CONFLICT
upsert using tx.ExecContext and commit the tx; ensure errors roll back the tx.
This keeps the existing logic (mergeIntegrityReasons, encodeIntegrityReasons,
r.now, store.FormatTimestamp) but performs all operations using the tx instead
of r.db to prevent lost updates.
- Around line 147-156: The CloseContext method currently sets r.db = nil before
calling closeRunSQLiteDatabase, which hides failures; change it so you capture
the db handle into a local variable (db := r.db), call
closeRunSQLiteDatabase(ctx, db) first, check its error, and only on success set
r.db = nil and return nil; on error leave r.db intact and return the error.
Update the CloseContext implementation (referencing CloseContext, r.db, and
closeRunSQLiteDatabase) to nil out the field only after a successful close.
---
Nitpick comments:
In `@internal/api/contract/compatibility.go`:
- Around line 3-8: Add a documentation comment for the exported
CompatibilityNote type that explains its purpose and what each field represents;
update the comment immediately above the type declaration for CompatibilityNote
and describe Surface, StableJSONFields, AdapterRequiredFor, and Notes so
external users understand when and how to use this struct.
In `@internal/api/contract/sse.go`:
- Around line 78-80: The parse-and-validate for the cursor sequence currently
collapses the ParseUint error into a generic "invalid cursor sequence" return;
change the logic in sse.go so you first call strconv.ParseUint and if err != nil
return a wrapped error like fmt.Errorf("parsing cursor sequence %q: %w",
parts[1], err), then separately validate sequence==0 and return the existing
fmt.Errorf("invalid cursor sequence %q", parts[1]) (returning StreamCursor{} on
each error) so the original ParseUint error is preserved and wrapped per
guidelines.
In `@internal/api/core/handlers_contract_test.go`:
- Around line 398-492: The SSE frame type and parser (canonicalSSEFrame,
readSSEFramesUntil, hasSSEFrame, firstSSEFrame and the helper
newCanonicalHandlersEngine) are duplicated across tests; extract them into a
single shared test helper (e.g., internal/api/testhelpers/sse_helpers_test.go)
and replace the copies in all test files to import/use that helper; move the
type and functions into the shared file, keep them unexported if intended for
package-level use or export them if cross-package, and update any tests
referencing the old local definitions to call the shared functions so there is a
single canonical implementation to maintain.
In `@internal/cli/daemon_commands_test.go`:
- Around line 1087-1088: The test function
TestDaemonStartCommandInternalChildUsesDetachedRunMode is missing t.Parallel()
so it can't run concurrently; open that function and add a call to t.Parallel()
immediately after the test begins (after acquireCLITestGlobalOverride(t) if that
setup must run first, or at the top of the function if safe) to mark it as
parallelizable, ensuring it follows the project's parallel test conventions.
- Around line 1132-1133: Add t.Parallel() at the start of
TestLaunchCLIDaemonProcessFailsWhenDaemonLogFileCannotBeOpened to allow it to
run concurrently; locate the test function declaration in
internal/cli/daemon_commands_test.go (function name:
TestLaunchCLIDaemonProcessFailsWhenDaemonLogFileCannotBeOpened) and insert a
call to t.Parallel() immediately after the first line in the function body so
the test uses its t.TempDir() isolation safely in parallel.
In `@internal/cli/daemon_exec_test_helpers_test.go`:
- Around line 68-72: The test currently ignores the error returned by
manager.Shutdown inside the t.Cleanup closure; either handle the error or
document why it's safe to ignore it. Update the t.Cleanup block around
manager.Shutdown(shutdownCtx, true) to capture the returned error and call
t.Logf or t.Fatalf on non-nil error (or add a short comment explaining why
suppressing the error is acceptable for test cleanup), referencing the existing
symbols shutdownCtx, shutdownCancel, manager.Shutdown and t.Cleanup so reviewers
can locate and verify the change.
In `@internal/cli/reviews_exec_daemon_additional_test.go`:
- Around line 1389-1424: Rename the subtest title to follow the "Should..."
pattern (e.g., t.Run("Should wait for durable terminal snapshot after terminal
event", ...) instead of the current text) and make the subtest run in parallel
by calling t.Parallel() at the start of the subtest body; update the t.Run
invocation and insert t.Parallel() inside the anonymous func used with t.Run
that exercises defaultWatchCLIRun (the test that uses newStaticClientRunStream,
stubDaemonCommandClient and snapshotFunc).
In `@internal/core/run/journal/journal.go`:
- Around line 404-414: The function isTerminalJournalEvent duplicates the
terminal-kind check already implemented by isTerminalEvent; update
recordDroppedSubmit (and any other callers) to call isTerminalEvent instead of
isTerminalJournalEvent, then remove or deprecate isTerminalJournalEvent to
eliminate duplicated logic; ensure all references (e.g., in recordDroppedSubmit)
are updated to use isTerminalEvent so terminal-kind changes remain centralized.
In `@internal/daemon/boot_integration_test.go`:
- Around line 514-523: The polling loop that checks QueryStatus (using
ProbeOptions{}) currently uses time.Sleep; replace it with a
time.NewTicker-based loop with defer ticker.Stop() to match the pattern used by
waitForHealthyDaemon/waitForManagedDaemonReady: create a ticker (e.g., 50ms),
check status, break/return when status.State == want, and also check a deadline
via time.Now().After(deadline) to t.Fatalf on timeout; reference QueryStatus,
ProbeOptions, and the local want variable when updating the loop.
In `@internal/daemon/run_integrity.go`:
- Around line 31-42: Wrap the raw errors returned from acquireRunDB and the DB
integrity operations with contextual run information including runID and phase
names; e.g., when calling acquireRunDB(ctx, runID) return fmt.Errorf("acquire
run DB for run %s: %w", runID, err) on error, and when
lease.DB().UpsertIntegrity(ctx, update) (and the corresponding get/upsert calls
on lines 70-89) return fmt.Errorf("upsert integrity for run %s: %w", runID, err)
(or use "get integrity" for get calls) so every error includes the run ID and
operation phase.
In `@internal/daemon/run_manager.go`:
- Around line 524-538: The call to m.persistRuntimeIntegrity currently only logs
a warning and proceeds; decide whether to fail fast or surface the persistence
error in the snapshot: either (A) propagate the error by returning it after the
call to m.persistRuntimeIntegrity (e.g., wrap and return the error from the
enclosing function) so the snapshot creation stops on persistence failure, or
(B) attach the persistence error to the returned RunSnapshot (add a
RuntimeIntegrityError/RuntimeIntegrityStatus field) so callers can observe the
failure while still returning the snapshot; implement the chosen approach at the
m.persistRuntimeIntegrity call site (just before loadRunIntegrity) and ensure
the log remains informative.
In `@internal/daemon/shutdown.go`:
- Around line 165-183: Add a short comment above the joinRunManagerMetricKey and
splitRunManagerMetricKey functions documenting that they use a null-byte
("\x00") as an internal separator, why that choice is safe (mode/status values
will never contain null bytes), and that the functions trim inputs and treat
empty parts as invalid; this clarifies the convention for future maintainers and
prevents confusion about the unconventional delimiter.
In `@internal/logger/logger_test.go`:
- Line 12: Add t.Parallel() as the first statement in each independent test
function in this file to enable concurrent execution; specifically insert it at
the top of TestInstallDaemonLoggerForegroundMirrorsToStderrAndFile and the other
three peer tests in the same file so each test (which already uses t.TempDir()
for isolation) runs in parallel. Ensure t.Parallel() is the immediate first
action inside the test function block for
TestInstallDaemonLoggerForegroundMirrorsToStderrAndFile and the other test
functions to follow the coding guideline.
In `@internal/logger/logger.go`:
- Around line 283-289: normalizeFilePath currently only trims whitespace; update
it to use filepath.Clean to normalize path components (collapse "..", ".",
redundant separators) before returning. Specifically, after trimming in
normalizeFilePath, call filepath.Clean(cleanPath) and return that cleaned value
(still return the existing error when the trimmed string is empty); import
"path/filepath" if not present. This ensures consistent, normalized paths for
log rotation and reduces path traversal issues.
In `@internal/store/rundb/close_test.go`:
- Around line 11-42: Wrap the existing
TestRunDBCloseContextDelegatesToSQLiteCloser body in a t.Run subtest named with
the "Should ..." pattern (e.g. t.Run("Should delegate CloseContext to sqlite
closer", func(t *testing.T) { ... })), moving all setup, the t.Cleanup restore
of closeRunSQLiteDatabase, the stub assignment to closeRunSQLiteDatabase,
creation of db and runDB, the ctx creation using runDBCloseContextKey("scope"),
the call to runDB.CloseContext(ctx), and all assertions into that subtest
closure so the test follows the repository's subtest pattern while preserving
the original behavior and cleanup semantics.
In `@internal/store/sqlite_test.go`:
- Around line 11-71: Combine the two separate tests
TestCloseSQLiteDatabaseCheckpointsBeforeClose and
TestCloseSQLiteDatabaseStillClosesWhenCheckpointFails into a single table-driven
test named TestCloseSQLiteDatabase that uses t.Run("Should ...") subtests;
create a slice of test cases describing the scenario (e.g., success checkpoint
vs failing checkpoint) and for each case set up the mocked checkpointSQLiteWAL
and closeSQLiteHandle behaviors, run CloseSQLiteDatabase(context.Background(),
&sql.DB{}), and assert the expected error and whether closeSQLiteHandle was
invoked and the order of steps; ensure you restore original checkpointSQLiteWAL
and closeSQLiteHandle via t.Cleanup and keep references to those symbols to
locate the existing mocks.
In `@pkg/compozy/runs/run_test.go`:
- Around line 62-82: Wrap the existing assertions in
TestAdaptRemoteRunSnapshotPreservesIncompleteReasons inside a subtest using
t.Run with a "Should..." description (e.g., t.Run("Should preserve incomplete
reasons and next cursor", func(t *testing.T) { ... })), and call t.Parallel() at
the start of that subtest; keep the call to
adaptRemoteRunSnapshot(apicore.RunSnapshot{...}) and the current assertions
unchanged but moved into the subtest body to satisfy the test naming and
parallelization guidelines.
🪄 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: 5afc339e-9f96-485a-a602-af09c3e4f43d
⛔ Files ignored due to path filters (45)
.codex/ledger/2026-04-20-MEMORY-canonical-daemon-contract.mdis excluded by!**/*.md.codex/ledger/2026-04-20-MEMORY-shared-transport-contract.mdis excluded by!**/*.md.codex/ledger/2026-04-21-MEMORY-daemon-improvs-qa-plan.mdis excluded by!**/*.md.codex/ledger/2026-04-21-MEMORY-observability-transcript.mdis excluded by!**/*.md.codex/ledger/2026-04-21-MEMORY-runtime-hardening.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/_tasks.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/analysis/qa/issues/BUG-001-terminal-snapshot-race.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-health-http.jsonis excluded by!**/*.json.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-health-uds.jsonis excluded by!**/*.json.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-snapshot-http.jsonis excluded by!**/*.json.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-snapshot-uds.jsonis excluded by!**/*.json.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-start.jsonis excluded by!**/*.json.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-status-http.jsonis excluded by!**/*.json.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-status-uds.jsonis excluded by!**/*.json.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-stop.jsonis excluded by!**/*.json.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-sync.jsonis excluded by!**/*.json.compozy/tasks/daemon-improvs/analysis/qa/test-cases/TC-FUNC-001.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/analysis/qa/test-cases/TC-FUNC-002.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/analysis/qa/test-cases/TC-INT-001.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/analysis/qa/test-cases/TC-INT-002.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/analysis/qa/test-cases/TC-INT-003.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/analysis/qa/test-cases/TC-INT-004.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/analysis/qa/test-cases/TC-INT-005.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/analysis/qa/test-plans/daemon-improvs-analysis-regression.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/analysis/qa/test-plans/daemon-improvs-analysis-test-plan.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/analysis/qa/verification-report.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/memory/MEMORY.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/memory/task_01.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/memory/task_02.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/memory/task_03.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/memory/task_04.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/memory/task_05.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/memory/task_06.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/memory/task_07.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/memory/task_08.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/memory/task_09.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/task_01.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/task_02.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/task_03.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/task_04.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/task_05.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/task_06.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/task_07.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/task_08.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/task_09.mdis excluded by!**/*.md
📒 Files selected for processing (72)
.compozy/tasks/daemon-improvs/analysis/qa/issues/.gitkeep.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-metrics-http.txt.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-metrics-uds.txt.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-run-id.txt.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-stop.stderr.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-stream-http.txt.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-stream-uds.txt.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-task-run.stderr.compozy/tasks/daemon-improvs/analysis/qa/logs/live-daemon-task-run.stdout.compozy/tasks/daemon-improvs/analysis/qa/screenshots/.gitkeepinternal/api/client/client.gointernal/api/client/client_contract_test.gointernal/api/client/client_transport_test.gointernal/api/client/operator.gointernal/api/client/reviews_exec.gointernal/api/client/runs.gointernal/api/contract/compatibility.gointernal/api/contract/contract_integration_test.gointernal/api/contract/contract_test.gointernal/api/contract/doc.gointernal/api/contract/errors.gointernal/api/contract/routes.gointernal/api/contract/sse.gointernal/api/contract/timeout.gointernal/api/contract/types.gointernal/api/core/errors.gointernal/api/core/handlers.gointernal/api/core/handlers_contract_test.gointernal/api/core/interfaces.gointernal/api/core/sse.gointernal/api/httpapi/transport_integration_test.gointernal/cli/daemon.gointernal/cli/daemon_commands.gointernal/cli/daemon_commands_test.gointernal/cli/daemon_exec_test_helpers_test.gointernal/cli/operator_transport_integration_test.gointernal/cli/reviews_exec_daemon_additional_test.gointernal/cli/run_observe.gointernal/core/run/journal/journal.gointernal/core/run/journal/journal_test.gointernal/core/run/ui/remote.gointernal/core/run/ui/remote_test.gointernal/daemon/boot_integration_test.gointernal/daemon/boot_test.gointernal/daemon/host.gointernal/daemon/run_integrity.gointernal/daemon/run_integrity_test.gointernal/daemon/run_manager.gointernal/daemon/run_snapshot.gointernal/daemon/runtime.gointernal/daemon/runtime_test.gointernal/daemon/service.gointernal/daemon/service_test.gointernal/daemon/shutdown.gointernal/daemon/signal_other.gointernal/daemon/signal_unix.gointernal/daemon/watchers.gointernal/logger/logger.gointernal/logger/logger_test.gointernal/store/globaldb/close_test.gointernal/store/globaldb/global_db.gointernal/store/rundb/close_test.gointernal/store/rundb/migrations.gointernal/store/rundb/run_db.gointernal/store/rundb/run_db_test.gointernal/store/sqlite.gointernal/store/sqlite_test.gopkg/compozy/runs/integration_test.gopkg/compozy/runs/remote_watch.gopkg/compozy/runs/run.gopkg/compozy/runs/run_test.gopkg/compozy/runs/transport_test.go
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
internal/store/globaldb/global_db.go (1)
88-90: 🛠️ Refactor suggestion | 🟠 MajorWrap close errors with GlobalDB context.
Line 89 returns a raw error without context. Wrap it using
fmt.Errorf()so failures are attributable from this layer, consistent with the error handling pattern used elsewhere in this file (e.g., lines 61, 80, 108).Suggested patch
if err := closeGlobalSQLiteDatabase(ctx, g.db); err != nil { - return err + return fmt.Errorf("globaldb: close sqlite database: %w", err) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/store/globaldb/global_db.go` around lines 88 - 90, The return of closeGlobalSQLiteDatabase(ctx, g.db) should be wrapped with context using fmt.Errorf so callers can attribute failures to the GlobalDB layer; replace `if err := closeGlobalSQLiteDatabase(ctx, g.db); err != nil { return err }` with a wrapped return like `if err := closeGlobalSQLiteDatabase(ctx, g.db); err != nil { return fmt.Errorf("GlobalDB: closeGlobalSQLiteDatabase failed: %w", err) }`, using fmt.Errorf and %w to preserve the original error.
🧹 Nitpick comments (2)
internal/api/core/handlers_contract_test.go (1)
59-61: Loop variable capture pattern is redundant in Go 1.22+.The
tc := tcpattern on lines 60 and 161 was necessary before Go 1.22 to capture loop variables for parallel subtests. Since Go 1.22+, loop variables are per-iteration by default, making this pattern unnecessary. Consider removing for cleaner code if the project targets Go 1.22+.Also applies to: 160-163
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/api/core/handlers_contract_test.go` around lines 59 - 61, The redundant loop-variable capture "tc := tc" inside the test loops (around the t.Run calls that iterate over testCases in handlers_contract_test.go) should be removed because Go 1.22+ makes loop variables per-iteration; delete the duplicate assignments found in the first t.Run loop and the second occurrence later in the file (the other t.Run block) so the subtests use the loop variable directly (keep the t.Run signature and test case usage unchanged).internal/daemon/boot_integration_test.go (1)
533-562: Replace thetime.Sleeppolling loops with ticker-driven waits.These helpers busy-poll with
time.Sleep(50 * time.Millisecond). That makes the integration tests slower/flakier and is out of line with the other wait helpers in this file, which already useTicker/Timerbased synchronization.As per coding guidelines, "NEVER use
time.Sleep()in orchestration — use proper synchronization primitives instead".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/daemon/boot_integration_test.go` around lines 533 - 562, Both waitForLogContains and waitForStderrContains currently busy-poll with time.Sleep; replace those polling loops with a ticker/timer select loop like the other helpers: create a time.NewTicker(50*time.Millisecond) and time.NewTimer(10*time.Second), defer Stop on the ticker and Timer's drain/Stop, and in a select handle ticker.C to re-check the condition (os.ReadFile + strings.Contains for waitForLogContains, stderr != nil && strings.Contains(stderr.String(), ...) for waitForStderrContains) and timer.C to fail the test with the same diagnostics; keep references to the existing functions and the synchronizedBuffer type to locate the code.
🤖 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/daemon/boot_integration_test.go`:
- Around line 283-353: Rename the subtest names to follow the repository's
"Should..." convention: in
TestManagedDaemonStopEndpointShutsDownAndRemovesSocket change
t.Run("force="+strconv.FormatBool(force), ...) to something like t.Run("Should
stop and remove socket when force="+strconv.FormatBool(force), ...) (referencing
the force loop and StopDaemon/QueryStatus/paths.SocketPath logic), and in
TestManagedDaemonRunModesControlLogging change t.Run("foreground mirrors to
stderr", ...) to t.Run("Should mirror logs to stderr in foreground mode", ...)
and t.Run("detached writes only file", ...) to t.Run("Should write logs only to
file in detached mode", ...) (these refer to startManagedDaemonHelperProcess,
RunModeForeground/RunModeDetached, waitForLogContains and
waitForStderrContains). Ensure no other behavior changes beyond renaming the
t.Run strings.
In `@internal/daemon/watchers_test.go`:
- Around line 218-276: Wrap the existing test body inside a t.Run subtest (e.g.
t.Run("Should preserve state when pre-sync reconcile fails", func(t *testing.T)
{ ... })) and replace the weak nil check on watcher.stopError() with a specific
error assertion that the recorded error is non-nil and its message contains a
reconcile-related substring; for example, call err := watcher.stopError(); if
err == nil { t.Fatal(...) } else if !strings.Contains(err.Error(), "reconcile")
{ t.Fatalf("stopError() = %v, want message containing %q", err, "reconcile") }
so the test uses a subtest pattern and validates the error message.
In `@internal/logger/logger.go`:
- Around line 221-236: The rotation code closes r.file before calling
rotateLogFiles/openLogFile which can leave r.file pointing at a closed
descriptor if rotation or reopening fails; change the logic to preserve the
active handle until a new file is successfully opened: keep the existing r.file
(and its descriptor) open, call rotateLogFiles using r.path, attempt to open a
new file via openLogFile into local variables (e.g., newFile, newSize), and only
if openLogFile succeeds assign r.file=newFile and r.size=newSize and then close
the previous file; if rotateLogFiles or openLogFile fail, leave r.file intact
and return the error so logging continues uninterrupted (referencing r.file,
rotateLogFiles, openLogFile, r.path, r.maxRetainedFiles, r.filePerm).
In `@internal/store/rundb/run_db_test.go`:
- Around line 457-485: The test is ineffective because db.now is called after
UpsertIntegrity has already taken integrityMu, so only one goroutine ever
signals nowEntered; fix this by moving the timestamp acquisition out of the
integrityMu critical section: in UpsertIntegrity, call db.now() (the now hook)
before acquiring integrityMu so multiple goroutines can enter and block on the
test's releaseNow/nowEntered signals; ensure any state that depends on the
timestamp is used after the lock is acquired and that you keep integrityMu
protection for shared state updates.
In `@internal/store/rundb/run_db.go`:
- Around line 163-167: closeRunSQLiteDatabase(ctx, db) can return an error even
though the underlying *sql.DB has been closed; to avoid leaving r.db pointing at
a closed/unusable handle, call closeRunSQLiteDatabase(ctx, db), capture its
error, then unconditionally set r.db = nil before returning (preserve and return
the error). Update the block surrounding r.db and closeRunSQLiteDatabase to
always clear the cached r.db regardless of the error path so retries don't hit
"database is closed" permanently.
---
Duplicate comments:
In `@internal/store/globaldb/global_db.go`:
- Around line 88-90: The return of closeGlobalSQLiteDatabase(ctx, g.db) should
be wrapped with context using fmt.Errorf so callers can attribute failures to
the GlobalDB layer; replace `if err := closeGlobalSQLiteDatabase(ctx, g.db); err
!= nil { return err }` with a wrapped return like `if err :=
closeGlobalSQLiteDatabase(ctx, g.db); err != nil { return fmt.Errorf("GlobalDB:
closeGlobalSQLiteDatabase failed: %w", err) }`, using fmt.Errorf and %w to
preserve the original error.
---
Nitpick comments:
In `@internal/api/core/handlers_contract_test.go`:
- Around line 59-61: The redundant loop-variable capture "tc := tc" inside the
test loops (around the t.Run calls that iterate over testCases in
handlers_contract_test.go) should be removed because Go 1.22+ makes loop
variables per-iteration; delete the duplicate assignments found in the first
t.Run loop and the second occurrence later in the file (the other t.Run block)
so the subtests use the loop variable directly (keep the t.Run signature and
test case usage unchanged).
In `@internal/daemon/boot_integration_test.go`:
- Around line 533-562: Both waitForLogContains and waitForStderrContains
currently busy-poll with time.Sleep; replace those polling loops with a
ticker/timer select loop like the other helpers: create a
time.NewTicker(50*time.Millisecond) and time.NewTimer(10*time.Second), defer
Stop on the ticker and Timer's drain/Stop, and in a select handle ticker.C to
re-check the condition (os.ReadFile + strings.Contains for waitForLogContains,
stderr != nil && strings.Contains(stderr.String(), ...) for
waitForStderrContains) and timer.C to fail the test with the same diagnostics;
keep references to the existing functions and the synchronizedBuffer type to
locate the code.
🪄 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: 57996ac2-e105-4e40-9ff4-365a7d723405
⛔ Files ignored due to path filters (32)
.compozy/tasks/daemon-improvs/reviews-001/_meta.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_001.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_002.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_003.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_004.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_005.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_006.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_007.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_008.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_009.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_010.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_011.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_012.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_013.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_014.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_015.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_016.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_017.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_018.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_019.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_020.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_021.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_022.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_023.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_024.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_025.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_026.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_027.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_028.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_029.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_030.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-001/issue_031.mdis excluded by!**/*.md
📒 Files selected for processing (31)
.gitignoreinternal/api/client/client_contract_test.gointernal/api/client/client_transport_test.gointernal/api/contract/compatibility.gointernal/api/contract/contract_integration_test.gointernal/api/contract/contract_test.gointernal/api/contract/sse.gointernal/api/contract/types.gointernal/api/core/handlers_contract_test.gointernal/api/httpapi/transport_integration_test.gointernal/api/testutil/sse.gointernal/cli/daemon_commands_test.gointernal/cli/daemon_exec_test_helpers_test.gointernal/cli/reviews_exec_daemon_additional_test.gointernal/core/run/journal/journal.gointernal/daemon/boot_integration_test.gointernal/daemon/boot_test.gointernal/daemon/run_integrity.gointernal/daemon/run_integrity_test.gointernal/daemon/shutdown.gointernal/daemon/watchers.gointernal/daemon/watchers_test.gointernal/logger/logger.gointernal/logger/logger_test.gointernal/store/globaldb/close_test.gointernal/store/globaldb/global_db.gointernal/store/rundb/close_test.gointernal/store/rundb/run_db.gointernal/store/rundb/run_db_test.gointernal/store/sqlite_test.gopkg/compozy/runs/run_test.go
✅ Files skipped from review due to trivial changes (10)
- .gitignore
- internal/daemon/boot_test.go
- internal/daemon/watchers.go
- internal/api/contract/compatibility.go
- internal/cli/reviews_exec_daemon_additional_test.go
- internal/store/globaldb/close_test.go
- internal/api/contract/contract_test.go
- internal/api/client/client_contract_test.go
- internal/api/contract/sse.go
- internal/daemon/run_integrity.go
🚧 Files skipped from review as they are similar to previous changes (4)
- internal/core/run/journal/journal.go
- internal/store/sqlite_test.go
- internal/logger/logger_test.go
- internal/cli/daemon_commands_test.go
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
internal/store/rundb/run_db_test.go (1)
449-490:⚠️ Potential issue | 🟠 MajorConcurrency test still doesn’t guarantee contention before release.
Line 477 only proves one
db.nowentry, while Line 489 releases the gate as soon as the second goroutine is scheduled (not necessarily blocked in the critical path). This can still pass with effectively sequential execution, so it does not reliably validate concurrent merge serialization.As per coding guidelines, "Prefer root-cause fixes in failing tests over workarounds that mask the real issue".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/store/rundb/run_db_test.go` around lines 449 - 490, The test TestRunDBUpsertIntegritySerializesConcurrentMerges can still release the gate before both goroutines are blocked in db.now; to fix this ensure both UpsertIntegrity callers have entered the db.now hook before closing releaseNow: increment the nowEntered channel capacity (or reuse it) and block until you receive two signals from nowEntered (one per goroutine) prior to closing releaseNow, and remove or adjust the secondStarted timing hack so the test waits on the two nowEntered receipts before releasing; reference TestRunDBUpsertIntegritySerializesConcurrentMerges, db.now, nowEntered, releaseNow and UpsertIntegrity to locate where to add the additional wait.
🧹 Nitpick comments (3)
internal/api/core/handlers_contract_test.go (2)
59-61: Consider using "Should..." prefix in subtest names for consistency.Per coding guidelines, test cases should use the
t.Run("Should...")pattern. For example:"Should return degraded status"instead of"degraded".Suggested naming
- { - name: "degraded", + { + name: "Should return 503 for degraded state",- { - name: "ready", + { + name: "Should return 200 for ready state",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/api/core/handlers_contract_test.go` around lines 59 - 61, Rename subtest name strings to follow the "Should..." convention: when calling t.Run(tc.name, ...) update the test case names in the testCases slice (referenced by variable testCases and used in t.Run) so each tc.name is prefixed with "Should " (e.g., change "degraded" to "Should return degraded status") ensuring all entries follow the consistent "Should..." pattern used across tests.
232-246: Consider adding a timeout to the HTTP client for test reliability.The
http.DefaultClienthas no timeout configured. If the server hangs before sending headers, the test will block indefinitely. WhileReadSSEFramesUntilhas a timeout for reading frames, the initial connection has no protection.Suggested fix
+ client := &http.Client{Timeout: 5 * time.Second} + request, err := http.NewRequestWithContext( context.Background(), http.MethodGet, server.URL+"/api/runs/"+runID+"/stream", http.NoBody, ) if err != nil { t.Fatalf("NewRequest() error = %v", err) } request.Header.Set(core.HeaderRequestID, "req-stream") - response, err := http.DefaultClient.Do(request) + response, err := client.Do(request)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/api/core/handlers_contract_test.go` around lines 232 - 246, The test uses http.NewRequestWithContext to open an SSE connection but then calls http.DefaultClient.Do which has no timeout; replace the use of http.DefaultClient with a dedicated http.Client that has a reasonable Timeout (e.g., a few seconds) and call client.Do(request) so the initial connection cannot hang indefinitely; you can alternatively wrap request creation in context.WithTimeout, but ensure the code around the SSE endpoint (the request to server.URL+"/api/runs/"+runID+"/stream") uses that timeout and continues to work with ReadSSEFramesUntil.internal/daemon/boot_integration_test.go (1)
327-353: Prefer a table-driven shape for the run-mode assertions.The foreground and detached branches share the same harness and differ only by mode plus stderr expectations. Folding them into a small table will keep future mode coverage in sync and matches the repo’s default testing pattern.
As per coding guidelines, "Use 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/daemon/boot_integration_test.go` around lines 327 - 353, Refactor TestManagedDaemonRunModesControlLogging into a table-driven test: replace the two explicit subtests with a slice of test cases (including fields like name, mode (use RunModeForeground / RunModeDetached), expectStderrMirror bool, and expectedModeString) and iterate with t.Run for each case; inside the loop call startManagedDaemonHelperProcess, waitForManagedDaemonReady, waitForLogContains and stopDaemonHelperProcess exactly as before, and assert stderr behavior using waitForStderrContains when expectStderrMirror is true or assert absence of the message in output.stderr when false, keeping references to the existing helpers (startManagedDaemonHelperProcess, waitForManagedDaemonReady, waitForLogContains, waitForStderrContains, stopDaemonHelperProcess) and preserving the existing log assertions for `"msg":"daemon started"` and the `"mode":"foreground"/"mode":"detached"` checks via the expectedModeString field.
🤖 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/daemon/boot_integration_test.go`:
- Around line 388-391: The cleanup and helper calls currently ignore errors
(e.g., the calls to cmd.Process.Kill(), cmd.Process.Wait(), ReadInfo, ReadFile)
using `_`, which hides failures; update each occurrence so you capture the error
(err := cmd.Process.Kill(), err := cmd.Process.Wait(), etc.) and handle it
explicitly — for unexpected failures call t.Fatalf or t.Errorf to surface the
root cause in CI, and for any genuinely expected/benign errors use an explicit
errors.Is(...) check with a short comment justifying why that specific error can
be ignored; apply this change to the cleanup block around
cmd.Process.Kill()/Wait and to the ReadInfo/ReadFile helper usages mentioned so
no error is discarded without handling or justification.
In `@internal/logger/logger_test.go`:
- Around line 12-177: Several top-level tests
(TestInstallDaemonLoggerForegroundMirrorsToStderrAndFile,
TestInstallDaemonLoggerDetachedWritesOnlyFile,
TestOpenRotatingFileRotatesAtConfiguredSize,
TestOpenRotatingFileKeepsWritingAfterRotationFailure,
TestValidateDaemonFilePathRejectsEmptyPath,
TestNormalizeFilePathCleansRelativeSegments) must be refactored into a
table-driven pattern using t.Run("Should...") subtests; convert these cases into
a single Test... function that iterates a slice of test cases and invokes t.Run
with descriptive "Should..." names for each scenario, keeping existing
assertions and reusing helpers like InstallDaemonLogger, openRotatingFile,
ValidateDaemonFilePath, and normalizeFilePath to locate logic. Also fix the
ignored error in TestOpenRotatingFileKeepsWritingAfterRotationFailure by
checking the return value from writer.Close() (the writer variable from
openRotatingFile) and handling/failing the test on error instead of using `_ =
writer.Close()`, or add an explicit comment justifying why it can be ignored if
you genuinely choose that route.
- Around line 118-120: In TestOpenRotatingFileKeepsWritingAfterRotationFailure
remove the redundant defer that calls _ = writer.Close() and do not ignore the
close error; delete that defer block and rely on the existing explicit
writer.Close() at the end of the test (or convert it to t.Cleanup) and
assert/check its error (e.g., require.NoError/if err != nil t.Fatalf) so the
writer variable is closed exactly once with proper error handling.
- Around line 134-136: The test currently only checks for a non-nil error from
writer.Write during a rotation failure; change that assertion to use errors.As
to confirm the returned (possibly wrapped) error is a file operation error such
as *os.PathError (i.e., replace the nil-check with an errors.As check against
os.PathError when calling writer.Write in the rotation test). Also make the
package export a sentinel error (e.g., var ErrInvalidPath = errors.New("invalid
daemon path")) used by ValidateDaemonFilePath, then update the test to assert
errors.Is(err, logger.ErrInvalidPath) (or alternatively compare the error
string) so the ValidateDaemonFilePath failure can be detected via errors.Is
rather than an unwrapped errors.New.
In `@internal/store/rundb/run_db_test.go`:
- Around line 365-447: The test
TestRunDBUpsertIntegrityIsStickyAndSurvivesReopen violates the repo policy
requiring subtests; wrap the test body in a t.Run("Should ...") subtest (e.g.
t.Run("Should persist integrity and sticky counts across reopen", func(t
*testing.T) { ... })) and similarly update the other new test referenced (lines
449-508) to use t.Run subtests (or table-driven t.Run cases if there are
multiple scenarios); keep the existing assertions and helper calls
(openTestRunDB, UpsertIntegrity, GetIntegrity, Open, Close) unchanged but move
them inside the t.Run closures and call t.Parallel() inside the subtest where
appropriate.
---
Duplicate comments:
In `@internal/store/rundb/run_db_test.go`:
- Around line 449-490: The test
TestRunDBUpsertIntegritySerializesConcurrentMerges can still release the gate
before both goroutines are blocked in db.now; to fix this ensure both
UpsertIntegrity callers have entered the db.now hook before closing releaseNow:
increment the nowEntered channel capacity (or reuse it) and block until you
receive two signals from nowEntered (one per goroutine) prior to closing
releaseNow, and remove or adjust the secondStarted timing hack so the test waits
on the two nowEntered receipts before releasing; reference
TestRunDBUpsertIntegritySerializesConcurrentMerges, db.now, nowEntered,
releaseNow and UpsertIntegrity to locate where to add the additional wait.
---
Nitpick comments:
In `@internal/api/core/handlers_contract_test.go`:
- Around line 59-61: Rename subtest name strings to follow the "Should..."
convention: when calling t.Run(tc.name, ...) update the test case names in the
testCases slice (referenced by variable testCases and used in t.Run) so each
tc.name is prefixed with "Should " (e.g., change "degraded" to "Should return
degraded status") ensuring all entries follow the consistent "Should..." pattern
used across tests.
- Around line 232-246: The test uses http.NewRequestWithContext to open an SSE
connection but then calls http.DefaultClient.Do which has no timeout; replace
the use of http.DefaultClient with a dedicated http.Client that has a reasonable
Timeout (e.g., a few seconds) and call client.Do(request) so the initial
connection cannot hang indefinitely; you can alternatively wrap request creation
in context.WithTimeout, but ensure the code around the SSE endpoint (the request
to server.URL+"/api/runs/"+runID+"/stream") uses that timeout and continues to
work with ReadSSEFramesUntil.
In `@internal/daemon/boot_integration_test.go`:
- Around line 327-353: Refactor TestManagedDaemonRunModesControlLogging into a
table-driven test: replace the two explicit subtests with a slice of test cases
(including fields like name, mode (use RunModeForeground / RunModeDetached),
expectStderrMirror bool, and expectedModeString) and iterate with t.Run for each
case; inside the loop call startManagedDaemonHelperProcess,
waitForManagedDaemonReady, waitForLogContains and stopDaemonHelperProcess
exactly as before, and assert stderr behavior using waitForStderrContains when
expectStderrMirror is true or assert absence of the message in output.stderr
when false, keeping references to the existing helpers
(startManagedDaemonHelperProcess, waitForManagedDaemonReady, waitForLogContains,
waitForStderrContains, stopDaemonHelperProcess) and preserving the existing log
assertions for `"msg":"daemon started"` and the
`"mode":"foreground"/"mode":"detached"` checks via the expectedModeString field.
🪄 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: 468c31dc-d387-44d8-9457-2137e8ba23fd
⛔ Files ignored due to path filters (8)
.compozy/tasks/daemon-improvs/reviews-002/_meta.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-002/issue_001.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-002/issue_002.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-002/issue_003.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-002/issue_004.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-002/issue_005.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-002/issue_006.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-002/issue_007.mdis excluded by!**/*.md
📒 Files selected for processing (7)
internal/api/core/handlers_contract_test.gointernal/daemon/boot_integration_test.gointernal/logger/logger.gointernal/logger/logger_test.gointernal/store/rundb/close_test.gointernal/store/rundb/run_db.gointernal/store/rundb/run_db_test.go
✅ Files skipped from review due to trivial changes (1)
- internal/logger/logger.go
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/store/rundb/run_db.go
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
internal/api/core/handlers_contract_test.go (1)
191-192: Missingt.Parallel()call.This test should include
t.Parallel()for consistency with other tests in this file.Suggested fix
func TestStreamRunEmitsCanonicalEventHeartbeatAndOverflowPayloads(t *testing.T) { + t.Parallel() gin.SetMode(gin.TestMode)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/api/core/handlers_contract_test.go` around lines 191 - 192, The test function TestStreamRunEmitsCanonicalEventHeartbeatAndOverflowPayloads is missing a t.Parallel() call; open the TestStreamRunEmitsCanonicalEventHeartbeatAndOverflowPayloads function and add a t.Parallel() immediately after the function begins (right after gin.SetMode(gin.TestMode)) so the test runs in parallel like the others.
🤖 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/api/core/handlers_contract_test.go`:
- Around line 200-214: The goroutine in the test blocks on `<-sendOverflow` and
can leak if the test fails before a heartbeat; update the goroutine to respect a
cancellation context or test “done” signal: accept a ctx (or use t.Cleanup to
create one) and replace the blocking `<-sendOverflow` with a select that listens
for either `case <-sendOverflow:` or `case <-ctx.Done():` so the goroutine can
exit on test cancellation; ensure you still send the overflow event to
`stream.events` (RunStreamItem with Overflow) and close
`stream.events`/`stream.errors` on shutdown.
In `@internal/store/globaldb/registry_test.go`:
- Around line 88-132: Wrap the test body of
TestGetByPathPrefersResolvedCanonicalWorkspaceRow in a subtest using t.Run with
a "Should..." description (e.g. t.Run("Should prefer resolved canonical
workspace row", func(t *testing.T) { ... })) and move the existing t.Parallel()
and all setup/assertions inside that subtest; do the same for the other added
test case referenced at lines 235-267 so both follow the repository's
t.Run("Should...") subtest pattern while keeping function name and helper calls
(db.Register, db.Get, db.now, etc.) unchanged.
- Around line 238-266: The test fixture uses root := string(filepath.Separator)
which yields "\" on Windows and produces non-absolute paths; update the fixture
so the constructed paths are absolute on Windows by prefixing a drive/volume
(use the same drive as the test input). Specifically, when building
root/usersDir/homeDir etc. for canonicalizeExistingPathCaseWith, set root to the
volume + separator (e.g., filepath.VolumeName(input) +
string(filepath.Separator)) or otherwise ensure input is absolute via
filepath.IsAbs/input->filepath.Abs before calling
canonicalizeExistingPathCaseWith so the function treats the path as absolute and
the test exercises the case-normalization logic.
In `@internal/store/globaldb/registry.go`:
- Around line 907-911: The current call to canonicalizeExistingPathCase in
registry.go should be made best-effort rather than a hard failure: if
canonicalizeExistingPathCase(resolvedPath) returns an error, do not return that
error — instead fall back to returning filepath.Clean(resolvedPath) (optionally
logging/debugging the canonicalization error) so lack of read permission on
parent directories doesn't block normalization; apply the same change for the
other instance referenced (the similar block around lines 946-949) so both uses
of canonicalizeExistingPathCase degrade gracefully.
- Around line 922-925: The validation currently runs filepath.Clean before
checking for empty input, letting whitespace-only strings become "."; change the
order to trim whitespace first and validate that result is non-empty (use
strings.TrimSpace on path and check trimmed == ""), then call filepath.Clean on
the trimmed value and assign to cleanPath; update the error path that returns
errors.New("globaldb: workspace path is required") to trigger on the trimmed
empty string.
---
Nitpick comments:
In `@internal/api/core/handlers_contract_test.go`:
- Around line 191-192: The test function
TestStreamRunEmitsCanonicalEventHeartbeatAndOverflowPayloads is missing a
t.Parallel() call; open the
TestStreamRunEmitsCanonicalEventHeartbeatAndOverflowPayloads function and add a
t.Parallel() immediately after the function begins (right after
gin.SetMode(gin.TestMode)) so the test runs in parallel like the others.
🪄 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: 0f005eed-ba6f-40dd-ae4a-031b5603653a
⛔ Files ignored due to path filters (9)
.compozy/tasks/daemon-improvs/reviews-003/_meta.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-003/issue_001.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-003/issue_002.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-003/issue_003.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-003/issue_004.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-003/issue_005.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-003/issue_006.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-003/issue_007.mdis excluded by!**/*.md.compozy/tasks/daemon-improvs/reviews-003/issue_008.mdis excluded by!**/*.md
📒 Files selected for processing (7)
internal/api/core/handlers_contract_test.gointernal/cli/commands_simple.gointernal/cli/daemon_commands_test.gointernal/daemon/boot_integration_test.gointernal/logger/logger_test.gointernal/store/globaldb/registry.gointernal/store/globaldb/registry_test.go
✅ Files skipped from review due to trivial changes (1)
- internal/logger/logger_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/daemon/boot_integration_test.go
Summary by CodeRabbit
New Features
Improvements
Bug Fixes