Skip to content

[codex] Refactor chat parser and transcript service ownership - #6216

Merged
azooz2003-bit merged 9 commits into
mainfrom
refactor-chat-service-injection
Jun 16, 2026
Merged

azooz2003-bit merged 9 commits into
mainfrom
refactor-chat-service-injection

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jun 16, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Change OSC133CommandParser from a reference type to a value type with explicit mutating parse state.
  • Remove AgentChatTranscriptService.shared and make the app composition root own/inject the transcript service into TerminalController.
  • Keep mobile/chat routing behavior on the same service instance while avoiding static runtime state access.

Validation

  • swift test --package-path Packages/CmuxAgentChat --filter OSC133CommandParserTests
  • ./ios/scripts/reload.sh --tag tgchat --no-launch

Notes

Mac ./scripts/reload.sh --tag refactor-chat-injection is currently blocked by an existing CmuxTerminal/Ghostty C API mismatch: ghostty_surface_set_renderer_realized is missing from scope in TerminalSurface+Renderer.swift.


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


Summary by cubic

Refactored the OSC133 chat parser to a value type and moved agent‑chat transcript service ownership to the app root via dependency injection. Typed Ghostty title‑change notifications and localized the “chat service unavailable” error; mobile chat routes now guard cleanly when the service is unset.

  • Refactors

    • OSC133CommandParser is now a struct; consume(_:) is mutating. Tests use var.
    • Removed AgentChatTranscriptService.shared. AppDelegate holds one instance, injects it into TerminalController, and starts it with an adoption callback; the service seeds once and holds a strong GhosttyTitleChangeSubscription.
    • TerminalController exposes an optional agentChatTranscriptService; mobile/chat routes and event ingestion guard and return unavailable if unset. The error is localized via mobile.chat.error.serviceUnavailable (EN/JA).
    • Typed .ghosttyDidSetTitle: added GhosttyTitleChange and GhosttyTitleChangeSubscription; posting/handling updated in GhosttyTerminalView, ContentView, and TabManager.
  • Migration

    • Replace any AgentChatTranscriptService.shared usage with the injected service (TerminalController.agentChatTranscriptService).
    • Ensure AppDelegate sets TerminalController.shared.agentChatTranscriptService and calls start { TerminalController.shared.adoptDetectedAgentSessions(workspaceID: $0) } before mobile/chat sockets are reachable.

Written for commit eb84e0e. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

Release Notes

  • Refactor
    • Improved internal architecture for agent chat transcript handling through enhanced dependency injection patterns.
    • Restructured title change notification system for better maintainability and reduced coupling.
    • Optimized memory management patterns in parser components.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026 •

Copy link
Copy Markdown

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
📝 Walkthrough

Walkthrough

This PR replaces shared agent chat transcript service access with injected wiring, adds typed Ghostty title-change helpers used by notification producers and consumers, and converts OSC133CommandParser from a class to a struct with corresponding mutable test setup.

Changes

Agent chat transcript injection and typed Ghostty title changes

Layer / File(s) Summary
Ghostty title change payload and subscription
Sources/GhosttyTitleChange.swift, Sources/GhosttyTitleChangeSubscription.swift, cmux.xcodeproj/project.pbxproj
Adds a typed title-change payload and a notification subscription helper, and wires both new source files into the Xcode target.
Typed title change adoption in UI handlers
Sources/GhosttyTerminalView.swift, Sources/ContentView.swift, Sources/TabManager.swift
Title notifications are posted from GhosttyTitleChange.userInfo, and UI consumers parse GhosttyTitleChange(notification:) instead of reading raw userInfo keys.
Transcript service startup and controller injection
Sources/Mobile/AgentChat/AgentChatTranscriptService.swift, Sources/TerminalController.swift, Sources/AppDelegate.swift
Removes the shared transcript service singleton, adds injected startup and controller attachment paths, and has AppDelegate own and start the service with a callback into TerminalController.
Mobile chat access through optional service
Sources/TerminalController+MobileChat.swift
Mobile chat endpoints and helpers now guard the injected transcript service and use it when present for history refresh, adoption, dumps, and session parameter lookup.

OSC133 parser value-type conversion

Layer / File(s) Summary
Parser struct conversion and mutable test usage
Packages/CmuxAgentChat/Sources/CmuxAgentChat/Parsing/OSC133CommandParser.swift, Packages/CmuxAgentChat/Tests/CmuxAgentChatTests/OSC133CommandParserTests.swift
OSC133CommandParser becomes a struct, its state-changing methods become mutating, and the tests switch parser bindings from let to var.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • manaflow-ai/cmux#5984: Also changes AgentChatTranscriptService and its TerminalController integration points, making it directly related to this service-wiring refactor.

Poem

🐇 I packed a transcript in my tiny app bag,
and traded one singleton for wiring less shaggy.
Ghostty titles now hop in a typed little frame,
while parsers turned struct and still play the same.
Carrot cheers for these neat tracks through the code!


Important

Pre-merge checks failed

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

❌ Failed checks (2 errors, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Cmux Expensive Synchronous Load ❌ Error PR adds expensive synchronous FileManager.contentsOfDirectory calls on the main actor via a title-change notification handler: GhosttyTitleChangeSubscription→adoptDetectedAgentSessions (on @MainAct... Move FileManager scan to Task.detached or async closure; gate main-actor adoption with cached off-main result or nil check as per swift-expensive-sync-load.md pattern.
Cmux Swift Concurrency ❌ Error GhosttyTitleChangeSubscription.swift introduces a fire-and-forget Task (line 19) with real lifecycle: Task { @mainactor in handler(change) } executes a closure with side effects without storing/c... Replace the fire-and-forget Task with direct @MainActor isolated handler call, or store the Task to manage its lifecycle explicitly when the closure invocation has meaningful side effects.
Docstring Coverage ⚠️ Warning Docstring coverage is 31.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description covers key changes but lacks required Testing and Checklist sections from the template. Add the Testing section (how changes were tested, what was verified manually) and complete the Checklist section confirming local testing, test additions, and bot reviews.
✅ Passed checks (17 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main refactoring changes: converting the parser to a value type and removing the shared singleton in favor of dependency injection.
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 All production changes maintain Swift 6 actor isolation compliance: OSC133CommandParser (struct, no isolation needed), GhosttyTitleChange (pure Sendable value type, correctly not @MainActor), Ghost...
Cmux Swift Blocking Runtime ✅ Passed PR introduces no blocking runtime primitives (semaphores, locks, sleeps, asyncAfter, main-queue sync) per swift-blocking-runtime.md rules; uses proper async/await patterns and callback-based depend...
Cmux Cache Substitution Correctness ✅ Passed PR eliminates .shared singleton via dependency injection and correctly uses staleRecord only for freshness detection before retrying fresh reads from authoritative sources.
Cmux No Hacky Sleeps ✅ Passed PR modifies only Swift source files and Xcode project file; contains no TypeScript, JavaScript, shell, or build/runtime script changes. This check applies only to those languages, per its scope def...
Cmux Algorithmic Complexity ✅ Passed PR refactors service ownership via dependency injection and converts parser to value type. No new algorithmic complexity violations introduced; the pre-existing O(NMlogM) pattern in adoptDetected...
Cmux Swift @Concurrent ✅ Passed PR introduces no Swift concurrency violations. Only new async code is Task { @MainActor in ... } in GhosttyTitleChangeSubscription, which properly schedules handler(@MainActor) work and qualifies a...
Cmux Swift File And Package Boundaries ✅ Passed New files (31, 28 lines) have clear single responsibilities. All existing files receive <250 lines added (actual: +4, +2, +1, +2). No mixed responsibilities. Removed singleton improves architecture...
Cmux Swift Logging ✅ Passed PR introduces no logging violations. New files GhosttyTitleChange.swift and GhosttyTitleChangeSubscription.swift contain no logging. Modified production code avoids print/debugPrint/dump/NSLog. One...
Cmux User-Facing Error Privacy ✅ Passed PR introduces no user-facing error privacy violations. Internal RPC errors follow established codebase patterns (e.g., "TabManager not available") with no credentials, vendor names, or sensitive da...
Cmux Full Internationalization ✅ Passed This PR introduces no new user-facing Swift UI/menu/alert/tooltip text. Three new RPC error messages in TerminalController+MobileChat.swift follow the existing pattern of unlocalized API responses...
Cmux Swiftui State Layout ✅ Passed PR introduces no new @Published/@Observable/@StateObject patterns, GeometryReader layout changes, store-reference-holding lazy rows, or render-time state mutations. All SwiftUI changes are either r...
Cmux Architecture Rethink ✅ Passed PR removes AgentChatTranscriptService.shared singleton, centralizes ownership in AppDelegate, and implements clear injection into TerminalController. No timing repairs, locks, observers creating sp...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR does not add or materially change any cmux-owned windows. New files are notification data structs and observer wrappers (no NSWindow/NSPanel/WindowGroup). Pre-existing window code in modified fi...
Cmux Source Artifacts ✅ Passed All 12 changed files are hand-written source, intentional test code, or required build configuration. No local artifacts, generated logs, caches, or forbidden directories detected in the diff.
✨ 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 refactor-chat-service-injection

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 and usage tips.

@vercel

vercel Bot commented Jun 16, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 16, 2026 7:48pm
cmux-staging Building Building Preview, Comment Jun 16, 2026 7:48pm

@azooz2003-bit
azooz2003-bit marked this pull request as ready for review June 16, 2026 03:28
@greptile-apps

greptile-apps Bot commented Jun 16, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR refactors OSC133CommandParser from a reference type to a value type, removes AgentChatTranscriptService.shared, and wires the service through AppDelegate → TerminalController dependency injection. It also introduces typed GhosttyTitleChange / GhosttyTitleChangeSubscription wrappers for the title-change notification.

  • OSC133CommandParser → struct: All consume/apply/appendText/openBlock/closeBlock/finalizeOpenOutput/flushOpenOutput methods marked mutating; tests updated to var.
  • DI wiring: AppDelegate owns private let agentChatTranscriptService, injects it into TerminalController.shared.agentChatTranscriptService, then calls start { TerminalController.shared.adoptDetectedAgentSessions(workspaceID: $0) }; all AgentChatTranscriptService.shared call sites in TerminalController+MobileChat replaced with guarded optional access returning a localized unavailable error.
  • Typed title notification: GhosttyTitleChange (Equatable, Sendable) centralises userInfo encoding/decoding; GhosttyTitleChangeSubscription owns the observer token and delivers via Task { @MainActor in } replacing the old MainActor.assumeIsolated inline dispatch.

Confidence Score: 4/5

Safe to merge pending resolution of the open review threads on TerminalController.swift and ContentView.swift that were flagged prior to this round and remain unaddressed in the diff.

The DI wiring and value-type conversion are mechanically correct and the new localized error key covers both supported locales. Two issues from earlier review rounds are still present in the changed code: agentChatTranscriptService on TerminalController is an unrestricted @MainActor var with no write guard (unlike the adjacent authCoordinator/browserSignInFlow), and the optional-chain comparison in ContentView.swift:2650 allows a malformed-notification + no-tab-selected path to call scheduleTitlebarTextRefresh() spuriously.

Sources/TerminalController.swift and Sources/ContentView.swift both have open findings from prior review rounds that are still visible in the diff and have not been addressed by this PR.

Important Files Changed

Filename Overview
Sources/TerminalController.swift Adds agentChatTranscriptService as a bare @MainActor var; unlike authCoordinator/browserSignInFlow it lacks private(set) and a dedicated setter, leaving it open to accidental nil-out or reassignment
Sources/ContentView.swift Optional-chain comparison GhosttyTitleChange(...)?.tabId == tabManager.selectedTabId introduces a nil == nil true case when the notification is malformed and no tab is selected, calling scheduleTitlebarTextRefresh() spuriously
Sources/TerminalController+MobileChat.swift All static AgentChatTranscriptService.shared references replaced with guarded optional access; chatServiceUnavailableErrorMessage now properly localized; send/interrupt/answer return chatTerminalBindingErrorMessage (not chatServiceUnavailableErrorMessage) when the service is nil due to routing through mobileChatTerminalParams
Sources/Mobile/AgentChat/AgentChatTranscriptService.swift Removed .shared singleton; start now takes an injection callback and guards against double-seeding; service holds strong GhosttyTitleChangeSubscription
Sources/AppDelegate.swift Composition root now owns agentChatTranscriptService; injects it into TerminalController and calls start with the adoption callback before mobile sockets become reachable
Sources/GhosttyTitleChangeSubscription.swift New RAII wrapper that owns a NotificationCenter observer; delivers typed changes via Task { @MainActor in }, changing prior synchronous MainActor delivery to async-enqueued
Sources/GhosttyTitleChange.swift New typed payload struct for .ghosttyDidSetTitle; Equatable and Sendable, clean failable init from Notification
Resources/Localizable.xcstrings Adds mobile.chat.error.serviceUnavailable with en and ja translations, consistent with all neighboring mobile.chat.* keys

Reviews (6): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment on lines +129 to 132
guard let service = agentChatTranscriptService else {
return .err(code: "unavailable", message: "Agent chat transcript service is not configured", data: nil)
}
var page = await service.history(sessionID: sessionID, beforeSeq: beforeSeq, limit: limit)

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 The "Agent chat transcript service is not configured" message is sent verbatim to the iOS mobile client as an API error body. The existing not_found error in this same function routes through String(localized:defaultValue:); these new guards skip localization entirely and expose an internal component name. The custom rules require that API error bodies not contain implementation details and that every user-visible string use a localized API. If the iOS app surfaces this message in the chat error banner, it violates both constraints. Replace with a cmux-terms description and a localized key.

Suggested change
guard let service = agentChatTranscriptService else {
return .err(code: "unavailable", message: "Agent chat transcript service is not configured", data: nil)
}
var page = await service.history(sessionID: sessionID, beforeSeq: beforeSeq, limit: limit)
guard let service = agentChatTranscriptService else {
return .err(code: "unavailable", message: String(
localized: "mobile.chat.error.serviceUnavailable",
defaultValue: "Chat is not available right now. Please try again shortly."
), data: nil)
}
var page = await service.history(sessionID: sessionID, beforeSeq: beforeSeq, limit: limit)

Comment on lines +56 to 59
guard let service = agentChatTranscriptService else {
return .err(code: "unavailable", message: "Agent chat transcript service is not configured", data: nil)
}
// Register coding agents cmux detects by terminal title but that never

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 Same issue in v2MobileChatSessions: the hardcoded "Agent chat transcript service is not configured" message is returned to the mobile client, exposes an internal class name, and is not localized. The mobile.chat.sessions RPC result is used by the iOS app to render the chat session list.

Suggested change
guard let service = agentChatTranscriptService else {
return .err(code: "unavailable", message: "Agent chat transcript service is not configured", data: nil)
}
// Register coding agents cmux detects by terminal title but that never
guard let service = agentChatTranscriptService else {
return .err(code: "unavailable", message: String(
localized: "mobile.chat.error.serviceUnavailable",
defaultValue: "Chat is not available right now. Please try again shortly."
), data: nil)
}
// Register coding agents cmux detects by terminal title but that never

/// listener starts. Socket auth commands read these on the main actor.
@MainActor private(set) var authCoordinator: AuthCoordinator?
@MainActor private(set) var browserSignInFlow: HostBrowserSignInFlow?
@MainActor var agentChatTranscriptService: AgentChatTranscriptService?

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 agentChatTranscriptService is exposed as a bare @MainActor var, making it settable (including to nil) by any @MainActor caller. The sibling authCoordinator / browserSignInFlow properties use private(set) and a dedicated attachAuth method to enforce single-writer semantics. Without that guard, accidental reassignment or nilification would silently break all mobile chat without an obvious crash.

Suggested change
@MainActor var agentChatTranscriptService: AgentChatTranscriptService?
@MainActor private(set) var agentChatTranscriptService: AgentChatTranscriptService?

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

🤖 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 `@Sources/Mobile/AgentChat/AgentChatTranscriptService.swift`:
- Around line 56-58: The adoptDetectedAgentSessions callback expects a workspace
ID based on its parameter naming and downstream handler
TerminalController.adoptDetectedAgentSessions(workspaceID:), but at line 81 a
tabId.uuidString is being passed instead. This causes a mismatch where the
surface ID won't correlate with any workspace UUID. Fix this by either: (1)
extracting the actual workspace ID from the title-change notification before
invoking adoptDetectedAgentSessions callback and passing that workspace ID
instead of tabId.uuidString, or (2) renaming the callback parameter from its
current name to reflect surface-ID semantics throughout the chain (the start
method signature, the callback parameter, the
TerminalController.adoptDetectedAgentSessions method parameter, and how it is
used in mobileResolveWorkspaceAndSurface) and ensure proper conversion happens
at the composition root in AppDelegate.swift:1934.

In `@Sources/TerminalController`+MobileChat.swift:
- Around line 46-49: Extract the repeated error message "Agent chat transcript
service is not configured" into a static computed property in
TerminalController+MobileChat.swift following the pattern of
chatTerminalBindingErrorMessage, and ensure it returns a localized string.
Update all three occurrences of this error message in the guard statements that
check for agentChatTranscriptService (at the anchor location in lines 46-49, and
at the sibling locations in lines 56-58 and lines 129-131) to use this new
static property instead of the hardcoded string. Then add the localization key
mobile.chat.error.serviceNotConfigured to Resources/Localizable.xcstrings with
English and Japanese translations to complete the localization.
🪄 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: 59ecd03a-254d-4bd8-ac12-11a11bfb28a0

📥 Commits

Reviewing files that changed from the base of the PR and between 4557975 and 25ba2e6.

📒 Files selected for processing (6)
  • Packages/CmuxAgentChat/Sources/CmuxAgentChat/Parsing/OSC133CommandParser.swift
  • Packages/CmuxAgentChat/Tests/CmuxAgentChatTests/OSC133CommandParserTests.swift
  • Sources/AppDelegate.swift
  • Sources/Mobile/AgentChat/AgentChatTranscriptService.swift
  • Sources/TerminalController+MobileChat.swift
  • Sources/TerminalController.swift

Comment on lines +56 to +58
/// - Parameter adoptDetectedAgentSessions: Composition-root callback that
/// adopts a title-detected agent for the workspace whose title changed.
func start(adoptDetectedAgentSessions: @escaping @MainActor (String) -> Void) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify parameter semantics at callsites and downstream handlers.

set -euo pipefail

echo "== start(adoptDetectedAgentSessions:) callsites =="
rg -n -C3 '\bstart\s*\(\s*adoptDetectedAgentSessions\s*:' Sources/AppDelegate.swift Sources/TerminalController.swift Sources/TerminalController+MobileChat.swift || true

echo
echo "== closure parameter naming/usage around adoptDetectedAgentSessions =="
rg -n -C3 '\badoptDetectedAgentSessions\b' Sources/AppDelegate.swift Sources/TerminalController.swift Sources/TerminalController+MobileChat.swift || true

echo
echo "== workspace/tab resolution APIs likely used by the closure =="
rg -n -C3 '\bworkspace\b|\btabId\b|workspaceID|tabManagerFor\s*\(' Sources/AppDelegate.swift Sources/TerminalController.swift Sources/TerminalController+MobileChat.swift || true

Repository: manaflow-ai/cmux

Length of output: 50373


🏁 Script executed:

sed -n '50,90p' Sources/Mobile/AgentChat/AgentChatTranscriptService.swift

Repository: manaflow-ai/cmux

Length of output: 1930


Fix tab ID vs workspace ID parameter mismatch in agent adoption callback.

Line 81 passes tabId.uuidString to the adoptDetectedAgentSessions callback, but the method signature (line 56–57) and downstream handler (TerminalController.adoptDetectedAgentSessions(workspaceID:) at AppDelegate.swift:1934) expect a workspace ID. When the callback invokes mobileResolveWorkspaceAndSurface with ["workspace_id": tabId], the surface ID will not match any workspace UUID, causing title-detected agent adoption to fail silently. Either extract the workspace ID from the title-change notification (if available) before invoking the callback, or rename the callback parameter and all downstream handlers to reflect surface-ID semantics and convert appropriately at the composition root.

🤖 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 `@Sources/Mobile/AgentChat/AgentChatTranscriptService.swift` around lines 56 -
58, The adoptDetectedAgentSessions callback expects a workspace ID based on its
parameter naming and downstream handler
TerminalController.adoptDetectedAgentSessions(workspaceID:), but at line 81 a
tabId.uuidString is being passed instead. This causes a mismatch where the
surface ID won't correlate with any workspace UUID. Fix this by either: (1)
extracting the actual workspace ID from the title-change notification before
invoking adoptDetectedAgentSessions callback and passing that workspace ID
instead of tabId.uuidString, or (2) renaming the callback parameter from its
current name to reflect surface-ID semantics throughout the chain (the start
method signature, the callback parameter, the
TerminalController.adoptDetectedAgentSessions method parameter, and how it is
used in mobileResolveWorkspaceAndSurface) and ensure proper conversion happens
at the composition root in AppDelegate.swift:1934.

Comment thread Sources/TerminalController+MobileChat.swift
Comment thread Sources/ContentView.swift
Comment on lines 2649 to 2652
view = AnyView(view.onReceive(NotificationCenter.default.publisher(for: .ghosttyDidSetTitle)) { notification in
guard let tabId = notification.userInfo?[GhosttyNotificationKey.tabId] as? UUID,
tabId == tabManager.selectedTabId else { return }
guard GhosttyTitleChange(notification: notification)?.tabId == tabManager.selectedTabId else { return }
scheduleTitlebarTextRefresh()
})

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 The optional-chain comparison introduces a nil == nil case that the original code did not have. tabManager.selectedTabId is UUID?, so when the failable GhosttyTitleChange.init returns nil (malformed notification) and selectedTabId is also nil (no tab selected), the guard passes and scheduleTitlebarTextRefresh() is called incorrectly. The original code used a non-optional tabId: UUID on the left-hand side, making UUID == nil always false. Use a guard let to preserve the original semantics.

Suggested change
view = AnyView(view.onReceive(NotificationCenter.default.publisher(for: .ghosttyDidSetTitle)) { notification in
guard let tabId = notification.userInfo?[GhosttyNotificationKey.tabId] as? UUID,
tabId == tabManager.selectedTabId else { return }
guard GhosttyTitleChange(notification: notification)?.tabId == tabManager.selectedTabId else { return }
scheduleTitlebarTextRefresh()
})
view = AnyView(view.onReceive(NotificationCenter.default.publisher(for: .ghosttyDidSetTitle)) { notification in
guard let change = GhosttyTitleChange(notification: notification),
change.tabId == tabManager.selectedTabId else { return }
scheduleTitlebarTextRefresh()
})

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

♻️ Duplicate comments (1)
Sources/Mobile/AgentChat/AgentChatTranscriptService.swift (1)

52-69: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix callback identifier contract mismatch (workspace ID vs tab/surface ID).

At Line 68, change.tabId.uuidString is passed into a callback documented and wired as workspace-scoped (adoptDetectedAgentSessions(workspaceID:) in AppDelegate/TerminalController). This can make title-triggered adoption no-op for the intended workspace. Align the contract end-to-end: either pass an actual workspace ID here, or rename/retype the callback as tab/surface-scoped and resolve workspace in the downstream handler before adoption.

🤖 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 `@Sources/Mobile/AgentChat/AgentChatTranscriptService.swift` around lines 52 -
69, The observeAgentTitleChanges function is passing change.tabId.uuidString to
the adoptDetectedAgentSessions callback, but the callback is contract-defined to
accept a workspace ID, not a tab/surface ID. This mismatch causes
title-triggered adoption to fail to work correctly for the intended workspace.
Fix this by either: (1) determining the actual workspace ID from the tab ID
within observeAgentTitleChanges and passing that workspace ID instead of the tab
ID, or (2) if changing the callback contract is preferred, rename the callback
parameter and update the downstream handler (adoptDetectedAgentSessions in
AppDelegate/TerminalController) to accept a tab/surface ID and resolve the
workspace ID there before performing the adoption.
🤖 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 `@cmux.xcodeproj/project.pbxproj`:
- Around line 459-460: The object IDs for GhosttyTitleChange.swift
(A11EAE000000000000000000 and A11EAE000000000000000001) and
GhosttyTitleChangeSubscription.swift (A11EAF000000000000000000 and
A11EAF000000000000000001) collide with IDs already used elsewhere in the
.pbxproj file, which can corrupt project wiring. Regenerate two pairs of fresh,
globally unique IDs to replace these colliding identifiers, then update every
reference to them throughout the project file: the two PBXBuildFile entries for
these files, their corresponding PBXFileReference entries, their references in
the Sources group children, and their entries in the PBXSourcesBuildPhase build
phase. Ensure all four IDs (two new PBXBuildFile IDs and two new
PBXFileReference IDs) are used consistently and uniquely across the entire
project structure at all affected sites.

---

Duplicate comments:
In `@Sources/Mobile/AgentChat/AgentChatTranscriptService.swift`:
- Around line 52-69: The observeAgentTitleChanges function is passing
change.tabId.uuidString to the adoptDetectedAgentSessions callback, but the
callback is contract-defined to accept a workspace ID, not a tab/surface ID.
This mismatch causes title-triggered adoption to fail to work correctly for the
intended workspace. Fix this by either: (1) determining the actual workspace ID
from the tab ID within observeAgentTitleChanges and passing that workspace ID
instead of the tab ID, or (2) if changing the callback contract is preferred,
rename the callback parameter and update the downstream handler
(adoptDetectedAgentSessions in AppDelegate/TerminalController) to accept a
tab/surface ID and resolve the workspace ID there before performing the
adoption.
🪄 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: 1dd59aff-3c7a-44e9-a8c8-3e44cf398e24

📥 Commits

Reviewing files that changed from the base of the PR and between 00d6338 and ec753ba.

📒 Files selected for processing (7)
  • Sources/ContentView.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/GhosttyTitleChange.swift
  • Sources/GhosttyTitleChangeSubscription.swift
  • Sources/Mobile/AgentChat/AgentChatTranscriptService.swift
  • Sources/TabManager.swift
  • cmux.xcodeproj/project.pbxproj

Comment thread cmux.xcodeproj/project.pbxproj Outdated

This branch was successfully deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant