Skip to content

feat: worktree-backed parallel multi-run for tasks run --multiple - #200

Merged
pedronauck merged 16 commits into
compozy:mainfrom
MatheusBBarni:feat/multi-task-run
Jun 16, 2026
Merged

pedronauck merged 16 commits into
compozy:mainfrom
MatheusBBarni:feat/multi-task-run

Conversation

@MatheusBBarni

@MatheusBBarni MatheusBBarni commented Jun 15, 2026 •

Copy link
Copy Markdown
Contributor

What this does

compozy tasks run --multiple alpha,beta --parallel now actually runs the tasks at the same time. Until now parallel was an accepted value that quietly fell back to sequential (enqueued) execution, so the flag was a promise the daemon never kept. This change makes it real: each child task runs in its own git worktree, several at once, bounded by a configurable limit.

enqueued stays the default. You only get parallelism when you ask for it, with --parallel or [tasks.run] run_multiple_mode = "parallel".

Why

Running independent task batches one after another wastes time when they don't depend on each other. The point of this work is wall-clock time for solo developers running independent tasks, without losing the per-task status and manual-review handoff the existing multi-run flow already gives you. Each child keeps its worktree after the run so you can read the diffs yourself. Nothing is auto-merged or auto-pushed.

The full background lives in the PRD, tech spec, and ADRs under .compozy/tasks/multi-task-run/. ADR-005 (worktree-backed parallel as opt-in) and ADR-008 (configurable limit) are the load-bearing ones for this PR.

What you get

  • --parallel and --parallel-limit N on tasks run --multiple.
  • A config key [tasks.run] run_multiple_parallel_limit (default 2, must be positive).
  • Limit precedence: the --parallel-limit flag wins, then the config key, then the default of 2. Mode precedence: --parallel, then run_multiple_mode, then enqueued.
  • Each child runs in a detached worktree under ~/.compozy/state/worktrees/..., kept after the run.
  • Fail-late behavior: if one child fails, the rest keep going. The parent ends failed and names which slugs failed, but only after every child has settled. It ends completed only when all children complete.
  • Cancelling the parent cancels the running children and marks the not-yet-started ones canceled.
  • The TUI shows each child's worktree path, branch, and status. The CLI stream tags child events with worktree=<path> and prints a final handoff block listing every slug, its status, run id, and worktree.

One thing the README is explicit about: worktrees isolate git working state and nothing else. They do not isolate ports, credentials, caches, provider rate limits, or databases, and they will not prevent merge conflicts. Don't expect full sandboxing.

How it works

The daemon already owned a sequential multi-run coordinator (task_multi). This PR turns it into one mode-aware scheduler that dispatches to either the existing enqueued path (unchanged) or the new parallel path. Both share the same event, snapshot, and cancellation helpers, so there aren't two coordinators drifting apart.

The parallel path does four things:

  1. Resolves the parent's branch and HEAD once, and rejects a detached checkout up front. You can't base worktrees on an ambiguous HEAD.
  2. Allocates a detached worktree per child at a deterministic path, registers it as its own workspace, and remaps the child runtime onto it.
  3. Fans children out with a counting semaphore sized to the resolved limit. Every worker goroutine is owned by the parent and joined before the parent reports a terminal status, so nothing is left running in the background.
  4. Aggregates after all workers finish: success only if every child succeeded, otherwise a single error naming the failed slugs.

Worktree metadata (path, base branch, base commit, status) travels on the existing task.multi.* item events and gets rebuilt into the snapshot, so attaching late or recovering after a crash still shows where each child ran.

The SQLite change worth a close look

This is the part to review carefully. The daemon's global catalog is SQLite in WAL mode (modernc driver). Running children at the same time surfaced a real bug: two children starting together both open a deferred transaction, both read, then both try to upgrade to a write. SQLite returns SQLITE_BUSY immediately for that read-to-write upgrade, and busy_timeout does not retry it. Under the race detector it reproduced every time; without it the timing usually hid the failure.

The fix is _txlock=immediate on the DSN, so write transactions start with BEGIN IMMEDIATE and take the write lock up front. Concurrent writers then wait on busy_timeout (5s) instead of deadlocking. It's a one-line DSN change, but it touches every database opened through store.OpenSQLiteDatabase, not just multi-run, so I ran it against the whole suite (3669 tests, race detector on) with no regressions. I also checked the modernc driver source to confirm it honors _txlock before depending on it.

I looked at serializing child startup instead, but that only moves the problem: children also write concurrently during execution and teardown. Fixing the lock mode at the source is the honest fix.

Tests

  • Config parsing, merge precedence, defaults, and zero/negative rejection for the new limit.
  • Contract and OpenAPI round-trips for the request and snapshot fields, plus old-payload compatibility for the event payloads.
  • Daemon scheduler tests for bounded concurrency (limit 2 with 3 children never starts a third before a slot frees, verified with a channel handshake rather than sleeps), fail-late (a failing sibling doesn't stop the others and the parent names the failed slug), cancellation (running child canceled, not-started item canceled, repeated cancel stays consistent), and the resolved limit landing on the started event.
  • TUI model tests for the worktree status line, and CLI tests for the handoff block, including that an aggregate failure still exits non-zero with the handoff printed.
  • Two end-to-end tests driving the real CLI against an in-process daemon with actual git worktrees.
  • A doc-drift guard so the README flags and the event reference stay in sync with the code.

It all passes under make verify (fmt, lint at zero issues, race tests, frontend checks, and the Playwright smoke suite).

Out of scope

No auto-merge, no auto-push, no conflict detection, no dependency-aware scheduling, no automatic worktree cleanup. Those are later phases in the PRD.

One known rough edge, recorded in the workflow memory: a --dry-run parallel run still creates real worktrees on disk, because only child execution is short-circuited. A follow-up could plan worktree paths without actually running git worktree add.

Commits

Ten task commits plus one for the planning docs, each gated by a green make verify:

Commit What it adds
task_01 configurable parallel limit in workspace config
task_02 multi-run API and client contracts
task_03 --parallel / --parallel-limit CLI controls and request wiring
task_04 worktree metadata on events and snapshot
task_05 git worktree allocation and path planning
task_06 mode-aware task_multi scheduler refactor
task_07 register and remap parallel children to worktrees
task_08 bounded fanout, fail-late aggregation, and the SQLite fix
task_09 worktree handoff rendering in the TUI and CLI
task_10 documentation and end-to-end coverage

Trying it

compozy tasks run --multiple alpha,beta,gamma --parallel --parallel-limit 3

Run it from a git workspace where each slug's task files are committed. The worktrees are detached checkouts of HEAD, so the task directories have to exist in the commit.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added parallel execution for compozy tasks run --multiple via --parallel, using git worktree isolation.
    • Added --parallel-limit (default: 2) and run_multiple_parallel_limit config to cap concurrency; multi-run requests/events/UI now include parallel_limit and worktree metadata.
  • Improvements
    • Parallel mode now consistently forwards and enforces the effective parallel_limit, including correct request encoding and snapshot reconstruction.
    • Streamed output and UI tabs now display worktree handoff details with dash placeholders when metadata is missing.
  • Documentation
    • Updated CLI help text and OpenAPI/event contracts to reflect the new fields.

MatheusBBarni and others added 11 commits June 14, 2026 18:22
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9a13c7d5-67d9-4a8c-8949-d81ce1166cc8

📥 Commits

Reviewing files that changed from the base of the PR and between 9dd9cfa and 990ed2f.

📒 Files selected for processing (1)
  • internal/core/run/ui/multi_remote_test.go

Walkthrough

Adds --parallel and --parallel-limit to tasks run --multiple, implementing a semaphore-bounded parallel scheduler in the daemon that allocates isolated git worktrees per child run. Extends the event payload, API contract, config, and snapshot types with worktree and parallel-limit metadata. Updates CLI flag wiring, mode resolution, streaming terminal handoff, and both TUI and non-UI output to display per-child worktree paths.

Changes

Parallel multi-run with git worktree isolation

Layer / File(s) Summary
Config types, event payload schema, and home layout
internal/core/workspace/config_types.go, internal/core/workspace/config_validate.go, internal/core/workspace/config_merge.go, internal/config/home.go, internal/version/version.go, pkg/compozy/events/kinds/task.go, internal/core/workspace/config_test.go, internal/config/home_test.go, pkg/compozy/events/kinds/payload_compat_test.go, pkg/compozy/events/docs_test.go, pkg/compozy/events/kinds/docs_test.go
Adds DefaultRunMultipleParallelLimit = 2, RunMultipleParallelLimit optional field on TaskRunConfig with EffectiveRunMultipleParallelLimit(), parallel-limit validation rejecting non-positive values. Introduces HomePaths.WorktreesDir under state dir. Adds UnstampedCommit sentinel constant. Extends TaskRunMultiplePayload with optional ParallelLimit, WorktreePath, BaseBranch, BaseCommit, and WorktreeStatus fields. Full config parsing, merging, validation, and payload compatibility tests.
API contract types, client forwarding, and OpenAPI
internal/api/contract/types.go, internal/api/client/client.go, internal/api/core/handlers.go, internal/api/httpapi/openapi_contract_test.go, internal/api/client/client_contract_test.go, internal/api/contract/contract_test.go, internal/api/core/handlers_smoke_test.go
Extends TaskRunMultipleRequest with optional parallel_limit field; adds worktree fields to TaskRunMultipleItem. Client StartTaskRunMultiple forwards ParallelLimit in outgoing request. HTTP handler StartTaskRunMultiple includes ParallelLimit when constructing internal request. OpenAPI and contract tests validate new request/response fields and JSON serialization/deserialization of worktree metadata and parallel limit.
Git worktree allocator
internal/daemon/task_multi_worktree.go, internal/daemon/task_multi_worktree_test.go
New taskMultiWorktreeAllocator module resolves parent branch/commit once (rejecting detached HEAD), plans deterministic per-child paths using sanitized slug segments and SHA-256-based workspace hash, and runs git worktree add --detach to create isolated worktrees marked with preserved status. Includes unit tests for path planning, segment sanitization, base resolution, allocation, collision detection, and real-repo integration tests for git operations.
Daemon parallel scheduler and run manager wiring
internal/daemon/task_multi.go, internal/daemon/run_manager.go, internal/daemon/host.go, internal/store/sqlite.go, internal/daemon/run_manager_test.go
RunManager initialized with WorktreesRoot and worktreeAllocator. runTaskMultiCoordinator dispatches to enqueued or parallel execution. runTaskMultiParallelQueue launches child workers with semaphore concurrency control, records failures without canceling healthy siblings (fail-late), and handles context cancellation. startTaskMultiWorktreeChild allocates worktrees, emits metadata before child-start, registers worktree workspace, and remaps child runtime config. emitTaskMultiEvent serializes with emitMu lock. applyTaskMultiItemMetadata merges worktree fields for backward-compatible snapshot reconstruction. SQLite DSN includes _txlock=immediate pragma for concurrent writer safety.
Comprehensive daemon parallel and worktree tests
internal/daemon/task_multi_test.go
Extensive test coverage for parallel execution semantics: runtime remapping for worktrees, base resolution and collision detection, parallel registration under distinct worktree workspaces, snapshot reconstruction with worktree metadata from events, parallel limit resolution and aggregation, concurrency bounds enforcement, fail-late and cancellation behavior, resolved-limit emission on started event, item-queued multiplicity, and OpenAPI contract validation for status enums.
CLI flags, mode resolution, and streaming handoff
internal/cli/daemon_commands.go, internal/cli/state.go, internal/cli/daemon_commands_test.go, internal/cli/root_test.go
Adds --parallel (bool) and --parallel-limit (int, default 2) flags to tasks run --multiple. runtimeConfig gains parallel and parallelLimit fields. Pre-run validator rejectMultipleOnlyParallelFlags rejects these flags unless --multiple is set. Refactored resolveTaskRunMultipleMode derives mode from --parallel flag (falling back to enqueued when false), then uses config. resolveTaskRunMultipleParallelLimit resolves limit with --parallel-limit override and config precedence. ParallelLimit conditionally included in daemon request only for parallel mode. streamTaskRunMultipleToTerminal watches parent queue completion and performs per-child handoff. Version check uses UnstampedCommit sentinel. Tests validate flag precedence, validation logic, and mode resolution. Golden help text documents new flags and defaults.
CLI and UI worktree handoff rendering
internal/cli/run_observe.go, internal/core/run/ui/multi_remote.go, internal/cli/daemon_commands_test.go, internal/core/run/ui/multi_remote_test.go
renderObservedTaskMultiItem uses unified segments-based output joining label, status, run ID, worktree path, and error. Handoff helpers (writeTaskRunMultipleHandoff, renderTaskRunMultipleHandoff, formatTaskRunMultipleHandoff) fetch snapshot and render per-child lines with slug, status, run ID, worktree path, optional branch, and error text; handoffValueOrDash renders "-" for missing metadata. TUI multiRunTab stores worktree metadata fields; newMultiRunTab initializes from snapshot item; applyTaskMultiWorktreeMetadata merges non-empty worktree fields on item updates. renderActiveTabWorktreeLine adds an owned row displaying active tab worktree summary via formatMultiRunWorktreeSummary and multiRunWorktreeLabel. Comprehensive tests verify metadata rendering, preservation through events, and fallback behavior.
CLI integration and E2E parallel tests
internal/cli/root_command_execution_test.go, internal/cli/tasks_run_parallel_e2e_test.go, internal/cli/testdata/tasks_run_help.golden
End-to-end tests prepare isolated git workspace, run CLI with --parallel and --parallel-limit, assert per-child snapshot worktree paths and preservation status. Streaming tests verify handoff output and aggregate failure reporting. Flag validation tests reject --parallel/--parallel-limit without --multiple. In-process test asserts failure when workspace lacks git root. README/CLI parity test extracts documented long options and validates CLI help. Golden help text updated with new flag usage, defaults, and worktree isolation semantics.

Sequence Diagram(s)

sequenceDiagram
  participant User as CLI User
  participant CLICmd as tasks run --multiple --parallel
  participant Daemon as Daemon API
  participant Scheduler as runTaskMultiParallelQueue
  participant Allocator as taskMultiWorktreeAllocator
  participant Git as git CLI

  User->>CLICmd: tasks run --multiple alpha,beta --parallel --parallel-limit 2
  CLICmd->>CLICmd: resolveTaskRunMultipleMode → parallel
  CLICmd->>CLICmd: resolveTaskRunMultipleParallelLimit → 2
  CLICmd->>Daemon: POST /api/task-runs/multiple {mode=parallel, parallel_limit=2}
  Daemon->>Scheduler: runTaskMultiParallelQueue (semaphore=2)
  Scheduler->>Allocator: ResolveBase(workspaceRoot)
  Allocator->>Git: git rev-parse HEAD / --abbrev-ref HEAD
  Git-->>Allocator: branch=main, commit=abc123
  loop per child (alpha, beta concurrently)
    Scheduler->>Allocator: Allocate(slug, index, parentRunID)
    Allocator->>Git: git worktree add --detach path abc123
    Git-->>Allocator: worktree created
    Scheduler->>Daemon: emit worktree_metadata event
    Scheduler->>Daemon: StartRun(child, worktreeWorkspace)
    Daemon-->>Scheduler: child run result
  end
  Scheduler->>Daemon: aggregateTaskMultiParallelResult
  Daemon-->>CLICmd: snapshot with per-child WorktreePath
  CLICmd->>User: handoff: alpha | completed | run=… | worktree=…
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • compozy/compozy#162: Introduced the initial task run --multiple flow with enqueued sequential execution; this PR extends the same StartTaskRunMultiple client/handler/request contract with ParallelLimit and worktree metadata forwarding to enable parallel child execution.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0810b7f940

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// stay short enough for local daemon/socket path constraints.
taskMultiWorktreeHashLen = 12
// taskMultiWorktreeParentShortLen bounds the parent-run path segment.
taskMultiWorktreeParentShortLen = 12

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep parent worktree paths unique across runs

The 12-character cap collapses every default multi-run parent ID from BuildRunID (for task-multi) to the same sanitized prefix, e.g. tasks-task-multi-... becomes tasks-task-m. Since the path is <workspace-hash>/<parent-short>/<NN-slug> and allocated worktrees are preserved while ensureTaskMultiWorktreeTargetFree rejects existing targets, a second parallel run in the same workspace with the same child index/slug will reuse the previous path and fail before launching that child. Use entropy from the full parent run ID (for example a hash or suffix) rather than only the shared leading prefix.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Jun 15, 2026 •

Copy link
Copy Markdown

Greptile Summary

This PR delivers real parallel execution for compozy tasks run --multiple --parallel, replacing the previous stub that silently fell back to sequential mode. Each child task now runs in an isolated git worktree under ~/.compozy/state/worktrees/, bounded by a configurable semaphore. The SQLite DSN gains _txlock=immediate to prevent read-to-write upgrade deadlocks that manifest when parallel children open write transactions concurrently.

  • Parallel scheduler (task_multi.go, task_multi_worktree.go): fail-late fanout with sync.WaitGroup + counting semaphore, emitMu serializing journal writes, and deterministic worktree path planning using workspace+parent-run hash namespacing.
  • CLI (daemon_commands.go): --parallel / --parallel-limit flags with explicit rejection when --parallel-limit is used outside parallel mode, plus a post-run handoff block listing each child's worktree path, status, and run id.
  • SQLite fix (store/sqlite.go): _txlock=immediate added globally so all write transactions acquire the WAL write lock upfront, eliminating the SQLITE_BUSY race that busy_timeout cannot retry on deferred-to-immediate lock upgrades.

Confidence Score: 5/5

Safe to merge. The parallel execution path is fail-late and fully owned by a WaitGroup; no goroutines are left running after the parent settles. The SQLite _txlock=immediate change is global but well-reasoned, tested against the full suite with the race detector, and correctly targets the upgrade-deadlock root cause rather than papering over it.

All three issues flagged in previous review rounds have been addressed: --parallel-limit is now rejected (not silently discarded) when mode resolves to enqueued; the double ItemQueued per child has been eliminated by skipping the upfront bulk emit in parallel mode; and the Ctrl+C handoff now checks ctx.Err() before fetching the snapshot. The new parallel scheduler correctly avoids sharing the childErrs slice between goroutines by index-partitioning it, and the emitMu mutex properly serializes journal writes without introducing lock ordering risks. The worktree path planner uses a SHA-256 digest suffix to disambiguate parent runs that share a truncated prefix, preventing 'target already exists' collisions.

No files require special attention. internal/store/sqlite.go carries a global DSN change (_txlock=immediate) worth keeping in view for any future SQLite-touching PRs, but the change itself is correct and the prior review note about the duplicate busy_timeout pragma remains the only outstanding minor observation there.

Important Files Changed

Filename Overview
internal/daemon/task_multi.go Mode-aware scheduler refactor: adds fail-late parallel path with semaphore-bounded fanout, emitMu for journal serialization, and shared queue lifecycle helpers used by both enqueued and parallel branches.
internal/daemon/task_multi_worktree.go New worktree allocator: deterministic path planning with SHA-256-keyed workspace and parent-run segments, detached-HEAD rejection, and git-command abstraction for testability. V1 intentionally never deletes worktrees.
internal/store/sqlite.go Adds _txlock=immediate to all SQLite DSNs; global change that prevents the read-to-write upgrade deadlock exposed by concurrent parallel-mode child writers.
internal/cli/daemon_commands.go Adds --parallel/--parallel-limit flags, resolves mode and limit with precedence rules, rejects --parallel-limit outside parallel mode, and calls streamTaskRunMultipleToTerminal with context-aware handoff skipping on Ctrl+C.
internal/cli/run_observe.go Adds writeTaskRunMultipleHandoff and related formatters; stream output tags child events with worktree=<path> and renders per-child handoff block with - placeholders for backward compat.
internal/core/run/ui/multi_remote.go TUI gains a worktree status line below the tab strip (height bumped to 3), additive applyTaskMultiWorktreeMetadata mirroring the snapshot builder, and em-dash fallbacks for missing fields.
internal/daemon/run_manager.go Adds WorktreesRoot config field, wires newTaskMultiWorktreeAllocator into the RunManager, and adds emitMu sync.Mutex to activeRun for parallel event serialization.
internal/core/workspace/config_types.go Adds RunMultipleParallelLimit *int TOML field, DefaultRunMultipleParallelLimit = 2, and EffectiveRunMultipleParallelLimit() accessor with safe nil handling.
internal/daemon/task_multi_test.go Comprehensive parallel-mode daemon tests: bounded concurrency (channel handshake, not sleeps), fail-late, cancellation, resolved-limit event, and single-ItemQueued-per-child invariant.
internal/daemon/task_multi_worktree_test.go New 598-line test suite covering path determinism, workspace isolation, truncated-prefix collision resistance, slug sanitization, and the full allocator round-trip using real git.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CLI
    participant Daemon
    participant Coordinator
    participant Semaphore
    participant Worker1
    participant Worker2
    participant Git

    CLI->>Daemon: "StartTaskRunMultiple(mode=parallel, limit=2)"
    Daemon->>Coordinator: runTaskMultiCoordinator
    Coordinator->>Coordinator: emitQueueStarted (seeds Slugs)
    Coordinator->>Coordinator: resolveTaskMultiParallelBase
    Coordinator->>Git: rev-parse --abbrev-ref HEAD
    Git-->>Coordinator: main (branch + commit)

    par Child alpha
        Coordinator->>Semaphore: acquire slot
        Semaphore-->>Coordinator: ok
        Coordinator->>Worker1: go runTaskMultiParallelChild(alpha)
        Worker1->>Git: worktree add --detach .../00-alpha HEAD
        Worker1->>Coordinator: emitItemQueued(alpha, worktreePath)
        Worker1->>Daemon: "startRun(alpha, workspace=worktree)"
        Worker1->>Worker1: waitForTaskMultiChild
    and Child beta
        Coordinator->>Semaphore: acquire slot
        Semaphore-->>Coordinator: ok
        Coordinator->>Worker2: go runTaskMultiParallelChild(beta)
        Worker2->>Git: worktree add --detach .../01-beta HEAD
        Worker2->>Coordinator: emitItemQueued(beta, worktreePath)
        Worker2->>Daemon: "startRun(beta, workspace=worktree)"
        Worker2->>Worker2: waitForTaskMultiChild
    end

    Worker1-->>Semaphore: release slot
    Worker1-->>Coordinator: "childErrs[0]=nil"
    Worker2-->>Semaphore: release slot
    Worker2-->>Coordinator: "childErrs[1]=nil"

    Coordinator->>Coordinator: wg.Wait() + finalizeTaskMultiParallel
    Coordinator->>Coordinator: "aggregateResult -> nil"
    Coordinator->>Coordinator: emitQueueCompleted
    Daemon-->>CLI: terminal event + handoff block
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant CLI
    participant Daemon
    participant Coordinator
    participant Semaphore
    participant Worker1
    participant Worker2
    participant Git

    CLI->>Daemon: "StartTaskRunMultiple(mode=parallel, limit=2)"
    Daemon->>Coordinator: runTaskMultiCoordinator
    Coordinator->>Coordinator: emitQueueStarted (seeds Slugs)
    Coordinator->>Coordinator: resolveTaskMultiParallelBase
    Coordinator->>Git: rev-parse --abbrev-ref HEAD
    Git-->>Coordinator: main (branch + commit)

    par Child alpha
        Coordinator->>Semaphore: acquire slot
        Semaphore-->>Coordinator: ok
        Coordinator->>Worker1: go runTaskMultiParallelChild(alpha)
        Worker1->>Git: worktree add --detach .../00-alpha HEAD
        Worker1->>Coordinator: emitItemQueued(alpha, worktreePath)
        Worker1->>Daemon: "startRun(alpha, workspace=worktree)"
        Worker1->>Worker1: waitForTaskMultiChild
    and Child beta
        Coordinator->>Semaphore: acquire slot
        Semaphore-->>Coordinator: ok
        Coordinator->>Worker2: go runTaskMultiParallelChild(beta)
        Worker2->>Git: worktree add --detach .../01-beta HEAD
        Worker2->>Coordinator: emitItemQueued(beta, worktreePath)
        Worker2->>Daemon: "startRun(beta, workspace=worktree)"
        Worker2->>Worker2: waitForTaskMultiChild
    end

    Worker1-->>Semaphore: release slot
    Worker1-->>Coordinator: "childErrs[0]=nil"
    Worker2-->>Semaphore: release slot
    Worker2-->>Coordinator: "childErrs[1]=nil"

    Coordinator->>Coordinator: wg.Wait() + finalizeTaskMultiParallel
    Coordinator->>Coordinator: "aggregateResult -> nil"
    Coordinator->>Coordinator: emitQueueCompleted
    Daemon-->>CLI: terminal event + handoff block
Loading

Reviews (5): Last reviewed commit: "test: wrap multi-run UI tests in t.Run("..." | Re-trigger Greptile

Comment thread internal/cli/daemon_commands.go
Comment thread internal/daemon/task_multi.go

@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: 8

🧹 Nitpick comments (1)
internal/api/httpapi/openapi_contract_test.go (1)

186-193: ⚡ Quick win

Use t.Run("Should...") per schema field assertion.

These loops currently collapse multiple cases into one failure path. Wrapping each field check in a subtest will align with the test guidelines and improve failure granularity.

Suggested refactor
- for _, field := range []string{
+ for _, field := range []string{
    "slugs",
    "mode",
    "parallel_limit",
    "presentation_mode",
    "runtime_overrides",
    "workspace",
  } {
-   if _, ok := taskRunMultipleProperties[field]; !ok {
-     t.Fatalf("TaskRunMultipleRequest must expose %s", field)
-   }
+   field := field
+   t.Run("Should expose TaskRunMultipleRequest field "+field, func(t *testing.T) {
+     t.Parallel()
+     if _, ok := taskRunMultipleProperties[field]; !ok {
+       t.Fatalf("TaskRunMultipleRequest must expose %s", field)
+     }
+   })
  }
...
- for _, field := range []string{
+ for _, field := range []string{
    "slug",
    "status",
    "run_id",
    "error_text",
    "worktree_path",
    "base_branch",
    "base_commit",
    "worktree_status",
  } {
-   if _, ok := multiRunItemProperties[field]; !ok {
-     t.Fatalf("TaskRunMultipleItem must expose %s", field)
-   }
+   field := field
+   t.Run("Should expose TaskRunMultipleItem field "+field, func(t *testing.T) {
+     t.Parallel()
+     if _, ok := multiRunItemProperties[field]; !ok {
+       t.Fatalf("TaskRunMultipleItem must expose %s", field)
+     }
+   })
  }

As per coding guidelines, **/*_test.go requires MUST use t.Run("Should...") pattern for ALL test cases and Use table-driven tests with subtests (t.Run) as the default pattern.

Also applies to: 207-219

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/httpapi/openapi_contract_test.go` around lines 186 - 193, The
field validation loop that iterates through the array of fields (slugs, mode,
parallel_limit, presentation_mode, runtime_overrides, workspace) currently
checks all fields within a single test path, causing the entire test to fail on
the first field error. Refactor this by wrapping each field validation check
inside a t.Run() subtest with a descriptive name like t.Run(fmt.Sprintf("Should
validate field: %s", field), func(t *testing.T) { ... }) to align with the test
guidelines and provide granular failure reporting. Apply this same pattern to
all similar field validation loops in the test function where multiple fields
are checked in sequence without individual subtests.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/api/contract/contract_test.go`:
- Around line 516-518: The test case naming in the table-driven test at the
t.Run call on line 516 does not follow the required "Should..." convention.
Update all test case names in the test table (referenced as tc.name in the t.Run
call) to follow the "Should..." naming pattern as mandated by the coding
guidelines, ensuring each test case name clearly describes what behavior it
should verify using this standard convention.

In `@internal/api/core/handlers_smoke_test.go`:
- Around line 731-775: The test function
TestStartTaskRunMultipleForwardsParallelLimitAndMode needs to follow the
repository's test convention by wrapping its logic in a t.Run() call. Move all
the test setup, HTTP request handling, and assertions from the main function
body into a t.Run("Should forward parallel limit and mode correctly") subtest,
keeping all existing test logic intact while conforming to the required naming
pattern.

In `@internal/cli/tasks_run_parallel_e2e_test.go`:
- Around line 99-127: The test function
TestTasksRunMultipleParallelEndToEndReportsWorktreePaths does not follow the
required t.Run subtest pattern. Wrap the entire test body in a t.Run call with a
descriptive name starting with "Should" that explains what the test verifies
(for example, reporting worktree paths in parallel runs). Apply this same t.Run
pattern to all other test functions in this file that currently have direct test
bodies without subtest wrappers.

In `@internal/core/workspace/config_test.go`:
- Around line 531-533: The table-driven test subtests in the test function are
using simple names like "one", "two", "five" in the tc.name field instead of
following the required "Should..." naming pattern per coding guidelines. Locate
the test case table definition (the slice of struct containing the test cases)
and update each test case's name field to start with "Should" followed by a
descriptive phrase that explains what the test validates, then the
t.Run(tc.name, func(t *testing.T) call will automatically use these properly
formatted names.

In `@internal/daemon/task_multi_test.go`:
- Around line 1542-1544: The error check in the GetRun call for "child-beta" on
line 1542 only verifies that an error occurred (err != nil) but does not assert
the specific error type, which can hide unrelated failures. Replace the weak
error check with an explicit assertion that the returned error is
globaldb.ErrRunNotFound to ensure the correct sentinel error is raised when the
run is not found, following the test guidelines that require specific error
assertions.

In `@internal/daemon/task_multi_worktree_test.go`:
- Around line 208-210: The test cases for sanitizeTaskMultiWorktreeSegment are
using tc.name values that do not follow the required "Should..." naming
convention. Update all test case name entries in the table-driven test to start
with "Should" followed by a descriptive action, so that when t.Run(tc.name, ...)
is called at line 208, each subtest will have a name matching the "Should..."
pattern as required by the test policy.

In `@internal/daemon/task_multi_worktree.go`:
- Around line 177-186: The sanitizeTaskMultiWorktreeSegment function call on
line 177 truncates the ParentRunID to 12 characters, which can cause path
collisions when different parent runs share the same leading characters,
resulting in worktree target already exists errors later in the code. After
sanitizing the ParentRunID using sanitizeTaskMultiWorktreeSegment to keep it
short, append a short hash suffix computed from the full spec.ParentRunID value
to the parent variable to maintain collision resistance while keeping paths
concise. This hash suffix should be deterministic and derived from the complete
parent run ID before truncation, then incorporated into the path construction in
the filepath.Join call.

In `@pkg/compozy/events/kinds/docs_test.go`:
- Around line 16-31: The test function
TestTaskRunMultiplePayloadFieldsDocumented does not follow the repository's
mandatory test convention. Wrap the entire test body after t.Parallel() in a
t.Run subtest with a descriptive name following the "Should..." pattern (for
example, "ShouldDocumentAllTaskRunMultiplePayloadFields"). Move the content
reading, payloadType reflection, field iteration, and validation checks inside
the t.Run callback function to conform to the required test structure.

---

Nitpick comments:
In `@internal/api/httpapi/openapi_contract_test.go`:
- Around line 186-193: The field validation loop that iterates through the array
of fields (slugs, mode, parallel_limit, presentation_mode, runtime_overrides,
workspace) currently checks all fields within a single test path, causing the
entire test to fail on the first field error. Refactor this by wrapping each
field validation check inside a t.Run() subtest with a descriptive name like
t.Run(fmt.Sprintf("Should validate field: %s", field), func(t *testing.T) { ...
}) to align with the test guidelines and provide granular failure reporting.
Apply this same pattern to all similar field validation loops in the test
function where multiple fields are checked in sequence without individual
subtests.
🪄 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: 691a8dfa-423e-473d-a9a2-e9b73236da7d

📥 Commits

Reviewing files that changed from the base of the PR and between 8e99a28 and 0810b7f.

⛔ Files ignored due to path filters (33)
  • .compozy/tasks/multi-task-run/_idea.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/_prd.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/_tasks.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/_techspec.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/adrs/adr-005.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/adrs/adr-006.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/adrs/adr-007.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/adrs/adr-008.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/memory/MEMORY.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/memory/task_01.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/memory/task_02.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/memory/task_03.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/memory/task_04.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/memory/task_05.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/memory/task_06.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/memory/task_07.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/memory/task_08.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/memory/task_09.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/memory/task_10.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/task_01.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/task_02.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/task_03.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/task_04.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/task_05.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/task_06.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/task_07.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/task_08.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/task_09.md is excluded by !**/*.md
  • .compozy/tasks/multi-task-run/task_10.md is excluded by !**/*.md
  • README.md is excluded by !**/*.md
  • docs/events.md is excluded by !**/*.md
  • openapi/compozy-daemon.json is excluded by !**/*.json
  • web/src/generated/compozy-openapi.d.ts is excluded by !**/generated/**
📒 Files selected for processing (36)
  • internal/api/client/client.go
  • internal/api/client/client_contract_test.go
  • internal/api/contract/contract_test.go
  • internal/api/contract/types.go
  • internal/api/core/handlers.go
  • internal/api/core/handlers_smoke_test.go
  • internal/api/httpapi/openapi_contract_test.go
  • internal/cli/daemon_commands.go
  • internal/cli/daemon_commands_test.go
  • internal/cli/root_command_execution_test.go
  • internal/cli/root_test.go
  • internal/cli/run_observe.go
  • internal/cli/state.go
  • internal/cli/tasks_run_parallel_e2e_test.go
  • internal/cli/testdata/tasks_run_help.golden
  • internal/config/home.go
  • internal/config/home_test.go
  • internal/core/run/ui/multi_remote.go
  • internal/core/run/ui/multi_remote_test.go
  • internal/core/workspace/config_merge.go
  • internal/core/workspace/config_test.go
  • internal/core/workspace/config_types.go
  • internal/core/workspace/config_validate.go
  • internal/daemon/host.go
  • internal/daemon/run_manager.go
  • internal/daemon/run_manager_test.go
  • internal/daemon/task_multi.go
  • internal/daemon/task_multi_test.go
  • internal/daemon/task_multi_worktree.go
  • internal/daemon/task_multi_worktree_test.go
  • internal/store/sqlite.go
  • internal/version/version.go
  • pkg/compozy/events/docs_test.go
  • pkg/compozy/events/kinds/docs_test.go
  • pkg/compozy/events/kinds/payload_compat_test.go
  • pkg/compozy/events/kinds/task.go

Comment thread internal/api/contract/contract_test.go
Comment thread internal/api/core/handlers_smoke_test.go
Comment thread internal/cli/tasks_run_parallel_e2e_test.go
Comment thread internal/core/workspace/config_test.go
Comment thread internal/daemon/task_multi_test.go Outdated
Comment thread internal/daemon/task_multi_worktree_test.go
Comment thread internal/daemon/task_multi_worktree.go
Comment thread pkg/compozy/events/kinds/docs_test.go
- worktree path appends a SHA-256 digest of the full parent run id so distinct parent runs no longer collide on the shared truncated "task-multi-<date>" prefix; preserved worktrees previously made a re-run fail with "target already exists".

- assert globaldb.ErrRunNotFound for the not-started child instead of a bare nil check.

- wrap test bodies / rename table cases to Should-prefixed t.Run subtests per the repo convention (contract, config, handlers smoke, parallel e2e, kinds docs, worktree sanitize).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 15, 2026
Comment thread internal/cli/daemon_commands.go
…vent

Addresses Greptile review findings on compozy#200:

- Ctrl+C during a streamed multi-run no longer surfaces a context-canceled error: the final handoff fetch is skipped when the context is already done (P1).

- An explicit --parallel-limit is now rejected when the resolved mode is enqueued instead of being silently discarded (P2).

- Parallel mode no longer emits item_queued twice per child; the upfront seeding is skipped for parallel (the started event already seeds the snapshot) so --stream shows each slug queued once (P2).

Regression tests added for each.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 15, 2026

@pedronauck pedronauck left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I reviewed this PR from an isolated worktree and ran three independent review passes (Skeptic, Architect, Minimalist). The core scheduler/worktree implementation looks solid overall, and make verify passes, but I’m requesting changes for one public contract mismatch that will affect generated clients.

Blocking change request

TaskRunMultipleItem.status is inconsistent across the live daemon, event docs, OpenAPI, and generated TypeScript client.

The daemon emits and snapshots "running" for an active child item:

  • internal/daemon/task_multi.go:25 defines taskMultiItemStatusRunning = "running".
  • internal/daemon/task_multi.go:1121 copies the event payload status directly into the API snapshot item.
  • docs/events.md:370 documents the item status set as queued, running, completed, failed, or canceled.

But the public schema and generated TS client still advertise "active" and omit "running":

  • openapi/compozy-daemon.json:1527 has ["queued", "active", "completed", "failed", "canceled"].
  • web/src/generated/compozy-openapi.d.ts:987 mirrors that as "queued" | "active" | "completed" | "failed" | "canceled".
  • The new metadata round-trip test in internal/api/contract/contract_test.go:558 also preserves Status: "active", which reinforces the stale schema value instead of the runtime value.

Failure scenario: while a multi-run child is running, GET /api/task-runs/multiple/:run_id/snapshot can return status: "running", which is outside the generated TypeScript union. Consumers generated from this PR’s OpenAPI contract can reject, mishandle, or fail to exhaustively handle a normal live snapshot. "active" is effectively dead for this path unless the daemon explicitly remaps it.

Please either:

  1. Update the OpenAPI schema/source and generated TypeScript types to use "running", and update the contract tests to prove snapshot item statuses match runtime/event statuses, or
  2. Intentionally remap the daemon snapshot status to "active" everywhere and update the event docs/runtime tests accordingly.

Non-blocking notes

  • _txlock=immediate in internal/store/sqlite.go is a process-wide SQLite behavior change, not just a parallel multi-run fix. The rationale makes sense for concurrent child writers, but it is worth calling out in the PR because explicit read transactions elsewhere may now wait for or contend on the writer lock.
  • The TUI now reserves a permanent worktree — line even for default enqueued multi-runs where worktree metadata is never populated. That is not a merge blocker, but suppressing the line until any worktree metadata exists would preserve transcript space and avoid a confusing placeholder.
  • If a journal emit fails immediately after worktree allocation, the worktree can exist without metadata in the snapshot. Narrow window, but worth considering in follow-up cleanup/recovery work.

Verification I ran locally in ../_worktrees/pr-200-review:

  • make verify -> exit 0
  • Focused checks: go test ./internal/daemon ..., go test ./internal/cli ..., go test ./internal/api/contract, go test ./internal/api/client, go test ./internal/api/core, narrow internal/api/httpapi OpenAPI contract test, go test ./pkg/compozy/events/..., and go test ./internal/core/workspace ./internal/config all passed.

MatheusBBarni and others added 2 commits June 15, 2026 21:30
Resolve two conflicts introduced by the upstream TUI redesign (compozy#201):
- internal/cli/daemon_commands.go: keep version.UnstampedCommit (same "none"
  value as upstream's noneValue, but semantically precise for a build commit).
- internal/core/run/ui/multi_remote.go: port the multi-run worktree handoff
  line onto the redesigned tab-strip API (renderBrandTabsRow, charmtheme.Keycap,
  bg-less renderGap/renderOwnedLineKnownOwned). multiRunTabsHeight stays 3 to
  reserve the tab line, worktree status line, and separator.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…tract

The daemon emits and snapshots "running" for an active multi-run child item
(taskMultiItemStatusRunning) and docs/events.md documents the set as
queued/running/completed/failed/canceled, but the OpenAPI schema and the
generated TypeScript client still advertised "active". A live
GET /api/task-runs/multiple/:run_id/snapshot could therefore return a status
outside the generated TS union, breaking generated consumers.

- openapi/compozy-daemon.json: TaskRunMultipleItem.status enum active -> running
- web/src/generated/compozy-openapi.d.ts: regenerated via `bun run codegen`
- contract/smoke tests: assert "running" instead of the stale "active"
- internal/daemon: add TestTaskRunMultipleItemStatusesMatchOpenAPIEnum proving
  the runtime status constants exactly match the published OpenAPI enum, so the
  contract and runtime can no longer drift silently.

Addresses PR compozy#200 review (pullrequestreview-4499973566).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/core/run/ui/multi_remote_test.go`:
- Around line 56-83: Wrap the body of test functions in the t.Run() subtest
pattern to align with the repository's testing standards. For the
TestRemoteMultiRunModelAppliesWorkspaceRootToParentAndChildren function and the
test code at lines 296-330, 332-350, and 394-414, move the test logic inside a
t.Run("Should...", func(t *testing.T) { ... }) call, using descriptive subtest
names that clearly explain what the test validates. This ensures all test cases
follow the mandatory t.Run() wrapper pattern required by the coding 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: 14e6d9c0-4e10-4cfd-9eb2-b13a8c303943

📥 Commits

Reviewing files that changed from the base of the PR and between 5ac952f and 9dd9cfa.

⛔ Files ignored due to path filters (3)
  • docs/events.md is excluded by !**/*.md
  • openapi/compozy-daemon.json is excluded by !**/*.json
  • web/src/generated/compozy-openapi.d.ts is excluded by !**/generated/**
📒 Files selected for processing (18)
  • internal/api/client/client.go
  • internal/api/client/client_contract_test.go
  • internal/api/contract/contract_test.go
  • internal/api/contract/types.go
  • internal/api/core/handlers.go
  • internal/api/core/handlers_smoke_test.go
  • internal/api/httpapi/openapi_contract_test.go
  • internal/cli/daemon_commands.go
  • internal/cli/daemon_commands_test.go
  • internal/cli/root_command_execution_test.go
  • internal/cli/run_observe.go
  • internal/core/run/ui/multi_remote.go
  • internal/core/run/ui/multi_remote_test.go
  • internal/core/workspace/config_test.go
  • internal/core/workspace/config_validate.go
  • internal/daemon/run_manager.go
  • internal/daemon/task_multi_test.go
  • pkg/compozy/events/docs_test.go
🚧 Files skipped from review as they are similar to previous changes (16)
  • internal/api/client/client.go
  • internal/api/contract/types.go
  • internal/core/workspace/config_validate.go
  • internal/daemon/run_manager.go
  • internal/api/core/handlers.go
  • pkg/compozy/events/docs_test.go
  • internal/api/httpapi/openapi_contract_test.go
  • internal/cli/run_observe.go
  • internal/api/contract/contract_test.go
  • internal/api/client/client_contract_test.go
  • internal/core/workspace/config_test.go
  • internal/cli/daemon_commands.go
  • internal/cli/daemon_commands_test.go
  • internal/core/run/ui/multi_remote.go
  • internal/cli/root_command_execution_test.go
  • internal/daemon/task_multi_test.go

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/core/run/ui/multi_remote_test.go`:
- Around line 56-83: Wrap the body of test functions in the t.Run() subtest
pattern to align with the repository's testing standards. For the
TestRemoteMultiRunModelAppliesWorkspaceRootToParentAndChildren function and the
test code at lines 296-330, 332-350, and 394-414, move the test logic inside a
t.Run("Should...", func(t *testing.T) { ... }) call, using descriptive subtest
names that clearly explain what the test validates. This ensures all test cases
follow the mandatory t.Run() wrapper pattern required by the coding 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: 14e6d9c0-4e10-4cfd-9eb2-b13a8c303943

📥 Commits

Reviewing files that changed from the base of the PR and between 5ac952f and 9dd9cfa.

⛔ Files ignored due to path filters (3)
  • docs/events.md is excluded by !**/*.md
  • openapi/compozy-daemon.json is excluded by !**/*.json
  • web/src/generated/compozy-openapi.d.ts is excluded by !**/generated/**
📒 Files selected for processing (18)
  • internal/api/client/client.go
  • internal/api/client/client_contract_test.go
  • internal/api/contract/contract_test.go
  • internal/api/contract/types.go
  • internal/api/core/handlers.go
  • internal/api/core/handlers_smoke_test.go
  • internal/api/httpapi/openapi_contract_test.go
  • internal/cli/daemon_commands.go
  • internal/cli/daemon_commands_test.go
  • internal/cli/root_command_execution_test.go
  • internal/cli/run_observe.go
  • internal/core/run/ui/multi_remote.go
  • internal/core/run/ui/multi_remote_test.go
  • internal/core/workspace/config_test.go
  • internal/core/workspace/config_validate.go
  • internal/daemon/run_manager.go
  • internal/daemon/task_multi_test.go
  • pkg/compozy/events/docs_test.go
🚧 Files skipped from review as they are similar to previous changes (16)
  • internal/api/client/client.go
  • internal/api/contract/types.go
  • internal/core/workspace/config_validate.go
  • internal/daemon/run_manager.go
  • internal/api/core/handlers.go
  • pkg/compozy/events/docs_test.go
  • internal/api/httpapi/openapi_contract_test.go
  • internal/cli/run_observe.go
  • internal/api/contract/contract_test.go
  • internal/api/client/client_contract_test.go
  • internal/core/workspace/config_test.go
  • internal/cli/daemon_commands.go
  • internal/cli/daemon_commands_test.go
  • internal/core/run/ui/multi_remote.go
  • internal/cli/root_command_execution_test.go
  • internal/daemon/task_multi_test.go
🛑 Comments failed to post (1)
internal/core/run/ui/multi_remote_test.go (1)

56-83: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Adopt mandatory t.Run("Should...") wrappers for these new test cases.

These added tests are written as direct top-level bodies instead of the required subtest pattern. Please wrap each case in t.Run("Should ...", func(t *testing.T) { ... }) to align with the repository’s _test.go standard.

As per coding guidelines, "MUST use t.Run(\"Should...\") pattern for ALL test cases" and "Use table-driven tests with subtests (t.Run) as the default pattern."

Also applies to: 296-330, 332-350, 394-414

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/core/run/ui/multi_remote_test.go` around lines 56 - 83, Wrap the
body of test functions in the t.Run() subtest pattern to align with the
repository's testing standards. For the
TestRemoteMultiRunModelAppliesWorkspaceRootToParentAndChildren function and the
test code at lines 296-330, 332-350, and 394-414, move the test logic inside a
t.Run("Should...", func(t *testing.T) { ... }) call, using descriptive subtest
names that clearly explain what the test validates. This ensures all test cases
follow the mandatory t.Run() wrapper pattern required by the coding guidelines.

Source: Coding guidelines

CodeRabbit flagged four test functions in multi_remote_test.go that ran their
assertions at the top level instead of the repository's mandatory
t.Run("Should...") subtest pattern. Wrap each body in a descriptive subtest:
- TestRemoteMultiRunModelAppliesWorkspaceRootToParentAndChildren
- TestMultiRunSpinnerSurvivesIdleActiveTab
- TestMultiRunSpinnerStopsWhenNoTabActive
- TestMultiRunTabsShowBrandOnce

No behavior change; assertions are unchanged. Addresses PR compozy#200 CodeRabbit review.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@MatheusBBarni
MatheusBBarni requested a review from pedronauck June 16, 2026 00:50
@pedronauck
pedronauck merged commit 6fa1d4b into compozy:main Jun 16, 2026
7 checks passed
This was referenced Jun 19, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Jul 3, 2026
compozybot added a commit that referenced this pull request Jul 3, 2026
* docs: update skill

* fix: support parallel execution when compozy runs inside a git worktree

Worktree-backed parallel execution (#200, #217) assumed the workspace
root is a primary checkout and was never tested from inside a linked
git worktree. Root-caused and fixed four confirmed failure modes:

- Git env leakage: daemon/CLI git runners inherited GIT_DIR,
  GIT_WORK_TREE, GIT_INDEX_FILE, GIT_COMMON_DIR and could resolve the
  wrong repository. Added internal/core/gitenv with a shared sanitizer
  applied to the worktree allocator, review-watch runner, CLI preflight,
  and snapshot runner.
- Repo-wide prune blast radius: scoped cleanup no longer runs
  git worktree prune across the whole repo family; owned paths are
  removed explicitly and Prune was dropped from the production
  WorktreeLifecycle surface.
- Purge vs live nested runs: run purge now defers when a planned
  worktree hosts another active run's workspace root, and when the
  registered workspace root no longer exists; shutdown logs and skips
  deferred purges instead of failing the pass.
- Late detached-HEAD failure: parallel task modes now preflight in the
  CLI before daemon bootstrap, rejecting compozy-managed worktree
  recursion and detached HEAD with actionable errors before any run
  rows, events, or worktrees are created.

Refuted with new linked-worktree coverage: fast-forward semantics,
purge branch-retention scope, artifact mirroring, workspace registry
double identity, and snapshot/scope on .git-file worktrees. Added a
linked-worktree fixture and S1-S6 scenario tests (unit + e2e).

Co-Authored-By: Claude Fable 5 <[email protected]>

* fix: address pr 223 review batch

* fix: wrap gitenv run errors

* fix: address gitenv review feedback

---------

Co-authored-by: Pedro Nauck <[email protected]>
Co-authored-by: Claude Fable 5 <[email protected]>
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.

2 participants