Skip to content

CmuxFleet: engine package skeleton (model, supervisor, scheduler, backoff) - #7374

Closed
austinywang wants to merge 7 commits into
mainfrom
feat-fleet-engine-skeleton
Closed

austinywang wants to merge 7 commits into
mainfrom
feat-fleet-engine-skeleton

Conversation

@austinywang

@austinywang austinywang commented Jul 5, 2026 •

Copy link
Copy Markdown
Contributor

Scope

Part 1 of #7361.

Adds a new pure SwiftPM package at Packages/macOS/CmuxFleet with deterministic, dependency-free Fleet model and reducer logic only. This PR includes typed identifiers, task/run snapshots, signals, commands, supervisor reducer, scheduler, retry backoff, and path sanitizer.

No app wiring, control-socket domain, IO, timers, UI, or localization changes are included.

Tests

  • cd Packages/macOS/CmuxFleet && swift test
  • python3 scripts/swift_file_length_budget.py
  • python3 scripts/check-workspace-package-groups.py --check
  • python3 scripts/check-package-resolved-policy.py

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Adds CmuxFleet, a deterministic Fleet engine core (model, supervisor, scheduler, backoff) with tests. Scopes run signals by attempt and preps the engine skeleton for #7361; no app wiring or I/O.

  • New Features

    • New package at Packages/macOS/CmuxFleet with typed IDs and task/run/PR types.
    • Pure FleetSupervisor reducer and deterministic FleetScheduler with concurrency and provisioning caps.
    • FleetBackoff and FleetPathSanitizer utilities; directory names use a stable hash suffix to avoid collisions; CI builds/tests CmuxFleet.
  • Bug Fixes

    • Enforces max retry attempts and scopes run signals by attempt; drops stale/mismatched events (agent start/stop, backoff elapsed, PID exit, stall timeout).
    • Fixes PR handoff races: when a PR appears during retry backoff, transition to awaiting review or done and cancel the scheduled backoff; ignore stale backoff elapses.
    • Correctly completes and cleans up when a PR turns terminal from awaiting review.
    • Path sanitizer fallback is sanitized and never empty.

Written for commit 6d10dd7. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added a new macOS package for fleet task supervision, dispatch scheduling, and retry/backoff logic, including workspace path generation.
    • Introduced a structured set of public models for tasks, runs, signals, notifications, and PR/task lifecycle states.
  • Bug Fixes
    • Improved backoff timing with overflow-aware, capped exponential delays and deterministic behavior.
    • Added stricter, validated task state transitions and stale-signal handling in supervision logic.
  • Tests
    • Added comprehensive unit tests for backoff, scheduling, sanitization, supervisor state transitions, race conditions, and stale signals.
  • Chores
    • Updated CI to include the new package in SwiftPM test runs.

@vercel

vercel Bot commented Jul 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Canceled Canceled Jul 5, 2026 9:04am
cmux-staging Building Building Preview, Comment Jul 5, 2026 9:04am

@coderabbitai

coderabbitai Bot commented Jul 5, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the CmuxFleet SwiftPM package, its core Fleet models and supervision logic, deterministic backoff/dispatch/path utilities, CI and workspace wiring, and unit tests covering the new behaviors.

Changes

CmuxFleet package implementation

Layer / File(s) Summary
Package and workspace wiring
Packages/macOS/CmuxFleet/Package.swift, .github/workflows/ci.yml, cmux.xcworkspace/contents.xcworkspacedata
Adds the CmuxFleet SwiftPM manifest, includes the package in the CI SwiftPM test matrix, and registers it in the workspace.
Identifiers, enums, and task models
Packages/macOS/CmuxFleet/Sources/CmuxFleet/Model/*
Defines the Fleet identifier types, task/run models, PR state/status types, source kinds, notification kinds, command enum, signal enum, and task state lifecycle helpers.
Backoff and dispatch selection
Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetBackoff.swift, Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetScheduler.swift, Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetBackoffTests.swift, Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetSchedulerTests.swift
Adds capped exponential backoff calculation and queued-task dispatch selection with capacity, ordering, and de-duplication tests.
Supervisor reducer and transition logic
Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetSupervisionConfig.swift, Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetSupervisor.swift, Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetSupervisor*.swift, Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetTaskStateTests.swift
Adds supervision limits, the deterministic reducer, guarded state transitions, and tests covering signals, retries, cancellation, PR races, stale inputs, and task-state legality.
Path sanitization
Packages/macOS/CmuxFleet/Sources/CmuxFleet/Provision/FleetPathSanitizer.swift, Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetPathSanitizerTests.swift
Adds deterministic filesystem-safe directory name generation and tests for trimming, hashing, collision avoidance, and fallback handling.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FleetSignal
  participant FleetSupervisor
  participant FleetBackoff
  participant FleetCommand

  FleetSignal->>FleetSupervisor: reduce(task, signal)
  FleetSupervisor->>FleetSupervisor: validate state and choose transition
  alt retry or relaunch
    FleetSupervisor->>FleetBackoff: delayMS(attempt, maxRetryBackoffMS)
    FleetBackoff-->>FleetSupervisor: delayMS
    FleetSupervisor->>FleetCommand: scheduleBackoff / resendAgentCommand
  else cancel or complete
    FleetSupervisor->>FleetCommand: killAgent / cleanupWorkspace / cancelBackoff
  else notify
    FleetSupervisor->>FleetCommand: postNotification
  end
  FleetSupervisor-->>FleetSignal: updated task and emitted commands
Loading

Possibly related issues

Suggested reviewers: lawrencecchen


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Algorithmic Complexity ❌ Error FleetScheduler.dispatch scans the full tasks array three times and sorts it with no benchmark or size bound, on a scalable scheduler path. In Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetScheduler.swift:21-72, compute active/provisioning counts and queued candidates in one pass (or cache counts) before sorting only the selected queue.
Description check ⚠️ Warning The description includes scope and testing, but it does not follow the required template sections or include the review trigger and checklist. Add the required Summary, Testing, Demo Video, Review Trigger, and Checklist sections so the PR description matches the repository template.
✅ Passed checks (23 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PASS: The diff is a pure SwiftPM value-type/function package; I found no @MainActor, actor/protocol/class, or shared-mutable Sendable isolation regressions in production files.
Cmux Swift Blocking Runtime ✅ Passed No semaphores, sleeps, sync waits, polling, delayed dispatch, or locks were added; the new Fleet code is pure/deterministic and test scaffolding only.
Cmux Browser Automation Off-Main ✅ Passed HEAD only changes CmuxFleet files; no browser automation, TerminalController, or ControlCommandExecutionPolicy code was touched, so the rule isn’t implicated.
Cmux Expensive Synchronous Load ✅ Passed PASS: The diff only adds pure model/reducer/path-sanitizer code; no @MainActor interactive path, disk/history loads, or JSON/transcript scans were added.
Cmux Cache Substitution Correctness ✅ Passed No authoritative fresh-read was replaced by a cached value; the new CmuxFleet code is pure deterministic model/reducer logic with no persistence/history/undo cache substitution.
Cmux No Hacky Sleeps ✅ Passed PASS: The only non-Swift change is GitHub Actions YAML, which the rule explicitly excludes; no new runtime sleep/polling/delayed-dispatch code was added.
Cmux Swift Concurrency ✅ Passed CmuxFleet adds only synchronous model/reducer code; no DispatchQueue, Combine, completion-handler, or fire-and-forget Task usage appears in the diff.
Cmux Swift @Concurrent ✅ Passed Changed CmuxFleet files contain no async, nonisolated, or @concurrent code; the rule’s failure conditions are not triggered.
Cmux Swift File And Package Boundaries ✅ Passed CmuxFleet is a new isolated SwiftPM package; all production Swift files are under 400 lines and pure domain logic, with oversized code only in tests.
Cmux Swiftpm Lockfiles ✅ Passed CmuxFleet Package.swift adds only local targets; no external deps, no package-local Package.resolved/.gitignore, and no xcodeproj package-ref changes.
Cmux Swift Logging ✅ Passed No added Swift runtime logging found in CmuxFleet sources/tests; scans showed no print/debugPrint/dump/NSLog/Logger usages.
Cmux User-Facing Error Privacy ✅ Passed PASS: The PR adds internal Fleet models/reducer code and tests only; no user-facing error/alert/command text or API error copy was introduced.
Cmux Full Internationalization ✅ Passed No user-facing Swift text, catalogs, or web locale files were added; the PR only adds model/reducer code, tests, CI, and workspace wiring.
Cmux Swiftui State Layout ✅ Passed The diff only adds a pure SwiftPM engine/models/tests package; no SwiftUI views, ObservableObject/@published, GeometryReader, or render-time state mutation appear.
Cmux Architecture Rethink ✅ Passed CmuxFleet is a pure deterministic SwiftPM package; no sleeps, timers, observers, locks, side channels, or duplicate UI entrypoints were introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No NSWindow/WindowGroup/NSWindowController or cmuxAuxiliaryWindowIdentifiers changes; PR only adds pure Swift model/reducer code.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source/config/test files; no temp dirs, logs, build output, caches, or scratch artifacts appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No production Sources file adds #if DEBUG/test-only accessors or seam-named members; new observability helpers live only in Tests, matching the rule.
Cmux No Ambient Global State ✅ Passed PASS: Production CmuxFleet sources use constructable structs with instance methods; no file-scope funcs/vars or shared/default singletons were added.
Title check ✅ Passed The title clearly summarizes the new CmuxFleet engine package skeleton and its main model, supervisor, scheduler, and backoff work.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-fleet-engine-skeleton

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps

greptile-apps Bot commented Jul 5, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds CmuxFleet, a pure SwiftPM package containing the deterministic model and reducer logic for the Fleet task engine. It introduces typed identifiers, task/run snapshots, signals, commands, a supervisor reducer, a scheduler, retry backoff, and a path sanitizer — all without any I/O, timers, UI, or app wiring.

  • FleetSupervisor is a pure (FleetTask, FleetSignal) → (FleetTask, [FleetCommand]) reducer with attempt-scoped guards on every signal, covering retry backoff, PR handoff, stall recovery, and cancellation.
  • FleetScheduler selects queued tasks for dispatch using a deterministic priority/age/ID sort against global concurrency and provisioning caps; FleetBackoff computes overflow-safe exponential delays.
  • FleetPathSanitizer produces stable, filesystem-safe directory names with an FNV-1a suffix hash; tests cover backoff, scheduling, state transitions, stale signals, and PR race conditions.

Confidence Score: 5/5

This is a pure, dependency-free value-type package with no I/O, timers, UI, or app wiring — all logic is covered by deterministic unit tests that run in isolation.

The reducer, scheduler, backoff, and sanitizer are all pure functions that are straightforwardly testable and tested. The matrix test in FleetSupervisorTests exercises every signal against every state, the stale-signal and PR-race suites cover key edge cases, and the FleetBackoff and FleetPathSanitizer tests cover boundary and overflow conditions. No blocking primitives, global state, or production-source test seams were introduced. The one open gap (a stalled task with an open PR receiving agentStopped silently no-ops) was surfaced in a prior review thread and remains open for a follow-up.

No files require special attention beyond what is already tracked in the open prior review thread about FleetSupervisor and FleetTaskState.

Important Files Changed

Filename Overview
Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetSupervisor.swift Core pure reducer: guard-heavy signal dispatch, correct attempt-scoped stale-signal rejection, consistent isBlocked clearing in retryOrFail and startAttempt, and automatic cancelBackoff emission when leaving retryBackoff via transition. The .stalled → .awaitingReview gap (previously reported, no reply) remains unaddressed.
Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetScheduler.swift Deterministic dispatch with global concurrency and provisioning caps; correctly uses !$0.isBlocked guard on queued tasks, stable 3-key sort (priority→createdAt→id), and a defensive dedup Set. No issues.
Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetBackoff.swift Overflow-safe exponential backoff using multipliedReportingOverflow and a performedDoublings >= Int.bitWidth safety net; correctly clamps negative maxMS to zero. Tests confirm edge cases including Int.max arguments.
Packages/macOS/CmuxFleet/Sources/CmuxFleet/Model/FleetTaskState.swift State machine covers expected edges; isTerminal and isActive properties are consistent with the canTransition table. The .stalled → .awaitingReview edge is intentionally absent from the table but is reached by the supervisor for stalled tasks with an open PR — the previously filed bug remains open.
Packages/macOS/CmuxFleet/Sources/CmuxFleet/Provision/FleetPathSanitizer.swift FNV-1a suffix hash, run-of-bad-chars collapsing, lead/trail trimming, and a hardcoded 'task' ultimate fallback all look correct. No issues.
Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetSupervisorTests.swift Comprehensive matrix test covers every signal × every state; individual scenario tests verify backoff scheduling, kill commands, notification emission, and stale-signal rejection. Coverage of pr != nil from .stalled is absent (the known bug is not covered).
Packages/macOS/CmuxFleet/Package.swift Swift 6, macOS 14, no external dependencies. No Package.resolved needed; ExistentialAny and InternalImportsByDefault upcoming-feature flags are appropriate.
.github/workflows/ci.yml Adds CmuxFleet to the swift-test job in alphabetical order. No issues.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Q[queued] -->|dispatched| P[provisioning]
    Q -->|userCancel / workspaceClosed / sourceReachedTerminalState| CA[cancelled]
    P -->|provisioned| L[launching]
    P -->|provisionFailed| F[failed]
    P -->|userCancel / workspaceClosed| CA
    L -->|agentSessionStarted| R[running]
    L -->|pidExited / stallTimeout / agentStopped| RB[retryBackoff]
    L -->|agentStopped + pr:open| AW[awaitingReview]
    L -->|agentStopped + pr:terminal| D[done]
    L -->|maxAttempts reached| F
    L -->|userCancel / workspaceClosed / sourceReachedTerminalState| CA
    R -->|blockingItemReceived| NI[needsInput]
    R -->|agentStopped + pr:open| AW
    R -->|agentStopped + pr:terminal| D
    R -->|pidExited / promptIdleObserved / stallTimeout / agentStopped| RB
    R -->|maxAttempts reached| F
    R -->|userCancel / workspaceClosed / sourceReachedTerminalState| CA
    NI -->|blockingItemResolved| R
    NI -->|pidExited / stallTimeout| RB
    NI -->|maxAttempts reached| F
    NI -->|userCancel / workspaceClosed / sourceReachedTerminalState| CA
    RB -->|backoffElapsed| L
    RB -->|prChanged:open| AW
    RB -->|prChanged:terminal| D
    RB -->|userCancel / workspaceClosed / sourceReachedTerminalState| CA
    AW -->|prChanged:terminal / sourceReachedTerminalState| D
    AW -->|userRetry| Q
    AW -->|userCancel| CA
    F -->|userRetry| Q
    F -->|prChanged:open| AW
    F -->|prChanged:terminal| D
    CA -->|userRetry| Q
    D:::terminal
    F:::terminal
    CA:::terminal
    classDef terminal fill:#f0f0f0,stroke:#999
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"}}}%%
flowchart TD
    Q[queued] -->|dispatched| P[provisioning]
    Q -->|userCancel / workspaceClosed / sourceReachedTerminalState| CA[cancelled]
    P -->|provisioned| L[launching]
    P -->|provisionFailed| F[failed]
    P -->|userCancel / workspaceClosed| CA
    L -->|agentSessionStarted| R[running]
    L -->|pidExited / stallTimeout / agentStopped| RB[retryBackoff]
    L -->|agentStopped + pr:open| AW[awaitingReview]
    L -->|agentStopped + pr:terminal| D[done]
    L -->|maxAttempts reached| F
    L -->|userCancel / workspaceClosed / sourceReachedTerminalState| CA
    R -->|blockingItemReceived| NI[needsInput]
    R -->|agentStopped + pr:open| AW
    R -->|agentStopped + pr:terminal| D
    R -->|pidExited / promptIdleObserved / stallTimeout / agentStopped| RB
    R -->|maxAttempts reached| F
    R -->|userCancel / workspaceClosed / sourceReachedTerminalState| CA
    NI -->|blockingItemResolved| R
    NI -->|pidExited / stallTimeout| RB
    NI -->|maxAttempts reached| F
    NI -->|userCancel / workspaceClosed / sourceReachedTerminalState| CA
    RB -->|backoffElapsed| L
    RB -->|prChanged:open| AW
    RB -->|prChanged:terminal| D
    RB -->|userCancel / workspaceClosed / sourceReachedTerminalState| CA
    AW -->|prChanged:terminal / sourceReachedTerminalState| D
    AW -->|userRetry| Q
    AW -->|userCancel| CA
    F -->|userRetry| Q
    F -->|prChanged:open| AW
    F -->|prChanged:terminal| D
    CA -->|userRetry| Q
    D:::terminal
    F:::terminal
    CA:::terminal
    classDef terminal fill:#f0f0f0,stroke:#999
Loading

Reviews (5): Last reviewed commit: "Fix CmuxFleet PR handoff races and path ..." | Re-trigger Greptile

Comment on lines +68 to +69
case .queued:
to == .cancelled

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.

P1 Missing queued → provisioning edge in the state machine. FleetScheduler.tasksToDispatch returns queued tasks specifically so the engine can start provisioning them, but canTransition(from: .queued, to: .provisioning) returns false. Because FleetSupervisor.transition calls canTransition as a hard guard, any engine code that routes the queued → provisioning step through the pure supervisor will silently drop the transition and leave the task stuck in .queued forever. The engine must either bypass the guard for this leg or the table must include the edge.

Suggested change
case .queued:
to == .cancelled
case .queued:
to == .provisioning || to == .cancelled

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 7ea2797: added a dispatched(taskID:at:) signal that the reducer handles (queued -> provisioning, emitting provisionWorkspace) and canTransition now permits queued -> provisioning.

— Claude Code

import Foundation

/// Reduces one Fleet task snapshot with one deterministic supervision signal.
public struct FleetSupervisor: Sendable {

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.

P2 Static-only namespace structs

FleetSupervisor, FleetBackoff, FleetScheduler, and FleetPathSanitizer are all public structs whose entire API is static funcs — the cmux no-ambient-global-state rule explicitly flags "a type whose API is mostly static funcs." The canonical fix for a pure reducer is to make configuration a stored property so the type is constructable and injectable: callers build a FleetSupervisor(config:) once and call supervisor.reduce(task:signal:), avoiding both the static-only namespace shape and the repeated config parameter at every call site. The same pattern applies to FleetBackoff, FleetScheduler (taking maxConcurrentAgents/provisioningCap), and FleetPathSanitizer (taking maxLength).

Rule Used: Flag new ambient global state in production Swift:... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 7ea2797: FleetSupervisor, FleetScheduler, FleetBackoff, and FleetPathSanitizer are now instantiable values with constructor-injected configuration (zero static funcs remain in the package).

— Claude Code

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetTaskStateTests.swift (1)

23-43: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test enshrines the same .stalled-unreachable gap as the implementation.

transitionTableMatchesExpectedEdges hardcodes an expected table where no state transitions into .stalled, mirroring the gap flagged in FleetTaskState.canTransition. Once that's fixed, this table needs the corresponding entries added or the test will start failing.

🤖 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 `@Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetTaskStateTests.swift`
around lines 23 - 43, Update the transition expectations in
FleetTaskStateTests.transitionTableMatchesExpectedEdges so they include the
newly supported transitions into .stalled, since the hardcoded expected table
currently mirrors the same gap as FleetTaskState.canTransition. Locate the test
by the transitionTableMatchesExpectedEdges method and adjust the expected
dictionary entries to reflect the corrected state machine, especially any
from-state values that should now allow .stalled.
🤖 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 `@Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetBackoff.swift`:
- Line 2: FleetBackoff is being used as a static-only namespace, which triggers
the namespace-type lint. Move delayMS onto FleetSupervisionConfig via an
extension, or change FleetBackoff so it can be instantiated and expose delayMS
as an instance method instead of keeping it as an all-static public type.

In `@Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetScheduler.swift`:
- Around line 2-13: `FleetScheduler` is being used as a namespace-only static
type, which violates the package lint rule. Make `tasksToDispatch` an
instance-based API on a constructable type (or move the behavior to an extension
on `FleetTask`/`[FleetTask]`) so `FleetScheduler` is no longer an all-static
struct. Update the call sites in `FleetSchedulerTests` and any other users from
`FleetScheduler.tasksToDispatch(...)` to the new instance/extension entry point,
keeping the same task-sorting and cap behavior.

In `@Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetSupervisor.swift`:
- Line 4: `FleetSupervisor` is being used as a namespace-only static type and
triggers the lint failure; move the reducer behavior onto the natural receiver
`FleetTask` instead. Replace `FleetSupervisor.reduce(task:signal:config:)` with
an instance-style `FleetTask.reduced(with:config:)` (or equivalent extension
method), and relocate the helper logic from the `private static` helpers
`acceptsAgentStop`, `startAttempt`, `retryOrFail`, and `transition` into the
same `FleetTask` extension as private helpers. Update call sites like
`FleetSupervisorTests` to invoke the method on the task instance rather than on
`FleetSupervisor`.

In
`@Packages/macOS/CmuxFleet/Sources/CmuxFleet/Provision/FleetPathSanitizer.swift`:
- Line 2: The public FleetPathSanitizer type is namespace-only and triggers
package-conventions-lint because it has only static behavior with no
instantiable surface. Refactor FleetPathSanitizer into an extension on String
and move the public API from FleetPathSanitizer.directoryName(for:) to an
instance-style String method such as fleetSanitizedDirectoryName(), updating all
call sites accordingly. Keep the existing helper logic in the same file as
private static or fileprivate helpers, including isAllowed, trimmed, and
shouldTrim, so the behavior stays unchanged while satisfying the lint.

---

Duplicate comments:
In `@Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetTaskStateTests.swift`:
- Around line 23-43: Update the transition expectations in
FleetTaskStateTests.transitionTableMatchesExpectedEdges so they include the
newly supported transitions into .stalled, since the hardcoded expected table
currently mirrors the same gap as FleetTaskState.canTransition. Locate the test
by the transitionTableMatchesExpectedEdges method and adjust the expected
dictionary entries to reflect the corrected state machine, especially any
from-state values that should now allow .stalled.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 34951315-a47f-4ec0-82dd-d09424d8da33

📥 Commits

Reviewing files that changed from the base of the PR and between 9c91710 and 5fe3bd5.

📒 Files selected for processing (25)
  • .github/workflows/ci.yml
  • Packages/macOS/CmuxFleet/Package.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetBackoff.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetScheduler.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetSupervisionConfig.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetSupervisor.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Model/FleetCommand.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Model/FleetIdentifiers.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Model/FleetNotificationKind.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Model/FleetPullRequestState.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Model/FleetPullRequestStatus.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Model/FleetRun.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Model/FleetRunEndReason.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Model/FleetSignal.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Model/FleetTask.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Model/FleetTaskSourceKind.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Model/FleetTaskState.swift
  • Packages/macOS/CmuxFleet/Sources/CmuxFleet/Provision/FleetPathSanitizer.swift
  • Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetBackoffTests.swift
  • Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetPathSanitizerTests.swift
  • Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetSchedulerTests.swift
  • Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetSupervisorTests.swift
  • Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetTaskStateTests.swift
  • Packages/macOS/CmuxFleet/Tests/CmuxFleetTests/FleetTestSupport.swift
  • cmux.xcworkspace/contents.xcworkspacedata

Comment thread Packages/macOS/CmuxFleet/Sources/CmuxFleet/Engine/FleetScheduler.swift Outdated
Comment on lines +88 to +95
case let .agentStopped(taskID, at):
guard taskID == task.id, acceptsAgentStop(from: task.state) else {
return (task, [])
}
if task.pr != nil {
return transition(task: task, to: .awaitingReview, at: at)
}
return retryOrFail(task: task, at: at, killAgent: false)

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.

P1 .stalled → .awaitingReview transition missing, causing agentStopped to silently no-op

acceptsAgentStop explicitly accepts .stalled (line 178), so the agentStopped handler lets stalled tasks through its guard. When task.pr != nil, the handler calls transition(task:, to: .awaitingReview, at:). However, canTransition(from: .stalled, to: .awaitingReview) returns false — only .retryBackoff, .failed, and .cancelled are permitted from .stalled. transition therefore returns (task, []), dropping the signal entirely. A stalled task with an attached PR will stay stuck in .stalled indefinitely with no recovery path.

The fix is either to add || to == .awaitingReview to the .stalled arm of canTransition, or to fall through to retryOrFail when task.pr != nil but the target transition would be .awaitingReview and the source state doesn't permit it.

This branch was successfully deployed

1 active deployment
Preview – cmux — 6d10dd76 Deployed Jul 5, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants