Skip to content

Fix codex sessions restoring into the wrong surface after reload (#4920) - #5331

Closed
lawrencecchen wants to merge 5 commits into
mainfrom
fix-codex-surface-jumble
Closed

lawrencecchen wants to merge 5 commits into
mainfrom
fix-codex-surface-jumble

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 4, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Codex sessions get "jumbled" after reload: a codex session restores into the wrong surface/pane (with two codex sessions, the surfaces swap). cwd is correct in each (codex is .cwdInFile), so it looks like a coherent session in the wrong pane. Only reproduces via the codex-family CLI launchers (cmux omx / oh-my-codex, cmux codex-teams); a plain codex started in a shell is fine.

This is the surface-mapping axis, independent of the cwd-resolution work in #5300/#5312 (the directory axis).

Root cause (adversarially-verified)

configureTmuxCompatEnvironment (the shared launcher env builder) overwrote only CMUX_WORKSPACE_ID / CMUX_SURFACE_ID from the operator's globally-focused pane (via system.identify), while leaving CMUX_PANEL_ID / CMUX_TAB_ID at the launch surface. So a codex launched in surface B while surface A is focused got CMUX_SURFACE_ID=A but CMUX_PANEL_ID=B (desynced). That leaked surface id is then preferred over PID truth by the codex hook and persisted as a SurfaceResumeBinding (which has no live-pid gate), so the wrong surface survives reload. codex-teams leaked the same via its watcher CLI args.

The matched per-surface in-app env (TerminalStartupEnvironment.applyManagedCmuxContextEnvironment) sets all four ids from the surface's own id, which is why plain codex is unaffected.

GitHub issues: #4920 (source), #695 (symptom).

Fix

  • New shared value-struct CMUXAgentLaunch/AgentSpawnIdentity: prefer the launcher's own workspace/surface identity, falling back to the focused pane only when the launcher has none. Pure logic, 5 golden tests.
  • configureTmuxCompatEnvironment (omx / claude-teams / omo / omc): resolve via AgentSpawnIdentity from the launcher's own processEnvironment, so CMUX_SURFACE_ID stays matched with the inherited CMUX_PANEL_ID.
  • runCodexTeams watcher args: derive the root workspace/surface from the launch env, not the focused pane.

Tests

  • AgentSpawnIdentityTests (5 golden cases) — verified green locally and on the AWS M4 Pro mac.
  • The stronger end-to-end test (real launcher env path → persisted SurfaceResumeBinding → reload → assert correct surface) is in progress; design + the 4-test plan are in plans/feat-codex-surface-jumble/DESIGN.md.

Dogfood

Open two surfaces A + B in one workspace, focus A, launch codex in B via cmux omx (or cmux codex-teams), let a session start, quit + reopen cmux. Expected: codex returns to B (before this fix it returned to A).

🤖 Generated with Claude Code


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


Note

Medium Risk
Changes how agent spawn env and codex-teams watcher args resolve surface identity; wrong logic would still mis-route sessions on reload, but scope is limited to CLI launcher paths with new unit and integration coverage.

Overview
Fixes Codex (and related CLI agent launchers) restoring into the wrong terminal surface after reload when the operator’s focused pane differed from the pane where the agent was launched (#4920).

A new CMUXAgentLaunch package introduces AgentSpawnIdentity, which stamps CMUX_WORKSPACE_ID / CMUX_SURFACE_ID from the launcher’s inherited env first, using the focused pane only as a fallback. configureTmuxCompatEnvironment and runCodexTeams now use that resolver instead of overwriting identity from system.identify, so CMUX_SURFACE_ID stays aligned with CMUX_PANEL_ID.

Tests: five unit tests on the resolver, CI runs swift test for CMUXAgentLaunch, a hidden __debug-tmux-compat-env command, and an integration test that asserts the launch surface is stamped—not the focused surface.

Reviewed by Cursor Bugbot for commit aaf772b. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes codex sessions restoring to the wrong surface after reload by using the launch surface/workspace identity for agent environments and watcher args. Codex launched via cmux omx or cmux codex-teams now resumes in the pane it was started from.

  • Bug Fixes
    • Added CMUXAgentLaunch/AgentSpawnIdentity to prefer the launcher’s own CMUX_WORKSPACE_ID/CMUX_SURFACE_ID, falling back to the focused pane only when absent.
    • Updated configureTmuxCompatEnvironment to use the resolver, keeping CMUX_SURFACE_ID aligned with inherited CMUX_PANEL_ID.
    • Changed runCodexTeams to derive watcher --workspace-id/--surface-id from the launch env, not the focused pane.
    • Added hidden __debug-tmux-compat-env (uses the passed socket) and an end-to-end test that asserts the launch surface is stamped and CMUX_SURFACE_ID == CMUX_PANEL_ID; also verifies a surface-scoped CMUX_TAB_ID passes through. Kept 5 unit tests for the resolver.
    • CI now runs CMUXAgentLaunch package tests in the SwiftPM gate to catch regressions.

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

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added a hidden debug command to inspect tmux-compat environment values.
  • Improvements

    • Launcher now resolves and stamps workspace/surface identity from its own inherited identity with fallback to the focused context; improved multi-surface handling and stricter focused-context checks.
  • Tests

    • Added unit and end-to-end tests validating identity resolution and environment stamping.
  • Chores

    • CI updated to run the new package's test suite.

Root cause (adversarially-verified): the CLI agent launchers stamped the
operator's FOCUSED pane into CMUX_WORKSPACE_ID/CMUX_SURFACE_ID instead of the
launch surface, while leaving CMUX_PANEL_ID/CMUX_TAB_ID at the launch surface.
So a codex launched in surface B while surface A is focused recorded surface A
and restored into the wrong surface after reload (the "jumble"). Flows through
the codex-family omx/codex-teams launchers; plain `codex` is unaffected because
its env comes from the matched per-surface terminal startup env.

- New shared resolver CMUXAgentLaunch/AgentSpawnIdentity: prefer the launcher's
  OWN surface/workspace identity, falling back to the focused pane only when the
  launcher has none. Pure value logic with golden tests.
- configureTmuxCompatEnvironment (CLI/cmux.swift): resolve via AgentSpawnIdentity
  so omx/claude-teams stamp the launch surface, keeping CMUX_SURFACE_ID matched
  with the inherited CMUX_PANEL_ID.
- runCodexTeams watcher args: derive the root workspace/surface from the launch
  env, not focusedContext, so the codex-teams watcher records the right surface.

Independent of the cwd-resolution work (#5300/#5312): that is the directory axis,
this is the surface-mapping axis. Full root cause + e2e survives-reload test plan
in plans/feat-codex-surface-jumble/DESIGN.md.

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

vercel Bot commented Jun 4, 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 4, 2026 5:43am
cmux-staging Building Building Preview, Comment Jun 4, 2026 5:43am

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fbd9a54bf9

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines +8 to +9
@Test("Prefers the launcher's own surface over the focused pane")
func prefersOwnOverFocused() {

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 Badge Split the regression test into a red commit

This test is explicitly covering the #4920 regression, but it lands in the same commit as the fix. /workspace/cmux/AGENTS.md requires regression tests for bug fixes to be added as a separate test-only commit before the fix so CI can demonstrate the test fails without the code change; as committed, the PR cannot show that red/green proof.

Useful? React with 👍 / 👎.

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.

The two regression tests here both exercise NEW code, so a clean test-only red commit isn't achievable: the AgentSpawnIdentity golden tests need the new resolver type, and the launcher-path e2e needs the new __debug-tmux-compat-env seam. The e2e also can't show red/green in CI because the default tests job runs cmux-unit (no app host) and app-hosted CLI tests XCTSkip there. I verified red->green by hand on the real binary instead (reverting the fix makes it stamp the focused surface A; with the fix it stamps the launch surface B) — evidence posted as a PR comment.

— Claude Code

@greptile-apps

greptile-apps Bot commented Jun 4, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes the codex/OMX surface-mapping jumble (#4920) where agents launched in surface B while surface A was focused would restore into A after reload, because configureTmuxCompatEnvironment stamped CMUX_SURFACE_ID from the focused pane, desyncing it from the inherited CMUX_PANEL_ID.

  • Introduces AgentSpawnIdentity (stateless resolver): own-env workspace/surface wins; focused pane is fallback only for un-managed launches. Five unit golden cases cover all edge conditions.
  • configureTmuxCompatEnvironment and runCodexTeams watcher args both now route through AgentSpawnIdentity.resolve(...), keeping CMUX_SURFACE_ID and CMUX_PANEL_ID in sync.
  • Adds hidden __debug-tmux-compat-env seam plus a new E2E regression test in CLINotifyProcessIntegrationRegressionTests that exercises the full real launcher path against a mock socket.

Confidence Score: 5/5

Safe to merge — the fix is narrowly scoped to surface-id stamping, both call sites are updated consistently, and the CMUX_SURFACE_ID == CMUX_PANEL_ID invariant is asserted by a new E2E regression test.

The identity resolver is a pure stateless function with exhaustive unit tests covering all branching cases. The two production call sites (configureTmuxCompatEnvironment and runCodexTeams watcher args) both route through the resolver correctly. The E2E integration test exercises the real CLI launcher path end-to-end via the hidden debug seam, so the regression path is covered without relying on manual dogfood alone.

No files require special attention.

Important Files Changed

Filename Overview
Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentSpawnIdentity.swift New stateless resolver; clean logic, well-documented, no stored state, per-element fallback correctly handles the mixed-identity edge case.
CLI/cmux.swift Two targeted call-site fixes (configureTmuxCompatEnvironment + runCodexTeams) plus the hidden debug-seam function; logic is correct and well-commented, previously acknowledged defensive guards retained.
Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/AgentSpawnIdentityTests.swift Five golden cases covering own-wins, fallback, blank-treated-as-absent, per-element independence, and all-nil; comprehensive for the resolver logic.
cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift New E2E regression test exercises the real CLI launcher env path via the hidden seam; asserts SURFACE==PANEL invariant and that the focused surface is not stamped.
.github/workflows/ci.yml Adds CMUXAgentLaunch to the package list so the new package is covered by CI.

Sequence Diagram

sequenceDiagram
    participant Launcher as Launcher Process (Surface B)
    participant Identity as AgentSpawnIdentity.resolve()
    participant Daemon as cmux Daemon
    participant Env as Process Environment

    Launcher->>Daemon: system.identify()
    Daemon-->>Launcher: focusedContext (Surface A)

    Note over Launcher,Identity: Before fix: always stamp focusedContext
    Note over Launcher,Identity: After fix: prefer own env, fallback to focused

    Launcher->>Identity: "resolve(own=B, focused=A)"
    Identity-->>Launcher: "(workspaceId=WS-B, surfaceId=B)"

    Launcher->>Env: "setenv CMUX_WORKSPACE_ID=WS-B"
    Launcher->>Env: "setenv CMUX_SURFACE_ID=B"

    Note over Env: CMUX_SURFACE_ID == CMUX_PANEL_ID (was desynced before)

    Launcher->>Daemon: watcher --workspace-id WS-B --surface-id B
    Note over Daemon: SurfaceResumeBinding stored for Surface B, restores correctly
Loading

Reviews (4): Last reviewed commit: "ci: run CMUXAgentLaunch package tests as..." | Re-trigger Greptile

Comment thread CLI/cmux.swift
focusedWorkspaceId: focusedContext.workspaceId,
focusedSurfaceId: focusedContext.surfaceId
)
guard let rootSurfaceId = rootIdentity.surfaceId, !rootSurfaceId.isEmpty else {

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 rootIdentity.surfaceId is produced by normalized(), which already strips empty strings and returns nil for them — so rootSurfaceId can never be a non-nil empty string at this point. The !rootSurfaceId.isEmpty clause is dead code and may mislead future readers into thinking the check is necessary.

Suggested change
guard let rootSurfaceId = rootIdentity.surfaceId, !rootSurfaceId.isEmpty else {
guard let rootSurfaceId = rootIdentity.surfaceId else {

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.

Acknowledged — rootIdentity.surfaceId comes from AgentSpawnIdentity.resolve which already normalizes (nil for empty), so the !isEmpty is a harmless defensive guard. Left as-is for clarity.

— Claude Code

Comment thread CLI/cmux.swift
guard let rootSurfaceId = rootIdentity.surfaceId, !rootSurfaceId.isEmpty else {
throw CLIError(message: "cmux codex-teams must be started from a cmux terminal surface")
}
let rootWorkspaceId = rootIdentity.workspaceId ?? focusedContext.workspaceId

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 rootIdentity.workspaceId already incorporates focusedContext.workspaceId as a fallback inside resolve(). The outer ?? focusedContext.workspaceId only diverges when focusedContext.workspaceId is empty/whitespace (where normalized returned nil), handing the raw untrimmed empty string to the watcher's --workspace-id. Failing loudly here would be safer and mirrors how rootSurfaceId is handled.

Suggested change
let rootWorkspaceId = rootIdentity.workspaceId ?? focusedContext.workspaceId
guard let rootWorkspaceId = rootIdentity.workspaceId else {
throw CLIError(message: "cmux codex-teams must be started from a cmux terminal surface")
}

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.

Acknowledged — resolve() already falls back to focusedContext.workspaceId internally; the outer ?? is a redundant-but-harmless belt-and-suspenders for the empty-focused edge. Left as-is.

— Claude Code

/// focusedWorkspaceId: "WS-A", focusedSurfaceId: "A"
/// ) // == (workspaceId: "WS-B", surfaceId: "B") — the launch surface, not the focused pane
/// ```
public struct AgentSpawnIdentity: Sendable, Equatable {

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 AgentSpawnIdentity has no stored properties, so Swift synthesises Equatable as unconditionally true — every instance equals every other. The conformance is not wrong, but it is meaningless and may mislead future readers into thinking instances carry distinguishable state. Consider dropping it unless it satisfies a concrete protocol requirement.

Suggested change
public struct AgentSpawnIdentity: Sendable, Equatable {
public struct AgentSpawnIdentity: Sendable {

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.

Kept Equatable + Sendable for consistency with the sibling value-struct resolvers (AgentResumeArgv, AgentResumeWorkingDirectory) that the package-design review asked for. It's a stateless value, so equality is trivially true; harmless.

— Claude Code

@coderabbitai

coderabbitai Bot commented Jun 4, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces AgentSpawnIdentity, a new resolver that determines which workspace/surface IDs to stamp into spawned agent environments. Instead of always using the operator's currently focused pane, the launcher now prefers its own inherited CMUX_* environment, falling back to focused context only when the launcher has no identity. This change is integrated into the launcher's environment stamping, codex-teams watcher startup, and exposed via a hidden debug command.

Changes

CMUX Identity Resolution and Launcher Integration

Layer / File(s) Summary
AgentSpawnIdentity resolution contract and tests
Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentSpawnIdentity.swift, Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/AgentSpawnIdentityTests.swift
Introduces AgentSpawnIdentity struct with resolve(...) method that prefers launcher's own workspace/surface IDs, falls back to focused context, and normalizes whitespace/empty strings as absent. Five test cases validate override precedence, fallback behavior, per-field independence, and all-nil scenarios.
Debug command and environment inspection
CLI/cmux.swift, cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
Adds hidden __debug-tmux-compat-env command that invokes debugDumpTmuxCompatEnvironment(...) to force socket path, resolve focused context, run configuration with debug shim, and print resolved CMUX_WORKSPACE_ID, CMUX_SURFACE_ID, CMUX_PANEL_ID, and CMUX_TAB_ID. E2E test validates stamps are taken from launcher environment, not focused pane context.
Launcher environment stamping
CLI/cmux.swift
Updates CMUX env stamping for launcher spawn: CMUX_WORKSPACE_ID and CMUX_SURFACE_ID are now resolved using AgentSpawnIdentity with launcher-inherited CMUX_* values as primary source and focused context as fallback.
Codex-teams watcher integration
CLI/cmux.swift
In codex-teams watch flow, enforces focused context presence, resolves root identity from launcher's own CMUX environment plus focused context fallback, requires non-empty rootSurfaceId, and derives rootWorkspaceId with fallback. Passes rootWorkspaceId to watcher CLI instead of direct focused context value.
CI: include CMUXAgentLaunch package tests
.github/workflows/ci.yml
Adds CMUXAgentLaunch to the PACKAGES list so CI runs that package's Swift tests.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nibble at code with a curious hop,
New identity rules make stampings stop and swap.
The launcher now knows which IDs to send,
Debug command prints truths from end to end.
Hooray — consistent envs, carrot cake to append!

🚥 Pre-merge checks | ✅ 17 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (17 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically identifies the main fix: codex sessions incorrectly restoring to the wrong surface after reload, with a direct issue reference.
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 AgentSpawnIdentity is a stateless Sendable value struct with synchronous methods. No implicit MainActor models, async service protocols, or mutable Sendable types without isolation introduced.
Cmux Swift Blocking Runtime ✅ Passed AgentSpawnIdentity is a pure synchronous value type with only string operations. No blocking primitives (sleep, semaphore, lock) added to production code; test scaffolding is appropriate.
Cmux No Hacky Sleeps ✅ Passed PR modifies only Swift code (excluded per check) and YAML CI config. Only change adds 'CMUXAgentLaunch' to test packages—no timing patterns or delays introduced.
Cmux Algorithmic Complexity ✅ Passed New production code uses O(1) algorithms with no collection scans. AgentSpawnIdentity.resolve() does string normalization on 4 parameters. extraEnvVars loops are bounded to 0-2 fixed items.
Cmux Swift Concurrency ✅ Passed No legacy async patterns found: AgentSpawnIdentity is synchronous, no DispatchQueue, Combine, completion handlers, or fire-and-forget Tasks introduced.
Cmux Swift @Concurrent ✅ Passed All Swift changes comply with @concurrent rules: no missing @concurrent on async, no invalid @concurrent usage, and network/file operations are called from synchronous CLI context.
Cmux Swift File And Package Boundaries ✅ Passed AgentSpawnIdentity (55 lines) isolated in CMUXAgentLaunch package with tests. CLI/cmux.swift oversized file gets ~70 bug-fix lines using extracted resolver; no mixed responsibilities.
Cmux Swift Logging ✅ Passed Print statements in hidden debug command __debug-tmux-compat-env are CLI output (allowed exception). AgentSpawnIdentity production code has no logging violations. No secrets exposed.
Cmux User-Facing Error Privacy ✅ Passed Debug seam is internal and error message uses safe product terms without vendor/provider/credential exposure, compliant with rules.
Cmux Full Internationalization ✅ Passed No user-facing strings: AgentSpawnIdentity is internal logic; debugDumpTmuxCompatEnvironment (hidden debug seam, explicitly marked non-user-facing) prints env var names; tests exempt from i18n.
Cmux Swiftui State Layout ✅ Passed No SwiftUI patterns in PR; all changes are CLI/Foundation code (AgentSpawnIdentity, debug seam, tests, CI config) with no Observable, @State, @Published, GeometryReader, or related SwiftUI constructs.
Cmux Architecture Rethink ✅ Passed Stateless resolver prefers launch surface, zero timing/locks/state, explicit invariant, clear owner. Meets allowed-case for small correctness fix.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR does not add standalone cmux-owned windows. Changes are CLI logic, a pure value type, tests, and CI configuration only.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering problem, root cause, fix, tests, and dogfood repro with clear rationale.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix-codex-surface-jumble

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.

testTmuxCompatEnvStampsLaunchSurfaceNotFocusedPane drives the real `cmux` binary
through the real configureTmuxCompatEnvironment (via a hidden __debug-tmux-compat-env
subcommand) with a mock socket reporting focused=surface A and the launcher's own
env=surface B. Asserts the stamped CMUX_SURFACE_ID == launch surface B (== CMUX_PANEL_ID),
not the focused surface A. Exercises the actual launcher env path, not just the resolver.

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

@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

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread CLI/cmux.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.

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 `@cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift`:
- Around line 8465-8471: The environment fixture sets CMUX_TAB_ID incorrectly to
the workspace-scoped launchWorkspace; change CMUX_TAB_ID to be surface-scoped by
mapping it to launchSurface (i.e., set "CMUX_TAB_ID": launchSurface) and only
include that key when a surface ID is available in the launcher-env environment
block where CMUX_SOCKET_PATH, CMUX_WORKSPACE_ID, CMUX_SURFACE_ID, CMUX_PANEL_ID,
HOME, etc. are declared.

In `@plans/feat-codex-surface-jumble/DESIGN.md`:
- Line 5: Add missing blank lines before each level-2 Markdown heading to
satisfy MD022: insert a single empty line immediately above headings like "##
Symptom" and the other "##" headings in this document (the ones noted in the
review), so every `##` heading is preceded by one blank line for proper markdown
style and readability.
🪄 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: c865f2da-6e13-444d-b940-79318bc3f0e5

📥 Commits

Reviewing files that changed from the base of the PR and between 2c35c54 and b0035db.

📒 Files selected for processing (5)
  • CLI/cmux.swift
  • Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentSpawnIdentity.swift
  • Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/AgentSpawnIdentityTests.swift
  • cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
  • plans/feat-codex-surface-jumble/DESIGN.md

Comment thread cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
Comment thread plans/feat-codex-surface-jumble/DESIGN.md Outdated
@lawrencecchen

Copy link
Copy Markdown
Contributor Author

e2e verification (real binary)

Ran the actual fixed cmux CLI through the new __debug-tmux-compat-env seam against a hand-rolled mock socket: launcher's own env = surface B, mock system.identify reports focused = surface A.

CMUX_WORKSPACE_ID=WS-B-launch
CMUX_SURFACE_ID=SURFACE-B-launch      <- the LAUNCH surface, not the focused pane (SURFACE-A-focused)
CMUX_PANEL_ID=SURFACE-B-launch        <- matched with SURFACE_ID
CMUX_TAB_ID=WS-B-launch

The mock confirms the CLI queried system.identify and received focused=A, yet still stamped B — so configureTmuxCompatEnvironment now sources the canonical identity from the launch surface, keeping CMUX_SURFACE_ID matched with CMUX_PANEL_ID. Before the fix it stamped SURFACE-A-focused.

CI note: the automated test testTmuxCompatEnvStampsLaunchSurfaceNotFocusedPane lives in CLINotifyProcessIntegrationRegressionTests, which uses bundledCLIPath() (app-hosted bundle). The default tests job runs cmux-unit (no app host), so that test XCTSkips there. It runs app-hosted (the cmux scheme) — same logic just verified by hand above. The AgentSpawnIdentity resolver has package golden tests (5/5, also confirmed on the AWS M4 Pro mac).

lawrencecchen and others added 2 commits June 3, 2026 20:02
…surface-scope test tab id

- Remove plans/feat-codex-surface-jumble/DESIGN.md (design docs live in the control repo, not cmux).
- cubic P2: debugDumpTmuxCompatEnvironment now injects the passed socketPath into the env before
  resolving the focused context, so it uses the socket the command was pointed at.
- CodeRabbit: the e2e fixture uses a surface-scoped CMUX_TAB_ID (distinct from the workspace id) and
  asserts it passes through, matching the repo's surface-scoped tab identity contract.

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

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23f30407b8

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines +44 to +45
let workspaceId = normalized(ownWorkspaceId) ?? normalized(focusedWorkspaceId)
let surfaceId = normalized(ownSurfaceId) ?? normalized(focusedSurfaceId)

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 Badge Keep workspace and surface from the same source

When only one launch-scope variable is present (for example CMUX_WORKSPACE_ID survived but CMUX_SURFACE_ID is absent), this resolves a mixed identity such as own workspace plus focused surface. That pair can point at a surface that is not in the chosen workspace; downstream socket calls like surface.split resolve workspace_id before surface_id and then reject the surface as not found, so codex-teams/omx launched from a partially populated or stale cmux environment can still restore or split into the wrong place. Prefer the complete own pair only when both ids are available, otherwise fall back to the complete focused pair (or resolve the surface's workspace) rather than mixing sources element-by-element.

Useful? React with 👍 / 👎.

The CI swift-test step ran a hardcoded package list that omitted CMUXAgentLaunch,
so its tests (AgentSpawnIdentity resolver, AgentLaunchSanitizer, Hermes/RovoDev
resolvers, and the resume-engine builders once they land) only compiled, never
executed. The package resolves standalone via SwiftPM (no GhosttyKit/app dep),
so add it to the gate. Verified locally: 59 tests pass.

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

Copy link
Copy Markdown
Contributor Author

Superseded by #5351, merged in 5b6325d. This PR's omx/teams launcher-env surface fix + the CMUXAgentLaunch CI gate were folded into #5351, which also adds the CODEX_HOME capture (#5350), the hook PID/TTY override + stale-env recovery (#5333), and 4 autoreview-driven hardening fixes.

@lawrencecchen
lawrencecchen deleted the fix-codex-surface-jumble branch June 4, 2026 22:58

This branch was successfully deployed

1 active deployment
Preview – cmux — aaf772b2 Deployed Jun 4, 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