Skip to content

fix: prevent cmux omo legacy plugin warning loop - #3731

Closed
liyue2008 wants to merge 1 commit into
manaflow-ai:mainfrom
liyue2008:fix/omo-legacy-plugin-name-loop
Closed

liyue2008 wants to merge 1 commit into
manaflow-ai:mainfrom
liyue2008:fix/omo-legacy-plugin-name-loop

Conversation

@liyue2008

@liyue2008 liyue2008 commented May 8, 2026 •

Copy link
Copy Markdown

Fixes an infinite warning loop where every 'cmux omo' run prints the legacy plugin migration warning.

Root Cause

The omoPluginName constant in CLI/cmux.swift was 'oh-my-opencode' (old name). On every launch, omoEnsurePlugin() appends it to the shadow config plugin array since the user config uses 'oh-my-openagent' (new name). The plugin then auto-migrates it back, creating a loop.

Fix

  • Changed omoPluginName from 'oh-my-opencode' to 'oh-my-openagent'
  • Added legacyOmoPluginName for backward-compatible detection
  • Updated plugin list check to skip append if either name is present
  • Updated install messages and config file references

Summary by cubic

Prevents the infinite legacy plugin warning loop in cmux omo by using the current plugin name and recognizing the old one. Users no longer see migration warnings on every run.

  • Bug Fixes
    • Set omoPluginName to oh-my-openagent; added legacyOmoPluginName (oh-my-opencode).
    • Skip appending the plugin if either name exists in the config to stop the loop.
    • Updated install messages and errors to use the current package name.
    • Config lookup now supports both oh-my-openagent.json(.c) and oh-my-opencode.json(.c), writing to the current name.

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

Summary by CodeRabbit

  • Chores
    • Updated plugin installation and configuration file handling to support both current and legacy naming conventions, improving backward compatibility with existing setups and ensuring smoother transitions between naming formats.

Update omoPluginName from 'oh-my-opencode' to 'oh-my-openagent'
and add backward-compatible check for legacy plugin name.

Every cmux omo run was appending 'oh-my-opencode' to the shadow
config plugin array (since it compared against the stale constant).
The oh-my-openagent plugin then detected this as a legacy entry and
auto-migrated it back, creating an infinite warning loop.

Also updates install messages and config file references to use
the current package name, with fallback support for legacy filenames.
@vercel

vercel Bot commented May 8, 2026

Copy link
Copy Markdown

Someone 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 May 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

CLI/cmux.swift introduces dual plugin name support by adding a legacyOmoPluginName constant and updating plugin configuration merging, shadow config copying, installation flows, and configuration loading to recognize both current (oh-my-openagent) and legacy (oh-my-opencode) plugin names with appropriate fallback logic.

Changes

Dual Plugin Name Support

Layer / File(s) Summary
Constants & Naming
CLI/cmux.swift
Introduces legacyOmoPluginName constant alongside omoPluginName to define both plugin identifiers.
Plugin Configuration Merging
CLI/cmux.swift
Plugin spec inclusion condition now treats both current and legacy plugin specs as valid existing entries when deciding to append the current plugin name.
Shadow Config Copying
CLI/cmux.swift
Expands shadow config file copy list to include both current (oh-my-openagent.*) and legacy (oh-my-opencode.*) config filenames in .json and .jsonc variants.
Plugin Installation (Bun & npm)
CLI/cmux.swift
Bun and npm install flows updated to use Self.omoPluginName in progress messages, retry behavior, and error handling; fallback error when neither tool is available now references installing the current plugin name.
Configuration Loading with Fallback
CLI/cmux.swift
Tmux-mode config loading first attempts oh-my-openagent.json, then falls back to legacy oh-my-opencode.json; symlinked config reading searches for both current and legacy filenames to seed omoConfig.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • manaflow-ai/cmux#2230: Both PRs update plugin installation and OMO bootstrap logic in CLI/cmux.swift; this PR adds current/legacy plugin name handling while the retrieved PR modifies install behavior and preflight checks.
  • manaflow-ai/cmux#2280: Both PRs modify CLI/cmux.swift's OMO bootstrap, installation, and shadow-config logic; this PR introduces current/legacy plugin-name support while the retrieved PR refactors shadow package.json and install retry flows.

Poem

🐰 A hop through naming, old and new,
oh-my-openagent steps into view,
While legacy paths still have their say,
Both dance together, finding their way,
Fallbacks and forwards, a graceful tune! 🌱✨

🚥 Pre-merge checks | ✅ 13 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (13 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: preventing the cmux omo legacy plugin warning loop by updating plugin name handling.
Description check ✅ Passed The description provides root cause analysis, specific fix details, and a summary. However, it lacks Testing and Demo Video sections from the template, and the checklist items are incomplete.
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 Swift 6 actor isolation issues introduced or worsened. Changes are synchronous file/config handling with local variables only, no shared mutable state, and no async/MainActor boundaries crossed.
Cmux Swift Blocking Runtime ✅ Passed PR changes only update string constants, conditional logic, and file I/O operations. No new blocking runtime patterns (semaphores, sleeps, locks, polling) are introduced or expanded by this PR.
Cmux No Hacky Sleeps ✅ Passed PR modifies only Swift files (CLI/cmux.swift). Custom check scope is TypeScript/JavaScript/shell/non-Swift runtime. Swift timing covered by separate rule. No hacky sleeps introduced.
Cmux Swift Concurrency ✅ Passed No legacy async patterns introduced. Changes only update constants and file path strings for plugin name migration. All async operations pre-existing elsewhere.
Cmux Swift @Concurrent ✅ Passed PR contains only synchronous code changes. No async/await, @concurrent, or actor isolation violations detected. All modifications are synchronous plugin configuration and file operations.
Cmux Swift File And Package Boundaries ✅ Passed Bug fix adding ~20 lines to oversized file. Meets allowed case: incidental touch below 250-line threshold with clear extraction path preserved.
Cmux Swift Logging ✅ Passed PR adds 4 FileHandle.standardError.write statements for CLI status messages. No print/debugPrint/dump/NSLog added. Compliant with swift-logging.md's allowed case for CLI output.
Cmux Swiftui State Layout ✅ Passed PR modifies CLI/cmux.swift, a CLI tool with no SwiftUI imports. No SwiftUI state patterns, ObservableObject, @Published, GeometryReader, or state mutations. Check not applicable.
Cmux Architecture Rethink ✅ Passed Small correctness fix with clear invariants. No timing/blocking patterns, new state, duplicate wiring, or split UI ownership. Fixes infinite warning loop via constant update.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@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/cmux.swift`:
- Around line 12067-12068: Replace the hardcoded filenames
("oh-my-openagent.json", "oh-my-openagent.jsonc", "oh-my-opencode.json",
"oh-my-opencode.jsonc") with uses of the new constants by constructing filenames
from Self.omoPluginName and Self.legacyOmoPluginName plus the extensions
(".json" and ".jsonc"); e.g. use "\(Self.omoPluginName).json",
"\(Self.omoPluginName).jsonc", "\(Self.legacyOmoPluginName).json",
"\(Self.legacyOmoPluginName).jsonc" wherever those four literals appear
(including the for-loop array and the other occurrences mentioned) so the code
consistently uses the defined constants.
- Around line 12139-12150: The mutable optional collection "found" flagged by
SwiftLint (discouraged_optional_collection) should be replaced by a single
immutable lookup: compute a let found = userOmoConfigURLs.lazy.compactMap { url
in if let data = try? Data(contentsOf: url), let existing = try?
JSONSerialization.jsonObject(with: data) as? [String: Any] { return existing }
else { return nil } }.first and then use if let found { omoConfig = found; try?
fm.removeItem(at: omoConfigURL) } so you remove the var/loop/break and keep the
same behavior for omoConfig and fm.removeItem(at: omoConfigURL).
🪄 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: 7d7ad8c0-9d78-4c87-8110-e18f9a174838

📥 Commits

Reviewing files that changed from the base of the PR and between 7142e31 and a246017.

📒 Files selected for processing (1)
  • CLI/cmux.swift

Comment thread CLI/cmux.swift
Comment on lines +12067 to +12068
for filename in ["oh-my-openagent.json", "oh-my-openagent.jsonc",
"oh-my-opencode.json", "oh-my-opencode.jsonc"] {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use Self.omoPluginName / Self.legacyOmoPluginName constants instead of hardcoded string literals

The new constants are defined in hunk 1 but are then bypassed by hardcoded literals scattered across four sites. If the plugin name changes again the constants would be updated but these literals would silently diverge.

♻️ Proposed fix — replace literals with constants
-        for filename in ["oh-my-openagent.json", "oh-my-openagent.jsonc",
-                         "oh-my-opencode.json", "oh-my-opencode.jsonc"] {
+        for filename in ["\(Self.omoPluginName).json", "\(Self.omoPluginName).jsonc",
+                         "\(Self.legacyOmoPluginName).json", "\(Self.legacyOmoPluginName).jsonc"] {
-        let omoConfigURL = shadowDir.appendingPathComponent("oh-my-openagent.json")
+        let omoConfigURL = shadowDir.appendingPathComponent("\(Self.omoPluginName).json")
-            let candidate = shadowDir.appendingPathComponent("oh-my-opencode.json")
+            let candidate = shadowDir.appendingPathComponent("\(Self.legacyOmoPluginName).json")
-            let userOmoConfigURLs = [
-                userDir.appendingPathComponent("oh-my-openagent.json"),
-                userDir.appendingPathComponent("oh-my-opencode.json")
-            ]
+            let userOmoConfigURLs = [
+                userDir.appendingPathComponent("\(Self.omoPluginName).json"),
+                userDir.appendingPathComponent("\(Self.legacyOmoPluginName).json")
+            ]

Also applies to: 12121-12121, 12127-12127, 12136-12138

🤖 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/cmux.swift` around lines 12067 - 12068, Replace the hardcoded filenames
("oh-my-openagent.json", "oh-my-openagent.jsonc", "oh-my-opencode.json",
"oh-my-opencode.jsonc") with uses of the new constants by constructing filenames
from Self.omoPluginName and Self.legacyOmoPluginName plus the extensions
(".json" and ".jsonc"); e.g. use "\(Self.omoPluginName).json",
"\(Self.omoPluginName).jsonc", "\(Self.legacyOmoPluginName).json",
"\(Self.legacyOmoPluginName).jsonc" wherever those four literals appear
(including the for-loop array and the other occurrences mentioned) so the code
consistently uses the defined constants.

Comment thread CLI/cmux.swift
Comment on lines +12139 to 12150
var found: [String: Any]?
for url in userOmoConfigURLs {
if let data = try? Data(contentsOf: url),
let existing = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
found = existing
break
}
}
if let found {
omoConfig = found
// Remove the symlink so we can write our own copy
try? fm.removeItem(at: omoConfigURL)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix SwiftLint discouraged_optional_collection warning on var found: [String: Any]?

SwiftLint 0.63.2 flags this as a discouraged_optional_collection violation. The mutable optional can be replaced with a single lazy.compactMap.first expression, which also eliminates the var and the break.

♻️ Proposed fix
-            var found: [String: Any]?
-            for url in userOmoConfigURLs {
-                if let data = try? Data(contentsOf: url),
-                   let existing = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
-                    found = existing
-                    break
-                }
-            }
-            if let found {
+            let found = userOmoConfigURLs.lazy.compactMap { url -> [String: Any]? in
+                guard let data = try? Data(contentsOf: url) else { return nil }
+                return try? JSONSerialization.jsonObject(with: data) as? [String: Any]
+            }.first
+            if let found {
                 omoConfig = found
                 // Remove the symlink so we can write our own copy
                 try? fm.removeItem(at: omoConfigURL)
🧰 Tools
🪛 SwiftLint (0.63.2)

[Warning] 12139-12139: Prefer empty collection over optional collection

(discouraged_optional_collection)

🤖 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/cmux.swift` around lines 12139 - 12150, The mutable optional collection
"found" flagged by SwiftLint (discouraged_optional_collection) should be
replaced by a single immutable lookup: compute a let found =
userOmoConfigURLs.lazy.compactMap { url in if let data = try? Data(contentsOf:
url), let existing = try? JSONSerialization.jsonObject(with: data) as? [String:
Any] { return existing } else { return nil } }.first and then use if let found {
omoConfig = found; try? fm.removeItem(at: omoConfigURL) } so you remove the
var/loop/break and keep the same behavior for omoConfig and fm.removeItem(at:
omoConfigURL).

@greptile-apps

greptile-apps Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes an infinite warning loop in cmux omo caused by omoPluginName being set to the old package name (oh-my-opencode) while users' configs already reference the renamed package (oh-my-openagent). On every launch the shadow config would append the old name, the plugin would auto-migrate it back, and the cycle repeated.

  • omoPluginName is updated to \"oh-my-openagent\" and a legacyOmoPluginName constant (\"oh-my-opencode\") is introduced; the plugin-list guard now skips the append if either name is already present, preventing the loop for both migrated and legacy users.
  • All config-file copy and read paths (shadow oh-my-openagent.json, IIFE fallback for oh-my-opencode.json, user-dir search list) are updated with matching backward-compatible fallback logic.
  • Install messages and error strings are refactored to interpolate Self.omoPluginName instead of using hardcoded literals.

Confidence Score: 4/5

The change is narrowly scoped to renaming the plugin constant and adding backward-compatible fallback paths; no control-flow or data-persistence logic is restructurally altered.

The root-cause fix is correct and well-contained. Two style-level issues remain: hardcoded legacy name strings that bypass the new constant in the file-copy loop and one IIFE, and ad-hoc stderr writes that predate this PR but are actively modified here without being moved to unified logging.

CLI/cmux.swift — the file-copy loop (lines 12067-12068) and the IIFE fallback (line 12127) use literal "oh-my-opencode" instead of Self.legacyOmoPluginName.

Important Files Changed

Filename Overview
CLI/cmux.swift Renames omoPluginName to oh-my-openagent, adds legacyOmoPluginName constant, updates plugin-list guard to check both names, and updates all config-file copy/read paths to fall back to legacy name — correctly fixing the warning-loop regression for migrated users. Two P2 style issues: hardcoded legacy name strings not using the new constant, and ad-hoc stderr writes that bypass unified logging.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["cmux omo launched — omoEnsurePlugin()"] --> B["Read user opencode.json plugin list"]
    B --> C{Contains oh-my-openagent?}
    C -- Yes --> E["Skip append — no loop"]
    C -- No --> D{Contains oh-my-opencode legacy?}
    D -- Yes --> E
    D -- No --> F["Append oh-my-openagent to shadow plugin list"]
    F --> G["Write shadow opencode.json"]
    E --> G
    G --> H{"node_modules/oh-my-openagent exists?"}
    H -- Yes --> I["Skip install"]
    H -- No --> J["bun/npm add oh-my-openagent"]
    J --> K["Read/write oh-my-openagent.json tmux config"]
    I --> K
    K --> L["setenv OPENCODE_CONFIG_DIR to shadow dir"]
Loading

Reviews (1): Last reviewed commit: "fix: prevent omo legacy plugin warning l..." | Re-trigger Greptile

Comment thread CLI/cmux.swift
Comment on lines +12067 to +12068
for filename in ["oh-my-openagent.json", "oh-my-openagent.jsonc",
"oh-my-opencode.json", "oh-my-opencode.jsonc"] {

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 Hardcoded legacy name diverges from legacyOmoPluginName constant

The file-copy loop uses the literal "oh-my-opencode" string instead of Self.legacyOmoPluginName. The same pattern appears inside the IIFE on line 12127 (shadowDir.appendingPathComponent("oh-my-opencode.json")). If the legacy name ever changes again, these two sites would need to be updated manually and are easy to miss — the whole value of the new constant is to have a single source of truth.

Comment thread CLI/cmux.swift
Comment on lines 12081 to +12115
@@ -12093,39 +12096,56 @@ struct CMUXCLI {
currentDirectoryURL: installDir
)
if retryStatus != 0 {
throw CLIError(message: "Failed to install oh-my-opencode. Try manually: npm install -g oh-my-opencode")
throw CLIError(message: "Failed to install \(Self.omoPluginName). Try manually: npm install -g \(Self.omoPluginName)")
}
}
} else if let npmPath = resolveExecutableInPath("npm") {
FileHandle.standardError.write("Installing oh-my-opencode plugin (this may take a minute on first run)...\n".data(using: .utf8)!)
FileHandle.standardError.write("Installing \(Self.omoPluginName) plugin (this may take a minute on first run)...\n".data(using: .utf8)!)
let status = try omoRunPackageInstall(
executablePath: npmPath,
arguments: ["install", Self.omoPluginName],
currentDirectoryURL: installDir
)
if status != 0 {
throw CLIError(message: "Failed to install oh-my-opencode. Try manually: npm install -g oh-my-opencode")
throw CLIError(message: "Failed to install \(Self.omoPluginName). Try manually: npm install -g \(Self.omoPluginName)")
}
} else {
throw CLIError(message: "Neither bun nor npm found in PATH. Install oh-my-opencode manually: bunx oh-my-opencode install")
throw CLIError(message: "Neither bun nor npm found in PATH. Install \(Self.omoPluginName) manually: bunx \(Self.omoPluginName) install")
}
FileHandle.standardError.write("oh-my-opencode plugin installed\n".data(using: .utf8)!)
FileHandle.standardError.write("\(Self.omoPluginName) plugin installed\n".data(using: .utf8)!)

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 Ad-hoc stderr writes bypass unified logging

Every FileHandle.standardError.write(...) call in this block is raw stderr output, sidestepping os_log/Logger. This violates the project's unified-logging rule (no print/ad-hoc file/stdout logging in production Swift). These lines were pre-existing but are being actively modified by this PR, making them in-scope. The install progress messages should route through a Logger subsystem and category so they appear in Console.app, are filterable, and don't mix with other process output.

Rule Used: Flag production Swift diagnostics that bypass unif... (source)

@vercel

vercel Bot commented May 12, 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 May 12, 2026 6:29am

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Thank you for this, @liyue2008! You had the fix first: the omo legacy plugin warning loop got fixed on main a few days after you opened this, in #3960, which checks both the new and legacy plugin names. So this one's covered. Really appreciate it :)

@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 25, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — a246017c Deployed May 12, 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.

2 participants