Skip to content

Improve Claude GUI mode detection for iOS - #7067

Merged
azooz2003-bit merged 52 commits into
mainfrom
feat-ios-claude-gui-detect
Jul 4, 2026
Merged

azooz2003-bit merged 52 commits into
mainfrom
feat-ios-claude-gui-detect

Conversation

@azooz2003-bit

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

Copy link
Copy Markdown
Collaborator

Summary

  • Detect wrapper-hosted Claude sessions for iOS GUI mode by reading argv/env details only after the process classifier confirms the process is Claude.
  • Surface fresh idle Claude prompts that have no CLAUDE_CODE_SESSION_ID, --session-id, or --resume yet by creating a cmux-owned pending session keyed to the terminal surface.
  • Route later same-surface Claude hook events into that pending record, preserving the pending row until real hook/store metadata can fill transcript/state.
  • Preserve the real Claude hook-store lookup id when a pending alias is canonicalized, so later refreshes read the real hook-store entry instead of the pending id.
  • Push versioned session_removed frames for retired aliases, and make iOS tombstone only versioned removals so old unversioned removes cannot hide future descriptors permanently.

Why

Codex rediscovery was reliable because Codex usually has an agent-named executable and an open rollout JSONL. Claude often appears as node, bun, or a versioned launcher; a brand-new idle claude prompt can also have no real session id in argv/env until the first hook fires. The observer now handles both paths without treating stale hook-store PIDs as live truth.

Tests

  • python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv
  • python3 scripts/check-test-determinism.py --strict
  • git diff --check
  • swift test --package-path Packages/Shared/CmuxAgentChat --filter 'ChatSessionListReducerTests|TerminalWireCodableTests'
  • xcodebuild test -project cmux.xcodeproj -scheme cmux-unit -destination 'platform=macOS' -derivedDataPath /tmp/cmux-iosgui-removal-tests6 -only-testing:cmuxTests/AgentChatSessionRegistryObservationTests -only-testing:cmuxTests/AgentChatSessionRegistryLifecycleTests

Reviews and CI

  • $autoreview: clean on head 3e8f3ea5a68563abc3a3febe10c3b8ac6fec5a5a.
  • cmux policy check: clean.
  • GitHub checks: green, 36 passed / 0 pending / 0 failed after rerunning an infrastructure-cancelled iOS routing job.
  • Greptile latest summary: safe to merge, minor non-blocking comments only.

Reloads

  • macOS tag: guifix, built via ./scripts/reload-cloud.sh --tag guifix with local fallback because no cloud builder slot was configured.
  • iOS simulator tag: guifix, built, installed, launched, signed in, and auto-paired on iPhone 17 simulator.
  • iPhone/iPad device reload: unavailable locally; devicectl reports Abdulaziz’s iPad, Aziz, and MyPhone as unavailable.
  • Web dev server: blocked locally because web/scripts/db-local.sh requires docker, and docker is not installed on this machine.

Notes

  • No user-facing strings changed.
  • The Claude trust warning seen in dogfood is separate from cmux detection: it can break Claude tool/permission behavior because .claude/settings.json allow entries are ignored until the project is trusted, but it is not the root cause of the GUI session detection bug fixed here.

@vercel

vercel Bot commented Jun 29, 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 Jul 3, 2026 7:24pm
cmux-staging Building Building Preview, Comment Jul 3, 2026 7:24pm

@coderabbitai

coderabbitai Bot commented Jun 29, 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

ObservedAgentSession gains workingDirectory, scan logic moves into a new overload-backed file, session records now carry and backfill that field, and mobile chat session listing now awaits process discovery before returning results. Tests and Xcode wiring cover the new scan path.

Changes

workingDirectory propagation through agent session scanning

Layer / File(s) Summary
ObservedAgentSession workingDirectory field
Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift
ObservedAgentSession gains an optional workingDirectory property.
AgentChatSessionRegistry+ObserveScan: scan overload and helpers
Sources/Mobile/AgentChat/AgentChatSessionRegistry+ObserveScan.swift
New file adds scanObservedAgentSessions, codingAgentDefinition, observedWorkingDirectory, sessionIDFromArguments, and UUID-like parsing helpers.
scanObservedAgentSessions wrapper and record update
Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift
The existing scanner delegates to the new overload; new records set workingDirectory; existing records backfill it when missing; local helper logic is removed; observeAgentProcesses removes throttling and continues with off-main scan plus main-actor apply.
mobile.chat.sessions awaits discovery
Sources/TerminalController+MobileChat.swift
v2MobileChatSessions becomes async, and the agent-process observation step is awaited before the session list is filtered and encoded.
Claude, Codex, and wiring tests
cmuxTests/AgentChatSessionRegistryObservationTests.swift, cmux.xcodeproj/project.pbxproj
Tests cover Claude and Codex scan outcomes, ignored inherited-environment and skipped non-agent cases, the shared test helper, and Xcode project wiring for the new source and test files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hop through env paths, soft and keen,
Finding where each agent’s been.
I await the scan, no race in sight,
And tuck the working dir in just right.
Codex, Claude, I’ve got your trail!

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: improved Claude GUI mode detection on iOS.
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 Changed scan helpers are nonisolated/Sendable, and UI-bound AgentChat/TerminalController types remain main-actor isolated.
Cmux Swift Blocking Runtime ✅ Passed No new semaphores, sleeps, sync waits, or locks were introduced; the new scan uses async/await and Task.detached, with blocking-like behavior only in test scaffolding.
Cmux Browser Automation Off-Main ✅ Passed PR only changes mobile chat observer/session code and tests; it doesn't touch browser.* routing, worker policy, or WebKit/AppKit wait paths.
Cmux Expensive Synchronous Load ✅ Passed The new session-list path awaits an off-main detached process scan; no expensive history/transcript loader was moved onto MainActor or the socket handler.
Cmux Cache Substitution Correctness ✅ Passed No fresh authoritative read was replaced by a cache/opportunistic value; the new observer still captures a fresh process snapshot and sessions await it before use.
Cmux No Hacky Sleeps ✅ Passed Touched files are Swift/tests/project only; no non-Swift runtime code introduced fixed sleeps, timers, or polling.
Cmux Algorithmic Complexity ✅ Passed The new observe scan is a single pass over cmuxScopedProcesses with Set dedup; no nested full-collection rescans or repeated hot-path sorting were introduced.
Cmux Swift Concurrency ✅ Passed The diff only adds a backfill guard in AgentChatSessionRegistry.swift; the async scan/await code was already in base, and no new legacy async patterns were introduced.
Cmux Swift @Concurrent ✅ Passed No changed nonisolated async helper lacks @concurrent; the heavy scan hops via Task.detached and the awaited caller is @MainActor UI-bound.
Cmux Swift File And Package Boundaries ✅ Passed The PR adds a 134-line focused scan helper and test code; the oversized registry file shrank, and no file mixes unrelated responsibilities or obviously belongs in a package boundary.
Cmux Swiftpm Lockfiles ✅ Passed Package.swift only rewires test deps; no external pins or Xcode SwiftPM refs changed, and the lone Package.resolved ignore is vendored.
Cmux Swift Logging ✅ Passed No forbidden logging was added in the touched Swift files; the only runtime logs are #if DEBUG cmuxDebugLog calls, which the rule allows.
Cmux User-Facing Error Privacy ✅ Passed Touched code is internal process-scanning/plumbing only; no new user-facing error, alert, command-output, or recovery copy exposes forbidden details.
Cmux Full Internationalization ✅ Passed The diff only changes internal Swift logic/tests and debug logs; no user-facing copy, localization APIs, or i18n catalog/message files were added or edited.
Cmux Swiftui State Layout ✅ Passed No SwiftUI state/layout patterns were introduced; the diff only changes process-scanning, controller, and test code, with no ObservableObject/@Published/GeometryReader/lazy-row store use.
Cmux Architecture Rethink ✅ Passed PASS: the PR keeps one owner (AgentChatSessionRegistry), adds only an on-demand process-table scan, and avoids new timers/observers/flags; tests cover the bridge.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: the Swift diff only touches agent-session detection/session-list code and tests; no NSWindow/NSPanel/WindowGroup/WindowController or cmuxAuxiliaryWindowIdentifiers changes.
Cmux Source Artifacts ✅ Passed Changed paths are handwritten Swift source, a test file, and pbxproj entries; no logs, caches, temp dirs, or other artifacts appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No new test/debug seam was added in Sources; the extracted scanner is used by production observeAgentProcesses(), and no new debug*/ForTesting accessor appears in shipping code.
Cmux No Ambient Global State ✅ Passed No new ambient global surface: added behavior lives on existing types, and the only new helpers are private static methods inside an extension.
Description check ✅ Passed The description covers summary, rationale, tests, and rollout notes, though it omits the template’s demo video, review trigger, and checklist sections.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-ios-claude-gui-detect

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 Jun 29, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR improves Claude GUI mode detection for iOS by teaching the observe-floor scanner to discover wrapper-hosted Claude sessions (node, bun, versioned launchers) and to surface fresh idle Claude prompts that have no session ID yet, via a pending alias keyed to the terminal surface. Hook events are routed into the pending record until real identity arrives, and the iOS tombstone logic is tightened so unversioned session_removed frames cannot permanently block future descriptors.

  • The observe-floor scanner is refactored into three new extension files (+Lifecycle, +LiveAgentPID, +ObserveScan), adding argv/env-based agent classification, pending-claude session creation, fork-launch exclusion, and a versioned session_removed wire event with a correct tombstone strategy in ChatSessionListReducer.
  • AgentChatSessionRecord gains endedAt, hookStoreSessionID, and adoptMissingBindings to preserve hook-store identity under pending aliases; AgentChatTranscriptService adds shouldListEndedSession backed by a 5-second-retry AgentChatEndedTranscriptListabilityCache to keep ended sessions without readable transcripts out of the iOS list.
  • v2MobileChatSessions becomes async and awaits a coalesced observe-floor scan (up to 750 ms) before returning the list, replacing the old fire-and-forget kick.

Confidence Score: 5/5

Safe to merge; the core detect-and-route logic is well-tested and the two findings are non-blocking quality nits.

The detection pipeline, pending-alias lifecycle, tombstone fix, and wire protocol changes are all covered by the new test suites. No correctness-critical paths have missing guards or wrong state transitions. The two findings are a per-call encoder allocation regression and internal visibility on test-injectable helper overloads — neither affects runtime correctness.

AgentChatSessionRegistry+LiveAgentPID.swift and AgentChatSessionRegistry+ObserveScan.swift — injectable-closure overloads that were private in the old file are now internal, consistent with a pattern flagged in a previous review round that remains unresolved.

Important Files Changed

Filename Overview
Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift Major refactor: splits observation logic into extension files, adds liveClaudeSessionIDsBySurfaceID index, pending-alias routing via canonicalClaudeSessionID, onRecordRemoved callback, and removes old private liveAgentPID/scan/uuid helpers (deprivated into new files).
Sources/Mobile/AgentChat/AgentChatSessionRegistry+ObserveScan.swift New file housing observe-floor scan logic; adds process classifier, pending-claude session surfacing, fork-launch exclusion, UUID extraction, and codex rollout path lookup — several helpers deprivated from the old registry (firstUUIDLike, openCodexRolloutPath, sessionIDFromArguments) now internal rather than private.
Sources/Mobile/AgentChat/AgentChatSessionRegistry+LiveAgentPID.swift New file with liveAgentPID overloads, process-tree depth, surface root PIDs, and pending-claude ID helpers — test-injectable overloads (processArgumentsAndEnvironment closure variants) are internal without private, shipping as test seams in release builds.
Sources/Mobile/AgentChat/AgentChatTranscriptService.swift Adds injectable hasEventSubscribers/emitEventPayload/now closures for testability, wires onRecordRemoved, adds observeAgentProcessesForListing and shouldListEndedSession, and integrates AgentChatEndedTranscriptListabilityCache.
Sources/Mobile/AgentChat/AgentChatTranscriptService+Wire.swift Splits wirePayload and descriptorChangedMeaningfully into a separate extension file but introduces per-call ChatWireCoding() allocation (old code cached the encoder as a stored property).
Packages/Shared/CmuxAgentChat/Sources/CmuxAgentChat/Store/ChatSessionListReducer.swift Now stateful (mutating applying); adds removedVersionBySessionID tombstone map with correct Int.max guard for unversioned session_removed frames.
Sources/TerminalController+MobileChat.swift v2MobileChatSessions is now async and awaits observeAgentProcessesForListing (up to 750 ms) before returning the list; adds ended-session transcript filter and scoped surface-ID observation.
Sources/Mobile/AgentChat/AgentChatEndedTranscriptListabilityCache.swift New cache tracking whether ended sessions have readable transcripts, with a 5-second retry window, preventing phantom ended-session rows in the iOS list.
Sources/Mobile/AgentChat/AgentChatSessionRecord.swift Adds endedAt, hookStoreSessionID, hookStoreLookupSessionID, rememberHookStoreSessionID, and adoptMissingBindings to preserve hook-store identity under pending aliases.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Process scan Task.detached] -->|ObservedAgentSession list| B[applyObservedSessions MainActor]
    B --> C{sessionID resolved?}
    C -->|real UUID env/argv/rollout| D[canonicalClaudeSessionID]
    C -->|no session id idle claude| E[pendingClaudeSessionID surfaceID]
    D --> F{pending alias on surface?}
    F -->|yes| G[Route into pending record hookStoreSessionID=realID]
    F -->|no| H[New record under real ID]
    E --> I[New pending record state=idle]
    G --> J[Hook event fires noteHookEvent]
    H --> J
    I --> J
    J --> K{ended and no transcript?}
    K -->|yes| L[emit session_removed versioned]
    K -->|no| M[emit descriptorChanged or stateChanged]
    L --> N[ChatSessionListReducer removes row no tombstone]
    M --> O[iOS session list updated]
    N --> P{future descriptorChanged version gt removed?}
    P -->|yes| Q[tombstone cleared row re-added]
    P -->|no| R[row stays hidden]
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
    A[Process scan Task.detached] -->|ObservedAgentSession list| B[applyObservedSessions MainActor]
    B --> C{sessionID resolved?}
    C -->|real UUID env/argv/rollout| D[canonicalClaudeSessionID]
    C -->|no session id idle claude| E[pendingClaudeSessionID surfaceID]
    D --> F{pending alias on surface?}
    F -->|yes| G[Route into pending record hookStoreSessionID=realID]
    F -->|no| H[New record under real ID]
    E --> I[New pending record state=idle]
    G --> J[Hook event fires noteHookEvent]
    H --> J
    I --> J
    J --> K{ended and no transcript?}
    K -->|yes| L[emit session_removed versioned]
    K -->|no| M[emit descriptorChanged or stateChanged]
    L --> N[ChatSessionListReducer removes row no tombstone]
    M --> O[iOS session list updated]
    N --> P{future descriptorChanged version gt removed?}
    P -->|yes| Q[tombstone cleared row re-added]
    P -->|no| R[row stays hidden]
Loading

Reviews (33): Last reviewed commit: "Address agent chat policy review fixes" | Re-trigger Greptile

Comment thread Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift Outdated
Comment thread Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift Outdated

@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/AgentChatSessionRegistry.swift`:
- Line 262: The workingDirectory assignment in AgentChatSessionRegistry should
not fall back to generic PWD because it can be stale or spoofed and then gets
persisted as authoritative state. Update observedWorkingDirectory (and the
related uses around SessionRegistry persistence) to source the path only from
CMUX_AGENT_LAUNCH_CWD, or introduce a separate trusted process-cwd source and
fail closed when that trusted value is missing. Ensure the same rule is applied
consistently wherever workingDirectory is recorded or preserved.
- Around line 250-253: Restrict the argv-based session ID fallback in
AgentChatSessionRegistry so it only runs for Claude and not for Codex-derived
identities. In the session lookup logic around sessionIDFromArguments(_:), keep
the existing rollout-path source as the authoritative identity source, and if
that is unavailable for Codex, fail closed instead of parsing
details?.arguments. Update the conditional that currently falls back when
sessionID is nil so it checks the agent identity first, preserves argv parsing
for Claude only, and avoids binding arbitrary UUID-like argv values to Codex
sessions.
🪄 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: a94ffb57-4218-426e-bc76-2888352b3b4f

📥 Commits

Reviewing files that changed from the base of the PR and between 0eb308e and cb9601a.

📒 Files selected for processing (2)
  • Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift
  • cmuxTests/TaskManagerResourcesTests.swift

Comment thread Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift Outdated
Comment thread Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift Outdated

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift (1)

124-143: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use the detail-aware classifier for live Claude PID recovery.

Line 135 still matches descendants with empty argv/env, so a live node/bun-hosted Claude process that the new scanner can detect will not be found here. That can leave observed Claude sessions without a live PID or mark them stale during exit/liveness recovery.

Suggested fix
             guard let info = snapshot.process(pid: pid),
-                  let def = CmuxTaskManagerCodingAgentDefinition.matchingDefinition(
-                      processName: info.name,
-                      processPath: info.path,
-                      arguments: [],
-                      environment: [:]
+                  let def = codingAgentDefinition(
+                      for: info,
+                      processArgumentsAndEnvironment: CmuxTopProcessSnapshot.processArgumentsAndEnvironment(for:)
                   ),
                   def.id == wantedID else { continue }

As per path instructions, correctness-critical agent/session identity should use a reliable structured source instead of process-name-only detection.

🤖 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/AgentChatSessionRegistry.swift` around lines 124 -
143, The live PID recovery in AgentChatSessionRegistry.liveAgentPID still uses
process-name-only matching with empty arguments/environment, so it can miss
Claude processes that the detail-aware scanner recognizes. Update this lookup to
use the same detail-aware classifier/structured identity source as the new
scanner when iterating snapshot.expandedPIDs, and keep matching against
ChatAgentKind.sourceName only after obtaining a structured agent match.

Source: Path instructions

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

Outside diff comments:
In `@Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift`:
- Around line 124-143: The live PID recovery in
AgentChatSessionRegistry.liveAgentPID still uses process-name-only matching with
empty arguments/environment, so it can miss Claude processes that the
detail-aware scanner recognizes. Update this lookup to use the same detail-aware
classifier/structured identity source as the new scanner when iterating
snapshot.expandedPIDs, and keep matching against ChatAgentKind.sourceName only
after obtaining a structured agent match.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9a0434fc-fb2b-4dc0-a7e3-3bd9bfb97cc2

📥 Commits

Reviewing files that changed from the base of the PR and between 4004b39 and 45ddba9.

📒 Files selected for processing (4)
  • Sources/Mobile/AgentChat/AgentChatSessionRegistry+ObserveScan.swift
  • Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/AgentChatSessionRegistryObservationTests.swift

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/Mobile/AgentChat/AgentChatSessionRegistry+ObserveScan.swift (1)

14-19: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Direct Codex detections still drop workingDirectory.

If a codex process is identified from its binary name/path and rollout file, loadDetails() never runs, so details stays nil and Line 50 always writes workingDirectory: nil. That means the new cwd preservation only works for sessions that already needed argv/env reads, while the direct-Codex path silently loses the launch cwd.

Proposed fix
-            guard let resolved = sessionID, !seen.contains(resolved) else { continue }
+            guard let resolved = sessionID, !seen.contains(resolved) else { continue }
+            let environment = loadDetails()?.environment
             seen.insert(resolved)
             result.append(ObservedAgentSession(
                 sessionID: resolved,
                 agentKind: ChatAgentKind(source: def.id),
                 surfaceID: surfaceID.uuidString,
                 workspaceID: process.cmuxWorkspaceID?.uuidString,
                 pid: process.pid,
-                workingDirectory: observedWorkingDirectory(details?.environment),
+                workingDirectory: observedWorkingDirectory(environment),
                 transcriptPath: transcriptPath
             ))

Also applies to: 44-50

🤖 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/AgentChatSessionRegistry`+ObserveScan.swift around
lines 14 - 19, The direct Codex detection path is skipping `loadDetails()`, so
`details` remains nil and `workingDirectory` is always written as nil in
`AgentChatSessionRegistry+ObserveScan`. Update the scan logic around the
`details` cache and the code that builds the session record so
`processArgumentsAndEnvironment(process.pid)` is also consulted for the Codex
binary-name/rollout-file path, then preserve the extracted cwd when constructing
the `workingDirectory` field.
🤖 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.

Outside diff comments:
In `@Sources/Mobile/AgentChat/AgentChatSessionRegistry`+ObserveScan.swift:
- Around line 14-19: The direct Codex detection path is skipping
`loadDetails()`, so `details` remains nil and `workingDirectory` is always
written as nil in `AgentChatSessionRegistry+ObserveScan`. Update the scan logic
around the `details` cache and the code that builds the session record so
`processArgumentsAndEnvironment(process.pid)` is also consulted for the Codex
binary-name/rollout-file path, then preserve the extracted cwd when constructing
the `workingDirectory` field.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8337c23c-c6e3-4ef0-bf48-7ec5832d3a26

📥 Commits

Reviewing files that changed from the base of the PR and between 45ddba9 and b0a7edb.

📒 Files selected for processing (2)
  • Sources/Mobile/AgentChat/AgentChatSessionRegistry+ObserveScan.swift
  • cmuxTests/AgentChatSessionRegistryObservationTests.swift

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift">

<violation number="1" location="Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift:200">
P1: The narrowed `allowsUnidentifiedClaudeLivenessFallback` may incorrectly end active canonical Claude sessions that run under wrapper processes without detectable session-ID markers. Previously all Claude sessions benefited from the unidentified liveness fallback; now only pending sessions do. When a watched wrapper PID exits and the underlying real agent lacks `--session-id`/`CLAUDE_CODE_SESSION_ID` markers, `liveAgentPID` returns nil and the session transitions to `.ended` even though the agent is still alive. Consider keeping the fallback active for canonical sessions that were originally discovered via wrapper args, or ensuring the real binary reliably carries the session markers so liveness can be matched via `expectedSessionIDs`.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

surfaceID: surfaceID,
kind: kind,
matchingSessionIDs: expectedSessionIDs,
allowUnidentifiedFallback: Self.allowsUnidentifiedClaudeLivenessFallback(for: record)

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: The narrowed allowsUnidentifiedClaudeLivenessFallback may incorrectly end active canonical Claude sessions that run under wrapper processes without detectable session-ID markers. Previously all Claude sessions benefited from the unidentified liveness fallback; now only pending sessions do. When a watched wrapper PID exits and the underlying real agent lacks --session-id/CLAUDE_CODE_SESSION_ID markers, liveAgentPID returns nil and the session transitions to .ended even though the agent is still alive. Consider keeping the fallback active for canonical sessions that were originally discovered via wrapper args, or ensuring the real binary reliably carries the session markers so liveness can be matched via expectedSessionIDs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift, line 200:

<comment>The narrowed `allowsUnidentifiedClaudeLivenessFallback` may incorrectly end active canonical Claude sessions that run under wrapper processes without detectable session-ID markers. Previously all Claude sessions benefited from the unidentified liveness fallback; now only pending sessions do. When a watched wrapper PID exits and the underlying real agent lacks `--session-id`/`CLAUDE_CODE_SESSION_ID` markers, `liveAgentPID` returns nil and the session transitions to `.ended` even though the agent is still alive. Consider keeping the fallback active for canonical sessions that were originally discovered via wrapper args, or ensuring the real binary reliably carries the session markers so liveness can be matched via `expectedSessionIDs`.</comment>

<file context>
@@ -197,7 +197,7 @@ final class AgentChatSessionRegistry {
                 kind: kind,
                 matchingSessionIDs: expectedSessionIDs,
-                allowUnidentifiedFallback: kind == .claude
+                allowUnidentifiedFallback: Self.allowsUnidentifiedClaudeLivenessFallback(for: record)
             )
             await MainActor.run { [weak self] in
</file context>
Suggested change
allowUnidentifiedFallback: Self.allowsUnidentifiedClaudeLivenessFallback(for: record)
allowUnidentifiedFallback: kind == .claude

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 11 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift">

<violation number="1" location="Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift:200">
P1: The narrowed `allowsUnidentifiedClaudeLivenessFallback` may incorrectly end active canonical Claude sessions that run under wrapper processes without detectable session-ID markers. Previously all Claude sessions benefited from the unidentified liveness fallback; now only pending sessions do. When a watched wrapper PID exits and the underlying real agent lacks `--session-id`/`CLAUDE_CODE_SESSION_ID` markers, `liveAgentPID` returns nil and the session transitions to `.ended` even though the agent is still alive. Consider keeping the fallback active for canonical sessions that were originally discovered via wrapper args, or ensuring the real binary reliably carries the session markers so liveness can be matched via `expectedSessionIDs`.</violation>
</file>

<file name="Sources/Mobile/AgentChat/AgentChatEndedTranscriptListabilityCache.swift">

<violation number="1" location="Sources/Mobile/AgentChat/AgentChatEndedTranscriptListabilityCache.swift:19">
P1: Negative cache entries can become permanently stale because `boundedTranscriptPath` checks live filesystem state (`fileExists`), which can change even when record fields haven't. Once an ended session is cached as unreadable, the early return on `readableBySessionID != nil` prevents ever re-checking the filesystem, so the session may never appear in the transcript list even after its file is written. Consider only short-circuiting when the cached value is already `true` (positive cache), or include an invalidation path for negative entries so transcript availability is eventually re-evaluated.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 8 files (changes from recent commits).

You’re at about 97% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/Mobile/AgentChat/AgentChatTranscriptService+Wire.swift">

<violation number="1" location="Sources/Mobile/AgentChat/AgentChatTranscriptService+Wire.swift:18">
P2: Wire payload construction fails closed with silent `nil`, risking undetected dropped fan-out events on encoding or schema mismatch. The `wirePayload` helper suppresses all encoding and JSON-shape failures with `try?` and returns `nil` without any diagnostics. Because this helper is explicitly used to create event fan-out payloads, a schema mismatch or caller passing an `Encodable` that does not serialize to a top-level object can cause outbound updates to be dropped silently. At the call site in `AgentChatTranscriptService.emit(frame:)`, a `nil` payload causes the frame to be silently discarded. In `TerminalController+MobileChat.v2MobileChatSessions`, descriptors that fail to encode are silently filtered out via `compactMap` with no non-debug logging. Even if failures are rare today, the silent suppression removes any signal when a regression does occur, making mobile transcript/session state desyncs hard to detect and debug. Consider surfacing encoding failures via at least debug+assert logging inside `wirePayload`, or propagating the error to callers so they can log or handle it appropriately.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

/// event fan-out expects.
func wirePayload<T: Encodable>(_ value: T) -> [String: Any]? {
let coding = ChatWireCoding()
guard let data = try? coding.encode(value),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Wire payload construction fails closed with silent nil, risking undetected dropped fan-out events on encoding or schema mismatch. The wirePayload helper suppresses all encoding and JSON-shape failures with try? and returns nil without any diagnostics. Because this helper is explicitly used to create event fan-out payloads, a schema mismatch or caller passing an Encodable that does not serialize to a top-level object can cause outbound updates to be dropped silently. At the call site in AgentChatTranscriptService.emit(frame:), a nil payload causes the frame to be silently discarded. In TerminalController+MobileChat.v2MobileChatSessions, descriptors that fail to encode are silently filtered out via compactMap with no non-debug logging. Even if failures are rare today, the silent suppression removes any signal when a regression does occur, making mobile transcript/session state desyncs hard to detect and debug. Consider surfacing encoding failures via at least debug+assert logging inside wirePayload, or propagating the error to callers so they can log or handle it appropriately.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/Mobile/AgentChat/AgentChatTranscriptService+Wire.swift, line 18:

<comment>Wire payload construction fails closed with silent `nil`, risking undetected dropped fan-out events on encoding or schema mismatch. The `wirePayload` helper suppresses all encoding and JSON-shape failures with `try?` and returns `nil` without any diagnostics. Because this helper is explicitly used to create event fan-out payloads, a schema mismatch or caller passing an `Encodable` that does not serialize to a top-level object can cause outbound updates to be dropped silently. At the call site in `AgentChatTranscriptService.emit(frame:)`, a `nil` payload causes the frame to be silently discarded. In `TerminalController+MobileChat.v2MobileChatSessions`, descriptors that fail to encode are silently filtered out via `compactMap` with no non-debug logging. Even if failures are rare today, the silent suppression removes any signal when a regression does occur, making mobile transcript/session state desyncs hard to detect and debug. Consider surfacing encoding failures via at least debug+assert logging inside `wirePayload`, or propagating the error to callers so they can log or handle it appropriately.</comment>

<file context>
@@ -0,0 +1,24 @@
+    /// event fan-out expects.
+    func wirePayload<T: Encodable>(_ value: T) -> [String: Any]? {
+        let coding = ChatWireCoding()
+        guard let data = try? coding.encode(value),
+              let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
+            return nil
</file context>

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 3 files (changes from recent commits).

You’re at about 98% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/Mobile/AgentChat/AgentChatSessionRegistry+ObserveScan.swift">

<violation number="1" location="Sources/Mobile/AgentChat/AgentChatSessionRegistry+ObserveScan.swift:286">
P2: The inline `explicitSessionOption` check duplicates the option-grammar already recognized by `sessionIDFromArguments`. If the grammar is ever extended, both sites must be updated or pending-session detection will diverge. Extract a shared helper (or reuse the parser) so the grammar lives in one place.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Sources/Mobile/AgentChat/AgentChatSessionRegistry+ObserveScan.swift Outdated

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 5 files (changes from recent commits).

You’re at about 98% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift">

<violation number="1" location="Sources/Mobile/AgentChat/AgentChatSessionRegistry.swift:200">
P1: The narrowed `allowsUnidentifiedClaudeLivenessFallback` may incorrectly end active canonical Claude sessions that run under wrapper processes without detectable session-ID markers. Previously all Claude sessions benefited from the unidentified liveness fallback; now only pending sessions do. When a watched wrapper PID exits and the underlying real agent lacks `--session-id`/`CLAUDE_CODE_SESSION_ID` markers, `liveAgentPID` returns nil and the session transitions to `.ended` even though the agent is still alive. Consider keeping the fallback active for canonical sessions that were originally discovered via wrapper args, or ensuring the real binary reliably carries the session markers so liveness can be matched via `expectedSessionIDs`.</violation>
</file>

<file name="Sources/Mobile/AgentChat/AgentChatTranscriptService+Wire.swift">

<violation number="1" location="Sources/Mobile/AgentChat/AgentChatTranscriptService+Wire.swift:18">
P2: Wire payload construction fails closed with silent `nil`, risking undetected dropped fan-out events on encoding or schema mismatch. The `wirePayload` helper suppresses all encoding and JSON-shape failures with `try?` and returns `nil` without any diagnostics. Because this helper is explicitly used to create event fan-out payloads, a schema mismatch or caller passing an `Encodable` that does not serialize to a top-level object can cause outbound updates to be dropped silently. At the call site in `AgentChatTranscriptService.emit(frame:)`, a `nil` payload causes the frame to be silently discarded. In `TerminalController+MobileChat.v2MobileChatSessions`, descriptors that fail to encode are silently filtered out via `compactMap` with no non-debug logging. Even if failures are rare today, the silent suppression removes any signal when a regression does occur, making mobile transcript/session state desyncs hard to detect and debug. Consider surfacing encoding failures via at least debug+assert logging inside `wirePayload`, or propagating the error to callers so they can log or handle it appropriately.</violation>
</file>

<file name="Sources/Mobile/AgentChat/AgentChatSessionRegistry+ObserveScan.swift">

<violation number="1" location="Sources/Mobile/AgentChat/AgentChatSessionRegistry+ObserveScan.swift:286">
P2: The inline `explicitSessionOption` check duplicates the option-grammar already recognized by `sessionIDFromArguments`. If the grammar is ever extended, both sites must be updated or pending-session detection will diverge. Extract a shared helper (or reuse the parser) so the grammar lives in one place.</violation>
</file>

<file name="Sources/Mobile/AgentChat/AgentChatSessionRecord.swift">

<violation number="1" location="Sources/Mobile/AgentChat/AgentChatSessionRecord.swift:83">
P2: The `adoptMissingBindings` method duplicates the same field-by-field adoption logic as `adoptBindings` with only a different overwrite policy. If a new binding field is added, both methods must be updated in lockstep or hydration behavior will diverge depending on which adoption path is used. Consider using a shared adoption primitive (for example, a single method with a propagation-policy parameter or a private field-by-field helper) so the field list is declared once.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

}

/// Fills gaps from the hook store without replacing live cmux bindings.
mutating func adoptMissingBindings(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The adoptMissingBindings method duplicates the same field-by-field adoption logic as adoptBindings with only a different overwrite policy. If a new binding field is added, both methods must be updated in lockstep or hydration behavior will diverge depending on which adoption path is used. Consider using a shared adoption primitive (for example, a single method with a propagation-policy parameter or a private field-by-field helper) so the field list is declared once.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/Mobile/AgentChat/AgentChatSessionRecord.swift, line 83:

<comment>The `adoptMissingBindings` method duplicates the same field-by-field adoption logic as `adoptBindings` with only a different overwrite policy. If a new binding field is added, both methods must be updated in lockstep or hydration behavior will diverge depending on which adoption path is used. Consider using a shared adoption primitive (for example, a single method with a propagation-policy parameter or a private field-by-field helper) so the field list is declared once.</comment>

<file context>
@@ -79,6 +79,19 @@ struct AgentChatSessionRecord: Sendable {
     }
 
+    /// Fills gaps from the hook store without replacing live cmux bindings.
+    mutating func adoptMissingBindings(
+        from entry: AgentChatHookSessionStore.Entry,
+        includingPID: Bool = true
</file context>

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 19 files (changes from recent commits).

You’re at about 98% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/Mobile/AgentChat/AgentChatSessionRegistry+LiveAgentPID.swift">

<violation number="1" location="Sources/Mobile/AgentChat/AgentChatSessionRegistry+LiveAgentPID.swift:85">
P2: `loadDetails()?.arguments` is evaluated eagerly for every non-root foreground PID before `codingAgentDefinition` runs, because Swift evaluates function arguments left-to-right. This means `processArgumentsAndEnvironment` is called even when the classifier's direct name/path match would succeed without reading argv, or when `shouldReadDetails` is `false` and argv is never needed. This contradicts the PR description's stated goal of "inspecting argv/env only after the process classifier confirms the process is Claude" and adds unnecessary per-process syscalls during tree scans. Consider deferring the argv read: either pass a lazy closure to `allowsLaunchKindEnvironment` or compute the wrapper check inside `codingAgentDefinition` after the `shouldReadDetails` gate.</violation>
</file>

<file name="cmuxTests/ClaudeHookFeedTelemetrySwiftTests.swift">

<violation number="1" location="cmuxTests/ClaudeHookFeedTelemetrySwiftTests.swift:337">
P2: The `runProcess` helper has a hang risk when the child process does not respond to `terminate()` (SIGTERM) within the 1-second grace period. After the timeout path fires, `readDataToEndOfFile()` blocks indefinitely because the process still holds the pipe write-ends open. If this edge case triggers, it stalls the entire test suite. Consider escalating to SIGKILL when the process still hasn't exited after the `terminate()` wait, and using nonblocking reads or closing the pipe write-ends to bound the drain. This follows the established pattern from `Ensure exit before draining pipes` guidance where the process must be confirmed dead before draining pipes.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

info.isTerminalForegroundProcessGroup,
let def = codingAgentDefinition(
for: info,
allowLaunchKindEnvironment: allowsLaunchKindEnvironment(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: loadDetails()?.arguments is evaluated eagerly for every non-root foreground PID before codingAgentDefinition runs, because Swift evaluates function arguments left-to-right. This means processArgumentsAndEnvironment is called even when the classifier's direct name/path match would succeed without reading argv, or when shouldReadDetails is false and argv is never needed. This contradicts the PR description's stated goal of "inspecting argv/env only after the process classifier confirms the process is Claude" and adds unnecessary per-process syscalls during tree scans. Consider deferring the argv read: either pass a lazy closure to allowsLaunchKindEnvironment or compute the wrapper check inside codingAgentDefinition after the shouldReadDetails gate.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/Mobile/AgentChat/AgentChatSessionRegistry+LiveAgentPID.swift, line 85:

<comment>`loadDetails()?.arguments` is evaluated eagerly for every non-root foreground PID before `codingAgentDefinition` runs, because Swift evaluates function arguments left-to-right. This means `processArgumentsAndEnvironment` is called even when the classifier's direct name/path match would succeed without reading argv, or when `shouldReadDetails` is `false` and argv is never needed. This contradicts the PR description's stated goal of "inspecting argv/env only after the process classifier confirms the process is Claude" and adds unnecessary per-process syscalls during tree scans. Consider deferring the argv read: either pass a lazy closure to `allowsLaunchKindEnvironment` or compute the wrapper check inside `codingAgentDefinition` after the `shouldReadDetails` gate.</comment>

<file context>
@@ -82,7 +82,11 @@ extension AgentChatSessionRegistry {
                   let def = codingAgentDefinition(
                       for: info,
-                      allowLaunchKindEnvironment: rootPIDs.contains(pid),
+                      allowLaunchKindEnvironment: allowsLaunchKindEnvironment(
+                          for: info,
+                          rootPIDs: rootPIDs,
</file context>

let timedOut = exitSignal.wait(timeout: .now() + timeout) == .timedOut
if timedOut {
process.terminate()
_ = exitSignal.wait(timeout: .now() + 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The runProcess helper has a hang risk when the child process does not respond to terminate() (SIGTERM) within the 1-second grace period. After the timeout path fires, readDataToEndOfFile() blocks indefinitely because the process still holds the pipe write-ends open. If this edge case triggers, it stalls the entire test suite. Consider escalating to SIGKILL when the process still hasn't exited after the terminate() wait, and using nonblocking reads or closing the pipe write-ends to bound the drain. This follows the established pattern from Ensure exit before draining pipes guidance where the process must be confirmed dead before draining pipes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cmuxTests/ClaudeHookFeedTelemetrySwiftTests.swift, line 337:

<comment>The `runProcess` helper has a hang risk when the child process does not respond to `terminate()` (SIGTERM) within the 1-second grace period. After the timeout path fires, `readDataToEndOfFile()` blocks indefinitely because the process still holds the pipe write-ends open. If this edge case triggers, it stalls the entire test suite. Consider escalating to SIGKILL when the process still hasn't exited after the `terminate()` wait, and using nonblocking reads or closing the pipe write-ends to bound the drain. This follows the established pattern from `Ensure exit before draining pipes` guidance where the process must be confirmed dead before draining pipes.</comment>

<file context>
@@ -0,0 +1,369 @@
+    let timedOut = exitSignal.wait(timeout: .now() + timeout) == .timedOut
+    if timedOut {
+        process.terminate()
+        _ = exitSignal.wait(timeout: .now() + 1)
+    }
+
</file context>

@azooz2003-bit
azooz2003-bit merged commit 5a7c147 into main Jul 4, 2026
37 of 38 checks passed
@azooz2003-bit
azooz2003-bit deleted the feat-ios-claude-gui-detect branch July 4, 2026 03:02
azooz2003-bit added a commit that referenced this pull request Jul 4, 2026
3 conflicts, resolved keeping HEAD's refactored structure:
- TaskManagerTypes.swift: kept HEAD (CmuxTaskManagerCodingAgentDefinition lives in
  the CMUXAgentLaunch package; main re-added it inline — dropped the dup).
- TerminalController+MobileChat.swift: kept HEAD's MobileChatRPCHandler delegation;
  main's inline v2MobileChat* methods moved to the handler in the refactor.
- project.pbxproj: union-dedup (#7067's new AgentChat scan/test files auto-merged
  and wired; 0 dangling refs, normalized, test-wiring lint green).

#7067's core detection work (observeAgentProcessesForListing, AgentChatSessionRegistry
scan files, tests) auto-merged cleanly. One divergent piece recorded for owner
reconcile (merge-deferred-gaps): whether MobileChatRPCHandler.sessions() adopts
#7067's observe-before-listing under HEAD's PR-6631 hook-event agent-session SoT.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
azooz2003-bit added a commit that referenced this pull request Jul 4, 2026
…/argumentNeedles public

#7067's app-target AgentChatSessionRegistry+ObserveScan reads these; the refactor
moved the struct into the CMUXAgentLaunch package and left them internal (main had
it app-target, same-module). Widen to public for the detection API.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – cmux — 59aa121e Deployed Jul 3, 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