Skip to content

Add OpenCode sidebar status integration - #7940

Open
e-jung wants to merge 9 commits into
manaflow-ai:mainfrom
e-jung:fm/cmux-1425-rebuild
Open

e-jung wants to merge 9 commits into
manaflow-ai:mainfrom
e-jung:fm/cmux-1425-rebuild

Conversation

@e-jung

@e-jung e-jung commented Jul 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add a built-in OpenCode session plugin path that maps OpenCode lifecycle/status events into cmux sidebar activity/status updates.
  • Add cmux-opencode-wrapper and shell integration wiring so OpenCode sessions launched inside cmux can auto-install the cmux OpenCode plugins.
  • Add localized Retrying sidebar status, focused wrapper/plugin tests, Xcode project wiring, and authoritative docs for the OpenCode wrapper/status behavior.

Validation

No-mistakes gate on fm/cmux-1425-rebuild:

  • Review: passed after no-mistakes-applied focused fixes for Copy CLI bundling and wrapper session-entrypoint gating.
  • Tests: passed Linux-runnable focused checks, including tests/test_opencode_wrapper.py and direct Bun validation of the embedded OpenCode plugin event-to-hook mapping.
  • Docs: passed after approving the focused-docs decision: authoritative docs are updated in docs/agent-hooks.md and docs/feed.md; README and 20 localized README copies intentionally left unchanged because the README statement is true but non-exhaustive.
  • Lint/static: passed configured Linux-runnable checks (check-pbxproj.sh, Python compile, JSON validation, wrapper shellcheck/bash syntax, Swift file-length budget for this change).

Additional M1 Air validation before publish:

  • Tagged cmux build passed with ./scripts/reload.sh --tag cmux-1425-rebuild using the repo's CMUX_SKIP_ZIG_BUILD=1 helper-stub mode.
  • Built tagged CLI passed CMUX_CLI_BIN=... python3 tests/test_opencode_plugin_install.py.
  • python3 tests/test_opencode_wrapper.py passed.
  • zsh -n passed on the zsh shell integration.

Known limitations documented, not fixed here:

  • A full Ghostty CLI helper build on the M1 Air failed independently with Zig 0.15.2 due undefined macOS linker symbols; the tagged app build was validated with the repo-supported CMUX_SKIP_ZIG_BUILD=1 stub path.
  • fish was not installed on the M1 Air host, so fish -n could not be run there.
  • Existing unrelated baseline issues were not changed: pre-existing shellcheck warnings in large shell-integration files and unrelated Swift file-budget drift noted during earlier local assessment.

Preserved work:

  • Preserved the older 2f54f7b109 commit/branch unchanged; this PR is rebuilt from current origin/main.

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


Note

Medium Risk
Changes agent hook bridging, session store updates, and shell opencode interception; mistakes could mis-report running/idle state or add latency on every session launch inside cmux.

Overview
Adds OpenCode sidebar integration by moving the embedded session plugin into CMUXCLI+OpenCodeSessionPluginSource.swift and expanding what it reports: turn lifecycle (prompt-submit / stop with once-per-turn guards and retries), notifications for permissions, questions, and errors, and a retrying status path into cmux.

Introduces cmux-opencode-wrapper, wired through bash/zsh/fish and the app Copy CLI bundle. Inside a live cmux terminal (socket ping + CMUX_SURFACE_ID), session-like opencode invocations best-effort run cmux hooks opencode install --yes before exec’ing the real binary; --help, admin subcommands, and outside-cmux paths pass through without install latency.

CLI handling for OpenCode notification hooks with cmux_status: retrying updates the session store, resume binding, PID, lifecycle, and a localized Retrying status pill (agent.generic.status.retrying).

Docs (agent-hooks.md, feed.md) describe wrapper-based setup and the new behaviors; tests cover the wrapper gating and extended plugin event→hook mapping.

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


Summary by cubic

Adds OpenCode sidebar status with a localized “Retrying” pill and a new opencode wrapper that auto-installs cmux OpenCode plugins only inside cmux terminals. Moves the session plugin to CMUXCLI+OpenCodeSessionPluginSource.swift, wires the wrapper into bash/zsh/fish and the Copy CLI, and updates docs/tests; supports Linear CMUX-1425.

  • New Features

    • Session plugin maps lifecycle to cmux hooks: running/streaming/thinking -> prompt-submit, idle/done/stopped -> stop, archived/deleted -> session-end; forwards permission prompts, questions, errors; includes launch metadata and recent message context.
    • “Retrying” status emits a notification, marks the session running, and shows a localized pill; cmux-opencode-wrapper resolves the real binary, pings the socket, and installs session + Feed plugins only for session-like invocations inside a live cmux terminal (skips --help/admin).
  • Bug Fixes

    • Debounced duplicate start/turn events; stopped active turns on errors; broadened running/idle parsing; stopped retry-only sessions on idle to avoid a stuck “running” state.
    • Handled failed hook delivery: the plugin retries and only flips start/turn/stop state after a successful hook call.

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

Review in cubic

Summary by CodeRabbit

  • New Features
    • Automatic OpenCode integration inside cmux, including one-time plugin installation on the first opencode run.
    • Added opencode command wrappers for bash, zsh, and fish.
    • OpenCode session lifecycle now bridges into cmux notifications, including permission/question prompts and errors.
  • Bug Fixes
    • Improved handling of OpenCode “retrying” so agent status and lifecycle correctly resume.
  • Documentation
    • Updated OpenCode setup/troubleshooting to reflect wrapper-based installation.
  • Localization
    • Added translated support for the “Retrying” agent status.
  • Tests
    • Expanded regression coverage for wrapper behavior and session event handling.

@vercel

vercel Bot commented Jul 12, 2026

Copy link
Copy Markdown

@e-jung is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 12, 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

OpenCode integration now installs a cmux wrapper, embeds a session plugin, bridges OpenCode events to cmux hooks, handles retrying agent status, updates shell integrations, documents setup behavior, and adds installation and wrapper coverage.

Changes

OpenCode integration

Layer / File(s) Summary
Session plugin event bridge
CLI/CMUXCLI+OpenCodeSessionPluginSource.swift, tests/test_opencode_plugin_install.py, cmux.xcodeproj/project.pbxproj
The embedded plugin tracks session state and maps OpenCode lifecycle, status, permission, question, and error events to cmux hooks, with project registration and lifecycle validation.
Wrapper installation and shell wiring
Resources/bin/cmux-opencode-wrapper, Resources/shell-integration/*, tests/test_opencode_wrapper.py
The wrapper resolves the real OpenCode binary, classifies invocations, conditionally installs hooks inside cmux, and is installed by Bash, Zsh, and Fish integrations.
Retrying agent status handling
CLI/cmux.swift, Resources/Localizable.xcstrings
The opencode notification path persists retrying session state, resumes the surface binding, updates agent status, and emits localized retrying telemetry.
Integration documentation
docs/agent-hooks.md, docs/feed.md
Documentation describes wrapper-based plugin installation, recorded OpenCode events, and troubleshooting behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant Shell
  participant cmuxOpencodeWrapper
  participant cmux
  participant OpenCode
  participant CMUXSessionRestore
  Shell->>cmuxOpencodeWrapper: invoke opencode
  cmuxOpencodeWrapper->>cmux: install OpenCode hooks when eligible
  cmuxOpencodeWrapper->>OpenCode: exec real opencode
  OpenCode->>CMUXSessionRestore: emit lifecycle and interaction events
  CMUXSessionRestore->>cmux: run opencode hook with session payload
  cmux->>CMUXSessionRestore: process retrying status
Loading

Suggested reviewers: lawrencecchen


Important

Pre-merge checks failed

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

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux User-Facing Error Privacy ❌ Error New OpenCode notifications forward raw upstream text (props.error.message, props.permission.message, props.question.question) into user-facing alerts. Sanitize these alerts to cmux/product terms only; keep upstream/vendor details and raw payload text in internal logs or telemetry, not hook payloads.
Cmux Full Internationalization ❌ Error The retrying key is fully translated, but the new OpenCode session.error path hard-codes Agent reported an error, which notification policy shows verbatim, so this user-facing copy isn't locali... Use the existing localized error-body key (or add translations) instead of the hard-coded Agent reported an error literal in the OpenCode session plugin.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (22 passed)
Check name Status Explanation
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 No new Swift actor-isolation debt appears: CMUXCLI is not @MainActor, and the new Swift file only adds static string constants plus embedded JS source.
Cmux Swift Blocking Runtime ✅ Passed No Swift diff adds semaphores, waits, sleeps, polling, main-queue sync, or locks; the new Swift file is only an embedded JS source string.
Cmux Browser Automation Off-Main ✅ Passed PR diff only changes OpenCode plugin/test files; no browser.* commands, worker routing, or WebKit/AppKit main-actor paths were touched.
Cmux Expensive Synchronous Load ✅ Passed PASS: The Swift diff only adds an embedded JS plugin string; no load(), transcript/trajectory, JSONL, or main-actor interactive history loading was introduced.
Cmux Cache Substitution Correctness ✅ Passed The new OpenCode JS state is transient, event-driven, and cold-cache-safe; no persistence/history/snapshot path replaces a fresh read with stale cached data.
Cmux No Hacky Sleeps ✅ Passed No new sleeps/polling were added in the changed shell/runtime files; the only timing is a bounded socket ping timeout, not a hacky delay.
Cmux Algorithmic Complexity ✅ Passed No new scalable nested scans: the JS plugin uses explicit caps (100 sessions, 300 message roles) and the Swift config helpers are install-time single-pass array operations.
Cmux Swift Concurrency ✅ Passed The only Swift addition is static plugin-source/constants plus synchronous file I/O; no new DispatchQueue, Task, Combine, or completion-handler async patterns appear.
Cmux Swift @Concurrent ✅ Passed No Swift concurrency violations: the new Swift file is a JS source string, and the cmux.swift change is synchronous with no new @concurrent/nonisolated async mismatch.
Cmux Swift File And Package Boundaries ✅ Passed PASS: the new 406-line Swift file is a single-purpose embedded OpenCode source blob; cmux.swift only shrank and no mixed Swift/package-boundary spillover appears.
Cmux Swiftpm Lockfiles ✅ Passed PR changes cmux.xcodeproj only for new files; no SwiftPM package-reference, Package.resolved, .gitignore, or workflow diffs were present.
Cmux Swift Logging ✅ Passed No new Swift logging APIs were added; the changed print calls are CLI command output in hooks/installer paths, which the rule allows.
Cmux Swiftui State Layout ✅ Passed Only an embedded JS source string changed; no SwiftUI state/layout patterns like ObservableObject, GeometryReader, LazyVStack/List row stores, or render-time state writes were introduced.
Cmux Architecture Rethink ✅ Passed The Swift diff is a local correctness fix in an embedded plugin string; it adds no sleeps, observers, duplicate owners, or split lifecycle wiring.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The only Swift change is a static JS plugin string in CMUXCLI; no NSWindow/NSPanel/WindowGroup code or cmuxAuxiliaryWindowIdentifiers changes were introduced.
Cmux Source Artifacts ✅ Passed All 12 changed paths are intentional source/config/docs/tests/localization files; no artifact-like paths or generated scratch dirs appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No Swift file under a production Sources/ path changed; the only new Swift source is under CLI/ and shows no test/debug seam markers.
Cmux No Ambient Global State ✅ Passed The only new Swift surface is an extension with static let constants; no new top-level func/var, namespace type, or singleton state was added.
Title check ✅ Passed The title clearly reflects the main change: adding OpenCode sidebar status integration.
Description check ✅ Passed The description covers summary and validation well, though demo video, review trigger, and checklist sections are omitted.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@greptile-apps

greptile-apps Bot commented Jul 12, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds automatic OpenCode sidebar status integration. The main changes are:

  • An embedded plugin that maps OpenCode lifecycle events to cmux hooks.
  • A wrapper that installs the plugin for OpenCode sessions launched inside cmux.
  • Shell integration for bash, zsh, and fish.
  • Retry-aware session state and a localized Retrying status.
  • Focused wrapper, plugin, project, and documentation updates.

Confidence Score: 5/5

No additional blocking issue qualifies for this follow-up review.

  • The updated lifecycle guards address repeated start and prompt events.
  • Failed hook deliveries keep local state eligible for retry.
  • Error and terminal events clean up active turns.
  • Session identity no longer falls back to an event identifier.
  • The new status catalog contains translated values across supported locales.

Important Files Changed

Filename Overview
CLI/CMUXCLI+OpenCodeSessionPluginSource.swift Adds the OpenCode lifecycle bridge with deduplicated state transitions, cleanup, retry handling, and structured session identity.
CLI/cmux.swift Handles OpenCode retry notifications by updating session state, resume bindings, lifecycle, and sidebar status.
Resources/bin/cmux-opencode-wrapper Adds gated plugin installation and delegates commands to the real OpenCode executable.
Resources/Localizable.xcstrings Adds translated Retrying status text for the supported locales.
cmux.xcodeproj/project.pbxproj Wires the extracted OpenCode plugin source and wrapper into the build.

Reviews (5): Last reviewed commit: "Handle failed OpenCode hook delivery" | Re-trigger Greptile

Comment on lines +1993 to 2114
"agent.generic.status.retrying": {
"extractionState": "manual",
"localizations": {
"ar": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"bs": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"da": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"it": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "再試行中"
}
},
"ko": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"nb": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"pl": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"th": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"tr": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"uk": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "Retrying"
}
}
}
},
"agent.hermes.notification.body.approvalCommand": {
"extractionState": "manual",
"localizations": {

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 Locale Entries Remain English

Most new locale entries contain the English value Retrying, including Arabic, German, Spanish, Korean, and both Chinese variants. Those users will see English in the sidebar, and copied English placeholders do not satisfy the repository's localization requirement.

Rule Used: Flag production user-facing text that is not fully... (source)

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

Comment on lines +325 to +327
} else if (isRunningStatus(openCodeStatusType(event))) {
sendHook("prompt-submit", ctx, event);
}

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 Running States Inflate Prompt Depth

Each active, busy, running, streaming, thinking, or working event becomes a new prompt-submit. If OpenCode emits multiple running states during one turn, the store increments active prompt depth several times, while the eventual idle event decrements it once, leaving the finished session marked as running.

Comment on lines +344 to +349
case "session.error":
sendHook("notification", ctx, event, {
message: openCodeEventMessage(event),
reason: "error",
});
break;

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 Error Leaves Turn Active

A terminal session.error only sends a notification and does not run the stop path that decrements active prompt depth. When OpenCode does not follow the error with a separate idle event, the failed turn remains logically active and the session can continue to appear running or restorable.

Comment on lines +309 to +315
case "session.updated":
if (props.info && props.info.time && props.info.time.archived) {
sendHook("session-end", ctx, event);
dropSession(sessionIdFor(event));
} else {
sendHook("session-start", ctx, event);
}

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 Updates Replay Session Start

Every non-archived session.updated event is treated as session-start, including ordinary metadata changes such as title updates. The downstream start handler clears notification-emission state and publishes start telemetry, so mid-session updates can replay start events and allow an already emitted notification to be sent again.

Comment on lines +89 to +101
function sessionIdFor(event) {
const props = eventProperties(event);
return firstString(
props.info && props.info.id,
props.sessionID,
props.sessionId,
props.session_id,
props.session && props.session.id,
event && event.sessionID,
event && event.sessionId,
event && event.id
);
}

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 Event ID Becomes Session ID

The final fallback accepts event.id as the persistent session identifier. For an event shape where id identifies the event rather than its session, each status event creates or updates a different cmux session record; after enough events, these phantom keys evict the real in-memory session context and route lifecycle updates under the wrong identity.

Rule Used: Flag correctness-critical detection/identity deriv... (source)

}
_cmux_install_cli_wrapper claude _CMUX_CLAUDE_WRAPPER cmux-claude-wrapper
_cmux_install_cli_wrapper grok _CMUX_GROK_WRAPPER
_cmux_install_cli_wrapper opencode _CMUX_OPENCODE_WRAPPER cmux-opencode-wrapper

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 Existing OpenCode Function Is Replaced

Unlike the guarded Claude registration and the fish integration, the bash/zsh helper unconditionally removes an opencode alias and installs its own function. A user function that sets required environment or arguments is silently discarded when cmux shell integration loads, changing how OpenCode starts; preserve an existing override or compose the wrapper with it in both shells.

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!

Comment thread CLI/CMUXCLI+OpenCodeSessionPluginSource.swift
Comment thread Resources/bin/cmux-opencode-wrapper
sendHook("notification", ctx, event, {
message: "OpenCode reported an error",
reason: "error",
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error text dropped in notifications

Low Severity

On session.error, the session plugin sends a fixed OpenCode reported an error notification message instead of the event’s error text via openCodeEventMessage, so cmux never surfaces the provider’s actual failure reason in sidebar notifications.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d6f8235. Configure here.

@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 `@CLI/CMUXCLI`+OpenCodeSessionPluginSource.swift:
- Around line 268-294: Update sendHook to return whether spawnSync completed
successfully, then adjust sendStartOnce, sendPromptSubmitOnce, and
sendStopIfActive to commit their session-state flag changes only after
successful hook delivery, or restore the prior flag when delivery fails. Ensure
failed or timed-out cmux processes do not permanently alter started or
activeTurn state.

In `@tests/test_opencode_plugin_install.py`:
- Around line 233-252: Add failure-path coverage to the OpenCode session event
tests around the existing fake_cmux setup and assertions: provide a fake_cmux
variant that exits non-zero or is unavailable, then verify that a failed hook
transition is not marked as sent and is retried on the next matching event.
Preserve the existing successful dedup assertions for session-start,
prompt-submit, and stop.
🪄 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: 163189f2-dcb8-4481-b7c9-68fdc4ea603d

📥 Commits

Reviewing files that changed from the base of the PR and between 2c2378e and 730d206.

📒 Files selected for processing (3)
  • CLI/CMUXCLI+OpenCodeSessionPluginSource.swift
  • Resources/Localizable.xcstrings
  • tests/test_opencode_plugin_install.py

Comment thread CLI/CMUXCLI+OpenCodeSessionPluginSource.swift
Comment thread tests/test_opencode_plugin_install.py

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

♻️ Duplicate comments (2)
tests/test_opencode_plugin_install.py (1)

224-313: 📐 Maintainability & Code Quality | 🔵 Trivial

No failure-path test coverage (still unresolved).

fake_cmux always exits 0, so this suite cannot catch the state-desync issue where a failed hook process still marks the transition as sent. Consider adding a fake_cmux variant that exits non-zero or is missing, then asserting the corresponding hook is retried on the next matching event.

🤖 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 `@tests/test_opencode_plugin_install.py` around lines 224 - 313, Add
failure-path coverage to the event tests around the existing hooks.event
sequence by using a fake_cmux variant that exits non-zero or is unavailable.
Assert that the corresponding hook transition is retried on the next matching
event, proving failed hook execution does not mark the transition as sent.
CLI/CMUXCLI+OpenCodeSessionPluginSource.swift (1)

269-296: 🩺 Stability & Availability | 🟠 Major

Roll back lifecycle flags when hook delivery fails (still unresolved).

sendStartOnce, sendPromptSubmitOnce, and sendStopIfActive still flip session flags before calling sendHook, and sendHook still ignores the spawnSync result (including timeouts, which return normally rather than throwing). If the cmux process fails or times out, the transition is lost for the rest of the session because the flag stays set.

🤖 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 `@CLI/CMUXCLI`+OpenCodeSessionPluginSource.swift around lines 269 - 296, Update
sendHook to report whether spawnSync delivery succeeded, including treating
process errors and timeouts as failures, then have sendStartOnce,
sendPromptSubmitOnce, and sendStopIfActive revert their sessionState flags when
delivery fails. Preserve the existing successful-transition behavior while
ensuring failed or timed-out hooks do not consume the lifecycle transition.
🤖 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.

Duplicate comments:
In `@CLI/CMUXCLI`+OpenCodeSessionPluginSource.swift:
- Around line 269-296: Update sendHook to report whether spawnSync delivery
succeeded, including treating process errors and timeouts as failures, then have
sendStartOnce, sendPromptSubmitOnce, and sendStopIfActive revert their
sessionState flags when delivery fails. Preserve the existing
successful-transition behavior while ensuring failed or timed-out hooks do not
consume the lifecycle transition.

In `@tests/test_opencode_plugin_install.py`:
- Around line 224-313: Add failure-path coverage to the event tests around the
existing hooks.event sequence by using a fake_cmux variant that exits non-zero
or is unavailable. Assert that the corresponding hook transition is retried on
the next matching event, proving failed hook execution does not mark the
transition as sent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f2b7acf8-3eba-400e-821a-6546fa5bb7c9

📥 Commits

Reviewing files that changed from the base of the PR and between 730d206 and d6f8235.

📒 Files selected for processing (2)
  • CLI/CMUXCLI+OpenCodeSessionPluginSource.swift
  • tests/test_opencode_plugin_install.py

@e-jung
e-jung force-pushed the fm/cmux-1425-rebuild branch from d6f8235 to 9b5fac9 Compare July 12, 2026 20:30

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6e9a8f0. Configure here.

});
} else if (isRunningStatus(openCodeStatusType(event))) {
sendPromptSubmitOnce(ctx, event);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Retrying pill sticks after recovery

Medium Severity

When a session.status retry happens during an active turn, the sidebar shows localized Retrying. If OpenCode then emits a running or streaming status without an idle stop in between, sendPromptSubmitOnce exits early because activeTurn is already true, so retrying is never cleared and no hook updates cmux back to Running.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6e9a8f0. Configure here.

@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

🤖 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 `@tests/test_opencode_wrapper.py`:
- Around line 29-104: Extend run_wrapper so the test can omit the fake
real_dir/opencode executable and exercise the wrapper-only PATH fallback, while
preserving the current default behavior for existing callers. Add coverage for
this scenario and assert the wrapper terminates without recursive invocation,
including the expected return code and captured outputs.
🪄 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: 518719d6-208a-4ac8-9800-43f83cf6b51b

📥 Commits

Reviewing files that changed from the base of the PR and between d6f8235 and 9b5fac9.

📒 Files selected for processing (12)
  • CLI/CMUXCLI+OpenCodeSessionPluginSource.swift
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Resources/bin/cmux-opencode-wrapper
  • Resources/shell-integration/cmux-bash-integration.bash
  • Resources/shell-integration/cmux-zsh-integration.zsh
  • Resources/shell-integration/fish/config.fish
  • cmux.xcodeproj/project.pbxproj
  • docs/agent-hooks.md
  • docs/feed.md
  • tests/test_opencode_plugin_install.py
  • tests/test_opencode_wrapper.py

Comment on lines +29 to +104
def run_wrapper(*, inside_cmux: bool, hooks_disabled: bool = False, args: list[str] | None = None) -> tuple[int, str, str, str]:
with tempfile.TemporaryDirectory(prefix="cmux-opencode-wrapper-test-") as td:
tmp = Path(td)
wrapper_dir = tmp / "wrapper-bin"
real_dir = tmp / "real-bin"
wrapper_dir.mkdir(parents=True, exist_ok=True)
real_dir.mkdir(parents=True, exist_ok=True)

wrapper = wrapper_dir / "cmux-opencode-wrapper"
shutil.copy2(SOURCE_WRAPPER, wrapper)
wrapper.chmod(0o755)

real_log = tmp / "real.log"
cmux_log = tmp / "cmux.log"
env_log = tmp / "env.log"
socket_path = tmp / "cmux.sock"

make_executable(
real_dir / "opencode",
"""#!/usr/bin/env bash
set -euo pipefail
printf '%s\\n' "$*" >> "$REAL_LOG"
printf 'cmux_bin=%s\\n' "${CMUX_OPENCODE_CMUX_BIN-}" >> "$ENV_LOG"
""",
)
make_executable(
wrapper_dir / "cmux",
"""#!/usr/bin/env bash
set -euo pipefail
printf '%s\\n' "$*" >> "$CMUX_LOG"
if [[ "${1:-}" == "--socket" ]]; then
shift 2
fi
if [[ "${1:-}" == "ping" ]]; then
exit 0
fi
exit 0
""",
)

unix_socket: socket.socket | None = None
if inside_cmux:
unix_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
unix_socket.bind(str(socket_path))

env = os.environ.copy()
env["PATH"] = f"{wrapper_dir}:{real_dir}:{env.get('PATH', '/usr/bin:/bin')}"
env["REAL_LOG"] = str(real_log)
env["CMUX_LOG"] = str(cmux_log)
env["ENV_LOG"] = str(env_log)
if inside_cmux:
env["CMUX_SURFACE_ID"] = "surface:opencode"
env["CMUX_SOCKET_PATH"] = str(socket_path)
else:
env.pop("CMUX_SURFACE_ID", None)
env.pop("CMUX_SOCKET_PATH", None)
if hooks_disabled:
env["CMUX_OPENCODE_HOOKS_DISABLED"] = "1"
else:
env.pop("CMUX_OPENCODE_HOOKS_DISABLED", None)

try:
result = subprocess.run(
[str(wrapper), *(args or ["run", "task"])],
env=env,
capture_output=True,
text=True,
check=False,
timeout=10,
)
finally:
if unix_socket is not None:
unix_socket.close()

return result.returncode, read_text(cmux_log), read_text(real_log), read_text(env_log)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add coverage for the "no real opencode on PATH" fallback.

Every run_wrapper invocation places a fake opencode binary in real_dir ahead of the wrapper on PATH, so find_real_opencode always succeeds. There's no fixture exercising the case where the wrapper is the only opencode-named entry reachable — which is exactly the path flagged as an infinite-recursion risk in Resources/bin/cmux-opencode-wrapper (Lines 157-160). Adding a scenario where real_dir is empty/absent would catch that regression directly.

Also applies to: 106-170

🧰 Tools
🪛 ast-grep (0.44.1)

[error] 90-97: Command coming from incoming request
Context: subprocess.run(
[str(wrapper), *(args or ["run", "task"])],
env=env,
capture_output=True,
text=True,
check=False,
timeout=10,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

🪛 Ruff (0.15.20)

[error] 91-91: subprocess call: check for execution of untrusted input

(S603)

🤖 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 `@tests/test_opencode_wrapper.py` around lines 29 - 104, Extend run_wrapper so
the test can omit the fake real_dir/opencode executable and exercise the
wrapper-only PATH fallback, while preserving the current default behavior for
existing callers. Add coverage for this scenario and assert the wrapper
terminates without recursive invocation, including the expected return code and
captured outputs.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants