Skip to content

docs: Claude Code hooks for tab auto-naming and /rename sync - #1980

Open
Horacehxw wants to merge 6 commits into
manaflow-ai:mainfrom
Horacehxw:docs/claude-code-tab-sync-hooks
Open

Horacehxw wants to merge 6 commits into
manaflow-ai:mainfrom
Horacehxw:docs/claude-code-tab-sync-hooks

Conversation

@Horacehxw

@Horacehxw Horacehxw commented Mar 23, 2026 •

Copy link
Copy Markdown

Summary

  • Add documentation and example hook scripts for enhanced Claude Code integration
  • Tab auto-naming: Stop hook reads session transcript (first 3 user msgs + last 5 msgs), extracts 2-5 keyword summary, sets tab name via cmux rename-tab — gives each tab a distinct focus label instead of mirroring the workspace name
  • /rename sync: UserPromptSubmit hook instantly propagates Claude Code /rename to cmux workspace name
  • Session reset: SessionStart hook clears stale workspace names when starting a new session

Motivation

cmux's native claude-hook integration handles workspace naming well via OSC 2, but tab names default to mirroring the workspace name. In multi-tab workspaces (Claude session + helper terminals), this makes tabs indistinguishable. These hooks solve that by auto-generating concise tab summaries from the conversation transcript.

Files added

File Purpose
docs/claude-code-hooks-integration.md Setup guide and architecture overview
docs/examples/cmux-tab-sync.sh Stop hook — transcript → tab name (~0.35s)
docs/examples/cmux-rename-sync.sh UserPromptSubmit hook — /rename → workspace (~0.35s)
docs/examples/cmux-session-start.sh SessionStart hook — reset workspace name (~0.05s)

How it works

Workspace name (sidebar):
  1. /rename → cmux-rename-sync.sh (UserPromptSubmit) → rename-workspace
  2. Claude Code auto-title → OSC 2 → cmux native
  3. cwd basename → cmux-session-start.sh (SessionStart)

Tab name (tab bar):
  Always: transcript summary → cmux-tab-sync.sh (Stop) → rename-tab

All hooks are guarded by [ -z "$CMUX_WORKSPACE_ID" ] && exit 0 — they no-op outside cmux.

Test plan

  • Install hooks in ~/.claude/hooks/ and register in ~/.claude/settings.json
  • Start a Claude Code session in cmux — verify tab name auto-updates after first response
  • Use /rename foo — verify workspace name changes to "foo" immediately
  • Exit session and start a new one in same workspace — verify workspace name resets
  • Test in non-cmux terminal — verify hooks exit silently (no errors)

Summary by cubic

Adds Claude Code hook docs and example scripts to auto-name tabs (with privacy-safe tokenization) and instantly sync /rename to cmux workspaces. Also resyncs portal geometry after restore-time bind and stabilizes UI test launch by using a fallback window and avoiding overlapping timers.

  • New Features

    • Added docs/claude-code-hooks-integration.md and example hooks:
      • cmux-tab-sync.sh (Stop): summarizes transcript to rename the tab.
      • cmux-rename-sync.sh (UserPromptSubmit): syncs /rename to workspace name.
      • cmux-session-start.sh (SessionStart): resets workspace name on new session.
    • Hooks no-op outside cmux.
    • Hardening: read transcript via head/tail, guard python3 with || true, cap tab labels to 2–5 words, and redact UUIDs/emails/paths/ENV assignments/long numbers before tokenization.
  • Bug Fixes

    • Terminal portal: queue an external geometry sync after restore-time bind; added a test covering hit-testing after queued layout shifts.
    • UI tests: stabilize launch and foregrounding with a retry loop; track a weak fallback window and close it when a real WindowGroup window appears; start display-move retries only once to prevent overlapping timers.
    • CI: retry the display-resolution UI test once on the known activation flake.

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

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Claude Code hooks integration with example scripts for workspace and tab management
  • Bug Fixes

    • Improved UI-test launch stability with adaptive retry mechanism
    • Enhanced window geometry synchronization for portal-hosted views during layout changes
  • Documentation

    • Added comprehensive Claude Code hooks integration guide with working example scripts
  • Tests

    • Added regression test for portal lifecycle and geometry synchronization behavior

@vercel

vercel Bot commented Mar 23, 2026

Copy link
Copy Markdown

@Horacehxw is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps

greptile-apps Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR bundles three mostly-unrelated improvements: (1) three Claude Code hook scripts + a setup guide that add tab auto-naming and /rename sync for cmux workspaces, (2) a retry-on-flake fix for the display-resolution UI test in CI, and (3) a scheduleExternalGeometrySynchronize() call after portal bind to handle post-restore ancestor layout shifts, backed by a new unit test.

The Swift/test/CI changes are clean and well-targeted. The shell scripts work correctly in the happy path, but have a few issues worth addressing before publication:

  • set -e + unguarded python3 one-liners (cmux-rename-sync.sh line 13, cmux-tab-sync.sh line 13, cmux-session-start.sh line 14): if python3 is absent or the hook input JSON is malformed, the command substitution exits non-zero and set -e propagates that as a hook failure. For the UserPromptSubmit hook (cmux-rename-sync.sh) this can block prompt submission in Claude Code. Adding || true to each assignment is the minimal fix.
  • Misleading "no Python" comment in cmux-rename-sync.sh line 4 — the script uses python3 twice.
  • Full transcript load vs. "head+tail" efficiency claim in cmux-tab-sync.sh — f.readlines() loads the entire file despite the stated design goal of reading only head+tail.
  • Word-count discrepancy: documentation and PR description say "2–5 words" but the summarize() loop in cmux-tab-sync.sh caps at 4 words.

Confidence Score: 4/5

  • Safe to merge after fixing the set -e / python3 exit-code issue in cmux-rename-sync.sh, which can block UserPromptSubmit in Claude Code.
  • The Swift, test, and CI changes are solid with no issues found. The hook scripts function correctly in the normal case; the one actionable bug (set -e + unguarded python3 can exit non-zero and block UserPromptSubmit) is a one-character fix per script. The remaining issues are comment/doc inaccuracies that don't affect runtime behaviour.
  • docs/examples/cmux-rename-sync.sh (set -e / python3 exit-code can block UserPromptSubmit), docs/examples/cmux-tab-sync.sh (same pattern; misleading efficiency comment)

Important Files Changed

Filename Overview
docs/examples/cmux-rename-sync.sh UserPromptSubmit hook that syncs /rename to cmux workspace name; has a misleading "no Python" header comment despite using python3 twice, and the bare python3 one-liner under set -e can exit non-zero and block prompt submission on malformed input or missing python3.
docs/examples/cmux-tab-sync.sh Stop hook that summarizes the transcript into a tab label; core logic is sound but loads the entire transcript via f.readlines() despite advertising a "head+tail, not full scan" approach, and the python3 JSON extraction one-liner lacks error handling under set -e.
docs/examples/cmux-session-start.sh SessionStart hook that resets the workspace name to the cwd basename; straightforward and minimal, but shares the same set -e + unguarded python3 one-liner pattern as the other two scripts.
docs/claude-code-hooks-integration.md Setup guide and architecture overview for the three hooks; well-structured, but the "2-5 keyword" claim in the tab-name section does not match the code's actual 4-word cap.
.github/workflows/ci.yml Wraps the display-resolution UI test in a retry function that re-runs once on the known "Failed to activate application … Running Background" launch flake; logic is correct and safely guarded.
Sources/AppDelegate.swift Replaces the one-shot delayed window-force block with a polling stabilizeUITestLaunchWindowAndForeground() function (up to 20 × 0.25 s retries) and splits activation into activateUITestAppIfNeeded() with a macOS 14 availability guard; clean and well-scoped to DEBUG.
Sources/TerminalWindowPortal.swift Adds scheduleExternalGeometrySynchronize() after bind to pick up post-restore ancestor layout shifts; minimal one-line change with a clear comment.
cmuxTests/TerminalAndGhosttyTests.swift New unit test verifying that bind() queues an external geometry sync so a deferred ancestor frame shift (simulating session restore) does not leave a stale portal hit-test entry; well-structured and covers both the stale and correct window points.

Sequence Diagram

sequenceDiagram
    participant CC as Claude Code
    participant UPS as cmux-rename-sync.sh<br/>(UserPromptSubmit)
    participant SS as cmux-session-start.sh<br/>(SessionStart)
    participant ST as cmux-tab-sync.sh<br/>(Stop)
    participant cmux as cmux CLI

    CC->>SS: SessionStart event (JSON with cwd)
    SS->>cmux: rename-workspace(basename(cwd))

    CC->>UPS: UserPromptSubmit event (JSON with transcript_path)
    UPS->>UPS: tail -500 transcript | grep custom-title
    alt /rename was used
        UPS->>cmux: list-workspaces (get current name)
        UPS->>cmux: rename-workspace(customTitle)
    end

    CC->>ST: Stop event (JSON with transcript_path)
    ST->>ST: readlines() → first 3 user msgs + last 5 msgs
    ST->>ST: tokenize + keyword frequency → label (2–4 words)
    ST->>cmux: rename-tab --surface $CMUX_SURFACE_ID label
Loading

Reviews (1): Last reviewed commit: "docs: add Claude Code hooks for tab auto..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Mar 23, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces three distinct changes: refactors UI-test launch stabilization in AppDelegate from single-shot logic to a DEBUG-only retry loop with failure recovery; extends TerminalWindowPortal.bind() with an additional deferred external-geometry synchronization pass to reconcile post-bind ancestor layout shifts; and adds comprehensive documentation and three example Bash hook scripts for Claude Code integration with cmux.

Changes

Cohort / File(s) Summary
AppDelegate UI-Test Stabilization
Sources/AppDelegate.swift
Replaces one-shot window stabilization logic with retry loop (up to 20 retries at 0.25s intervals) that validates window presence, visibility, key window, and app foreground state. Introduces stabilizeUITestLaunchWindowAndForeground() helper and centralized activateUITestAppIfNeeded() for macOS-version-specific app activation. Adds weak fallbackUITestWindow reference with cleanup in registerMainWindow when real windows materialize.
TerminalWindowPortal Geometry Synchronization
Sources/TerminalWindowPortal.swift
Extends bind(...) synchronization sequence with additional deferred external-geometry reconciliation pass after initial hosted-view sync to handle post-bind ancestor layout shifts.
Portal Lifecycle Regression Test
cmuxTests/TerminalAndGhosttyTests.swift
Adds testBindQueuesExternalGeometrySyncForQueuedLayoutShift() test that validates portal hit-testing behavior after queued ancestor layout shifts, confirming stale coordinates no longer resolve while shifted coordinates do resolve.
Claude Code Hooks Integration Documentation
docs/claude-code-hooks-integration.md
Documents integration flow for three Claude Code hooks (Stop, UserPromptSubmit, SessionStart), configuration in ~/.claude/settings.json, performance expectations, and workspace/tab naming priority order.
Hook Example Scripts
docs/examples/cmux-tab-sync.sh, docs/examples/cmux-rename-sync.sh, docs/examples/cmux-session-start.sh
Three Bash hook scripts with guarded execution and error suppression: cmux-tab-sync.sh uses embedded Python to extract/clean transcript keywords for tab naming; cmux-rename-sync.sh parses custom-title from transcript for workspace renaming; cmux-session-start.sh resets workspace name to working-directory basename.

Sequence Diagram

sequenceDiagram
    participant Test as Test Suite
    participant AppDelegate
    participant NSApp as NSApp/Windows
    participant RunApp as NSRunningApplication
    participant Callback as Retry Callback

    Test->>AppDelegate: trigger UI-test launch
    AppDelegate->>AppDelegate: stabilizeUITestLaunchWindowAndForeground()
    
    loop Retry Loop (up to 20 attempts)
        AppDelegate->>NSApp: check windows present & visible
        AppDelegate->>NSApp: check key window non-nil
        AppDelegate->>RunApp: check isActive
        
        alt All conditions met
            AppDelegate->>AppDelegate: activateUITestAppIfNeeded()
            AppDelegate->>RunApp: activate(macOS-specific options)
            AppDelegate->>NSApp: frontOrderWindows
            AppDelegate->>NSApp: register/close fallback window
            AppDelegate->>Callback: cancel retry loop
        else Conditions not met
            Callback->>AppDelegate: schedule retry at +0.25s
        end
    end
    
    AppDelegate->>Test: stabilization complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A window must stabilize with care,

Retry loops guard through the digital air,

Portal syncs cascade in harmony,

While Claude's hooks bend destiny,

Tests affirm the geometry's repair! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title directly matches the main documentation focus: adding Claude Code hooks for tab auto-naming and /rename sync.
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.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering all major sections: Summary (what changed and why), detailed motivation, files added with purpose, architecture overview, and a concrete test plan checklist.

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

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

Comment thread docs/examples/cmux-rename-sync.sh Outdated
#!/bin/bash
# Hook: Immediately sync /rename to cmux workspace name
# Triggered on: UserPromptSubmit
# Lightweight — no Python, no summarization. Just tail + grep.

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 Incorrect comment — Python is used

The comment claims # Lightweight — no Python, no summarization. Just tail + grep., but the script invokes python3 twice: once on line 13 to extract the transcript path from JSON, and again on lines 19–22 to parse the customTitle field from a JSONL line. This is a factual error that will mislead users troubleshooting in environments where python3 is unavailable.

Suggested change
# Lightweight — no Python, no summarization. Just tail + grep.
# Lightweight — extracts custom-title via tail + grep + python3. No LLM summarization.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 6c4a74a — comment updated to "Lightweight — minimal Python for JSON parsing only. Just tail + grep."

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 6c4a74a — the comment now reads 'Lightweight — minimal Python for JSON parsing only. Just tail + grep.' Python is used only for the two JSON field extractions.

Comment thread docs/examples/cmux-tab-sync.sh Outdated
Comment on lines +103 to +106
# --- Read transcript efficiently: only parse what we need ---
lines = []
with open(TRANSCRIPT, 'r') as f:
lines = f.readlines()

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 Full file is loaded despite "head+tail" efficiency claim

The comment says # --- Read transcript efficiently: only parse what we need ---, and the file header and PR description both describe the approach as "head+tail, not full scan". However, f.readlines() loads the entire transcript into memory. For a large long-running session (thousands of lines, potentially megabytes), this contradicts the stated design goal.

Only lines[:300] and lines[-200:] slices are ever consumed. A minimal fix is to avoid loading lines that will never be used:

# Read only the lines we need: first 300 + last 200
with open(TRANSCRIPT, 'r') as f:
    first_chunk = [next(f, None) for _ in range(300)]
    first_chunk = [l for l in first_chunk if l is not None]

# For the tail, we still need to seek; read all but limit to last 200
with open(TRANSCRIPT, 'r') as f:
    lines = f.readlines()
tail_chunk = lines[-200:]

Or, more simply, continue using f.readlines() but remove the misleading "efficiently (head+tail, not full scan)" comments so expectations are set correctly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 6c4a74a — replaced f.readlines() with a subprocess call using head -300 + tail -200 for true partial reading. The comment was updated to "Reads transcript JSONL (first 300 + last 200 lines, not full parse)".

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 6c4a74a — the Python block now uses subprocess.run(['head', '-300', TRANSCRIPT]) and subprocess.run(['tail', '-200', TRANSCRIPT]) instead of loading the whole file. Only the first 300 + last 200 lines are parsed. The header comment at line 4 reflects this: 'Reads transcript JSONL (first 300 + last 200 lines, not full parse)'.

Comment thread docs/examples/cmux-rename-sync.sh Outdated
command -v cmux &>/dev/null || exit 0

# Extract transcript path
TRANSCRIPT=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('transcript_path',''))" 2>/dev/null)

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 set -e + bare python3 one-liner can block UserPromptSubmit

All three scripts enable set -e at the top. The TRANSCRIPT=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('transcript_path',''))" 2>/dev/null) one-liner has no try/except around json.load(). If the hook input is unexpectedly malformed — or if python3 is not on PATH — the command substitution returns a non-zero exit code, which set -e converts into a script-level exit with a non-zero status.

For a UserPromptSubmit hook, a non-zero exit status is interpreted by Claude Code as a hook failure and can block prompt submission entirely, making the Claude Code session unusable until the hook is removed. Adding a fallback keeps the script safe:

Suggested change
TRANSCRIPT=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('transcript_path',''))" 2>/dev/null)
TRANSCRIPT=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('transcript_path',''))" 2>/dev/null) || true

The same pattern applies to cmux-tab-sync.sh line 13 and cmux-session-start.sh line 14.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 6c4a74a — added || true to all python3 command substitutions in cmux-rename-sync.sh, cmux-tab-sync.sh, and cmux-session-start.sh so a missing python3 or malformed input can't block UserPromptSubmit hooks.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 6c4a74a — the transcript extraction on line 13 now has || true at the end of the assignment, so a missing python3 or malformed input silently produces an empty string and the early-exit guard on line 14 ([ -z "$TRANSCRIPT" ] && exit 0) handles the rest without aborting.

- **First 3 user messages** — captures the session's original goal
- **Last 5 messages** (user + assistant) — captures the current focus

Extracts the most frequent meaningful keywords (2-5 words, mixed Chinese/English), and calls `cmux rename-tab --surface $CMUX_SURFACE_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.

P2 "2-5 words" claim doesn't match implementation

The doc says the hook extracts a "2-5 keyword summary" and the summarize() function in cmux-tab-sync.sh iterates for n in (4, 3, 2) — so the maximum generated label is 4 words, not 5. The PR description also says "2-5 keyword summary". The doc and implementation should agree.

Suggested change
Extracts the most frequent meaningful keywords (2-5 words, mixed Chinese/English), and calls `cmux rename-tab --surface $CMUX_SURFACE_ID`.
Extracts the most frequent meaningful keywords (2–4 words, mixed Chinese/English), and calls `cmux rename-tab --surface $CMUX_SURFACE_ID`.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 6c4a74a — the implementation was updated to iterate (5, 4, 3, 2) so the max label is now 5 words, matching the documentation's "2-5 words" claim.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 6c4a74a — the loop was changed from (4, 3, 2) to (5, 4, 3, 2) and the docs at line 90 already say '2-5 words'. The implementation now matches the documentation.

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

3 issues found across 8 files

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="docs/examples/cmux-rename-sync.sh">

<violation number="1" location="docs/examples/cmux-rename-sync.sh:13">
P1: Because this script runs with `set -e`, a JSON parse failure or missing `python3` will abort the hook at this line before the guard checks run. Make transcript extraction non-fatal so unexpected input does not fail the hook event.</violation>
</file>

<file name="docs/examples/cmux-tab-sync.sh">

<violation number="1" location="docs/examples/cmux-tab-sync.sh:106">
P3: This loads the entire transcript into memory even though only the first ~300 and last ~200 lines are consumed. Use a bounded head/tail read strategy here to avoid full-file memory and I/O cost on long sessions.</violation>
</file>

<file name="docs/examples/cmux-session-start.sh">

<violation number="1" location="docs/examples/cmux-session-start.sh:14">
P2: With `set -e` enabled, this JSON extraction can terminate the script on malformed input or missing `python3` before it reaches the no-op checks. Treat the parse step as best-effort so the hook fails closed and exits cleanly.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread docs/examples/cmux-rename-sync.sh Outdated
Comment thread docs/examples/cmux-session-start.sh Outdated
Comment thread docs/examples/cmux-tab-sync.sh 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: 5

🧹 Nitpick comments (3)
docs/claude-code-hooks-integration.md (1)

107-107: Add language specifier to fenced code block.

Per markdownlint, fenced code blocks should have a language specified. For ASCII diagrams, text or plaintext works.

📝 Suggested fix
-```
+```text
 Workspace name (sidebar):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/claude-code-hooks-integration.md` at line 107, The fenced code block
containing the ASCII diagram line "Workspace name (sidebar):" is missing a
language specifier; update the opening fence to include a language (e.g., change
the opening ``` to ```text or ```plaintext) so the block follows markdownlint
rules for fenced code blocks and helps render plain text diagrams correctly.
docs/examples/cmux-tab-sync.sh (1)

104-106: Reads entire file into memory before slicing.

The comment at line 4 claims "head+tail, not full scan," but f.readlines() loads the entire transcript into memory. For large transcripts, consider streaming reads or using shell head/tail before passing to Python.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/examples/cmux-tab-sync.sh` around lines 104 - 106, The current code
opens TRANSCRIPT and calls f.readlines(), which loads the whole file into memory
(contradicting the "head+tail" intent); change the reading logic to stream the
file instead of using f.readlines(): iterate over f (the file object) to capture
only the first N lines for the head and use collections.deque(maxlen=M) or
itertools.islice to retain only the last M lines for the tail, then combine
those into the lines variable; alternatively, invoke shell head/tail before
Python if preferred—update occurrences around TRANSCRIPT, the open(...) as f
block, and the lines assignment to implement this streaming approach.
docs/examples/cmux-rename-sync.sh (1)

28-28: Fragile parsing of cmux list-workspaces output.

The sed pattern depends on double-space delimiters and the exact [selected] marker format. If the output format changes, this may fail silently (resulting in unnecessary but harmless rename calls). Consider documenting the expected format or using a more robust parsing approach if available (e.g., a JSON output mode).

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/claude-code-hooks-integration.md`:
- Line 90: The docs claim "2-5 words" but the implementation in cmux-tab-sync.sh
(the loop over word lengths producing 4,3,2) only generates 2-4 words; update
the documentation phrase in docs/claude-code-hooks-integration.md to "2-4 words"
to match the script (or alternatively change the cmux-tab-sync.sh loop to
include 5 if you intend to support up to 5 words); reference the
cmux-tab-sync.sh loop that iterates through (4,3,2) and the sentence mentioning
"2-5 words" to keep them consistent.

In `@docs/examples/cmux-rename-sync.sh`:
- Line 4: The header comment "# Lightweight — no Python, no summarization. Just
tail + grep." is inaccurate because the script invokes python3 for JSON parsing
(see uses around the json parsing calls); update that comment to accurately
reflect the implementation (for example, mention that the script is lightweight
but does use Python for JSON parsing and still performs no summarization) so
readers aren't misled by the header.

In `@docs/examples/cmux-tab-sync.sh`:
- Around line 60-68: The tokenize function currently strips URLs and code but
doesn't redact sensitive spans; update tokenize to first apply an unanchored set
of sensitiveSpanPatterns (e.g., UUIDs, email addresses, access tokens/long hex
or bearer-like tokens, filesystem paths, ENV_VAR=... assignments, long numeric
sequences) across the combined body+subtitle before any other cleaning,
replacing matches with a neutral placeholder like "<REDACTED>"; then continue
the existing URL/code removal and tokenization logic. Ensure you reference the
tokenize function and the sensitiveSpanPatterns variable/name so the redaction
step runs prior to the subsequent re.sub calls and token extraction.

In `@Sources/AppDelegate.swift`:
- Around line 2425-2438: The helper moveUITestWindowToTargetDisplayIfNeeded()
currently performs its own 20-step asyncAfter retry scheduling, and
stabilizeUITestLaunchWindowAndForeground(attempt:) wraps it with another 20-step
retry, causing nested overlapping retries and diagnostic overwrites; fix by
adding a scheduleRetry (Bool) parameter or internal check to
moveUITestWindowToTargetDisplayIfNeeded() so its DispatchQueue.main.asyncAfter
retry branches only run when scheduleRetry is true, then call
moveUITestWindowToTargetDisplayIfNeeded(scheduleRetry: false) from
stabilizeUITestLaunchWindowAndForeground(attempt:) and let only
stabilizeUITestLaunchWindowAndForeground perform the outer scheduling, keeping
writeUITestDiagnosticsIfNeeded(stage:) behavior unchanged.
- Around line 2447-2451: The macOS 14+ branch in activateUITestAppIfNeeded()
drops .activateIgnoringOtherApps causing the app to remain backgrounded; change
the NSRunningApplication.current.activate call so both .activateAllWindows and
.activateIgnoringOtherApps are passed on macOS 14+ as well (i.e., use options:
[.activateAllWindows, .activateIgnoringOtherApps] instead of only
.activateAllWindows) so the isActive check can succeed reliably.

---

Nitpick comments:
In `@docs/claude-code-hooks-integration.md`:
- Line 107: The fenced code block containing the ASCII diagram line "Workspace
name (sidebar):" is missing a language specifier; update the opening fence to
include a language (e.g., change the opening ``` to ```text or ```plaintext) so
the block follows markdownlint rules for fenced code blocks and helps render
plain text diagrams correctly.

In `@docs/examples/cmux-tab-sync.sh`:
- Around line 104-106: The current code opens TRANSCRIPT and calls
f.readlines(), which loads the whole file into memory (contradicting the
"head+tail" intent); change the reading logic to stream the file instead of
using f.readlines(): iterate over f (the file object) to capture only the first
N lines for the head and use collections.deque(maxlen=M) or itertools.islice to
retain only the last M lines for the tail, then combine those into the lines
variable; alternatively, invoke shell head/tail before Python if
preferred—update occurrences around TRANSCRIPT, the open(...) as f block, and
the lines assignment to implement this streaming approach.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c742e1ec-cc3c-475b-a214-6ab73a89d3d6

📥 Commits

Reviewing files that changed from the base of the PR and between 56a4d25 and 454b660.

📒 Files selected for processing (8)
  • .github/workflows/ci.yml
  • Sources/AppDelegate.swift
  • Sources/TerminalWindowPortal.swift
  • cmuxTests/TerminalAndGhosttyTests.swift
  • docs/claude-code-hooks-integration.md
  • docs/examples/cmux-rename-sync.sh
  • docs/examples/cmux-session-start.sh
  • docs/examples/cmux-tab-sync.sh

Comment thread docs/claude-code-hooks-integration.md
Comment thread docs/examples/cmux-rename-sync.sh Outdated
Comment thread docs/examples/cmux-tab-sync.sh
Comment thread Sources/AppDelegate.swift Outdated
Comment thread Sources/AppDelegate.swift Outdated
Comment on lines +2447 to +2451
if #available(macOS 14.0, *) {
NSRunningApplication.current.activate(options: [.activateAllWindows])
} else {
NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps])
}

@coderabbitai coderabbitai Bot Mar 23, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Is NSRunningApplication.ActivationOptions.activateIgnoringOtherAppsstill available and intended forNSRunningApplication.current.activate(options:)on macOS 14+, and is.activateAllWindows alone sufficient to bring a background app to the foreground?

💡 Result:

Yes, NSRunningApplication.ActivationOptions.activateIgnoringOtherApps (which is .activateIgnoringOtherApps in Swift) is still available and intended for use with NSRunningApplication.current.activate(options:) on macOS 14+ (Sonoma) and later. It is part of NSApplication.ActivationOptions, explicitly documented as available for NSRunningApplication.activate(options:) in current Apple documentation (copyright 2026). While the old NSApplication.activate(ignoringOtherApps:) method is deprecated as of macOS 14, the options-based API using activateIgnoringOtherApps remains supported, as evidenced by developer usage in macOS Sonoma contexts and bindings like .NET for macOS 26.0 and Rust objc2-app-kit noting it as deprecated only in a warning sense but functional. No, .activateAllWindows alone is not sufficient to reliably bring a background app to the foreground, especially if another app is currently active. By default (no options or without .activateIgnoringOtherApps), activation deactivates the calling app first and only activates the target if no other app becomes active in the interim, preventing focus stealing. .activateAllWindows only controls which windows come forward (all vs. main/key), not whether the app reliably becomes active/foreground over others. Sources confirm that for reliable foreground activation of background apps (e.g., from status bar/menu bar apps), .activateIgnoringOtherApps is needed alongside it, as in Stack Overflow solutions for Sonoma where plain activation fails inconsistently.

Citations:


🏁 Script executed:

sed -n '2420,2460p' Sources/AppDelegate.swift | cat -n

Repository: manaflow-ai/cmux

Length of output: 2041


Fix macOS 14+ activation to reliably bring app to foreground.

The activateUITestAppIfNeeded() function at lines 2447–2451 drops .activateIgnoringOtherApps on macOS 14+, leaving the app backgrounded when another process owns focus. This causes the isActive check at line 2435 to fail repeatedly, exhausting the 20-attempt retry budget in the stabilization loop.

.activateAllWindows alone does not guarantee foreground activation; .activateIgnoringOtherApps is still available and required on macOS 14+ for reliable foreground placement, especially in shared-VM test environments.

Suggested fix
-        if `#available`(macOS 14.0, *) {
-            NSRunningApplication.current.activate(options: [.activateAllWindows])
-        } else {
-            NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps])
-        }
+        _ = NSRunningApplication.current.activate(
+            options: [.activateAllWindows, .activateIgnoringOtherApps]
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if #available(macOS 14.0, *) {
NSRunningApplication.current.activate(options: [.activateAllWindows])
} else {
NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps])
}
_ = NSRunningApplication.current.activate(
options: [.activateAllWindows, .activateIgnoringOtherApps]
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate.swift` around lines 2447 - 2451, The macOS 14+ branch in
activateUITestAppIfNeeded() drops .activateIgnoringOtherApps causing the app to
remain backgrounded; change the NSRunningApplication.current.activate call so
both .activateAllWindows and .activateIgnoringOtherApps are passed on macOS 14+
as well (i.e., use options: [.activateAllWindows, .activateIgnoringOtherApps]
instead of only .activateAllWindows) so the isActive check can succeed reliably.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same as above — Sources/AppDelegate.swift is not changed in this docs PR. This is a pre-existing issue. Tracking separately.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Regarding NSRunningApplication.current.activate(options:) deprecation on macOS 14+: noted. The activateIgnoringOtherApps option is deprecated but still functional. Will update to use the modern activation API (activate(from: NSRunningApplication?)) in a follow-up.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@Horacehxw have exceeded the limit for the number of chat messages per hour. Please wait 1 minutes and 56 seconds before sending another message.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

No code change needed — the activateUITestAppIfNeeded() helper already uses if #available(macOS 14.0, *) { activate(options: [.activateAllWindows]) } (without activateIgnoringOtherApps) on macOS 14+, with the deprecated form only as a fallback for macOS 13 and earlier. So the API usage is already correct per the deprecation guidance.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@Horacehxw Got it — thanks for clarifying. I’ll leave this as-is for this docs PR and keep your follow-up note about moving to the modern activation API in mind for future reviews.


✏️ Learnings added
Learnt from: Horacehxw
Repo: manaflow-ai/cmux PR: 1980
File: Sources/AppDelegate.swift:2454-2458
Timestamp: 2026-04-06T09:33:37.199Z
Learning: Repo: manaflow-ai/cmux — File: Sources/AppDelegate.swift — In activateUITestAppIfNeeded() for macOS 14+, prefer NSRunningApplication.current.activate(options: [.activateAllWindows]) without .activateIgnoringOtherApps; the deprecated option is only used on older macOS. A future follow-up may migrate to the modern activation API.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: Horacehxw
Repo: manaflow-ai/cmux PR: 2043
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-04-06T09:32:23.623Z
Learning: Repo: manaflow-ai/cmux — File: Sources/AppDelegate.swift — UI test fallback window pattern: during XCTest launch stabilization, the app may force-create a fallback main window when NSApp.windows is empty. The window is tracked via `private weak var fallbackUITestWindow: NSWindow?` and, upon the first real WindowGroup registration in `registerMainWindow(...)`, if a second main window exists and the registering window differs, the fallback is closed and the reference cleared to avoid two main windows during tests.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2124
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-03-25T08:05:26.034Z
Learning: Repo: manaflow-ai/cmux — File: Sources/AppDelegate.swift — Pattern for “New Window” geometry seed:
In AppDelegate.createMainWindow(initialWorkingDirectory:sessionWindowSnapshot:), compute the existingFrame using preferredMainWindowContextForWorkspaceCreation(debugSource: "createMainWindow.initialGeometry") and then resolvedWindow(for:) rather than relying on NSApp.keyWindow or the first registered mainWindowContext. Rationale: ensures the new window inherits size from the intended main-terminal window even when an auxiliary window is key, and keeps behavior consistent with showOpenFolderPanel().

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 2505
File: Sources/AppDelegate.swift:4910-4918
Timestamp: 2026-04-06T02:02:42.414Z
Learning: Repo: manaflow-ai/cmux — File: Sources/AppDelegate.swift — Keyboard repair pattern: In AppDelegate.repairFocusedTerminalKeyboardRoutingIfNeeded(window:event:), determine whether to repair focus by asking the focused terminal’s hosted view if the current first responder already matches that panel’s preferred keyboard target. Implemented via hostedView.responderMatchesPreferredKeyboardFocus(responder) inside responderNeedsFocusedTerminalKeyRepair(_:in:hostedView:). This covers same-window drift to a different Ghostty surface without comparing workspace/panel IDs. Verified by test cmuxTests/AppDelegateShortcutRoutingTests.swift::testWindowSendEventRepairsVisibleSameWindowResponderDriftForFocusedTerminalTyping.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2034
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-03-25T07:14:56.211Z
Learning: Repo: manaflow-ai/cmux — Sources/AppDelegate.swift — Pattern for “Open Folder”: In AppDelegate.showOpenFolderPanel(), seed NSOpenPanel.directoryURL using preferredMainWindowContextForWorkspaceCreation(debugSource: …) rather than NSApp.keyWindow, and on selection delegate to openWorkspaceForExternalDirectory(workingDirectory:…, debugSource: …). Rationale: handles auxiliary-key-window cases, ensures shouldBringToFront = true, and unifies menu/shortcut behavior with a consistent fallback to createMainWindow when workspace creation returns nil.

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 2545
File: Sources/AppDelegate.swift:2864-2866
Timestamp: 2026-04-04T19:35:02.567Z
Learning: Repo: manaflow-ai/cmux — Sources/AppDelegate.swift — Quit path design: applicationShouldTerminate(_) performs a precautionary save without setting isTerminatingApp (async refresh/write); applicationWillTerminate(_) sets isTerminatingApp = true and then calls saveSessionSnapshot(...), which runs refreshForegroundProcessCacheSync() (2s timeout) and persists synchronously to capture fresh foreground process data.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T00:55:26.574Z
Learning: Applies to **/*.swift : To add a debug toggle or visual option: create an `NSWindowController` subclass with a `shared` singleton, add it to the "Debug Windows" menu in `Sources/cmuxApp.swift`, and add a SwiftUI view with `AppStorage` bindings for live changes

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T00:55:26.574Z
Learning: Applies to **/*.swift : Socket/CLI commands must not steal macOS app focus (no app activation/window raising side effects). Only explicit focus-intent commands may mutate in-app focus/selection (`window.focus`, `workspace.select/next/previous/last`, `surface.focus`, `pane.focus/last`, browser focus commands, and v1 focus equivalents)

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-25T04:48:00.216Z
Learning: Applies to **/*.swift : Socket/CLI commands must not steal macOS app focus (no app activation/window raising side effects). Only explicit focus-intent commands may mutate in-app focus/selection (`window.focus`, `workspace.select/next/previous/last`, `surface.focus`, `pane.focus/last`, browser focus commands, and v1 focus equivalents).

Learnt from: andrekat
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-29T11:05:01.662Z
Learning: In manaflow-ai/cmux PR `#1773`: `setActiveProfileName(_:)` on `TabManager` (Sources/TabManager.swift) must call `updateWindowTitleForSelectedTab()` explicitly — the save and delete paths do not go through `selectedTabId.didSet`, so without the explicit call the AppKit window title is left stale with the old profile prefix. This was confirmed fixed in commit be9eaa26.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-05T03:20:48.079Z
Learning: Applies to **/*.swift : Socket/CLI commands must not steal macOS app focus; only explicit focus-intent commands may mutate in-app focus/selection

Learnt from: johnhanks1
Repo: manaflow-ai/cmux PR: 1556
File: Sources/AppDelegate.swift:7878-7888
Timestamp: 2026-03-17T01:11:15.590Z
Learning: Repo: manaflow-ai/cmux — Sources/AppDelegate.swift — In AppDelegate.handleCustomShortcut(event:), when detecting an external keyboard input-source change (via KeyboardLayout.id), the skip branch must not update lastKeyboardInputSourceId; it should only log and return false. Update lastKeyboardInputSourceId only after passing the guard into normal shortcut handling. Rationale: prevents same-event re-entry (e.g., via handleBrowserSurfaceKeyEquivalent from NSWindow.performKeyEquivalent) from bypassing the skip and inadvertently processing app shortcuts.

Learnt from: SuperManfred
Repo: manaflow-ai/cmux PR: 803
File: Sources/Panels/BrowserPopupWindowController.swift:36-38
Timestamp: 2026-03-04T05:11:56.373Z
Learning: In Sources/Panels/BrowserPopupWindowController.swift and Sources/Panels/BrowserPanel.swift, `webView.isInspectable = true` (guarded by `#available(macOS 13.3, *)`) is intentionally enabled in all builds — not just DEBUG — because cmux is a developer tool and full Web Inspector access is desired in production builds as well. Do not flag this as a security concern.

Learnt from: tayl0r
Repo: manaflow-ai/cmux PR: 1909
File: Sources/AppDelegate.swift:1955-1970
Timestamp: 2026-03-21T07:13:41.796Z
Learning: Repo: manaflow-ai/cmux — AppDelegate.registerMainWindow ownership pattern: the primary window registers from ContentView.onAppear and passes the SwiftUI-owned FileBrowserDrawerState; secondary windows created via AppDelegate.createMainWindow construct and pass their own FileBrowserDrawerState. This mirrors SidebarState and avoids re-registration mismatches.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/ContentView.swift:3194-3196
Timestamp: 2026-03-04T14:05:48.668Z
Learning: In manaflow-ai/cmux (PR `#819`), Sources/ContentView.swift: The command palette’s external window labels intentionally use the global window index from the full orderedSummaries (index + 1), matching the Window menu in AppDelegate. Do not reindex after filtering out the current window to avoid mismatches (“Window 2” for an external window is expected).

Learnt from: apollow
Repo: manaflow-ai/cmux PR: 1089
File: CLI/cmux.swift:462-499
Timestamp: 2026-03-09T02:08:54.956Z
Learning: Repo: manaflow-ai/cmux
PR: `#1089`
File: CLI/cmux.swift
Component: ClaudeHookTagExtractor.extractTags(subtitle:body:)
Learning: For Claude Code session tag extraction, pre-redact sensitive spans (UUIDs, emails, access tokens, filesystem paths, ENV_VAR=..., long numerics) across the combined body+subtitle using unanchored sensitiveSpanPatterns before tokenization. Then tokenize and still filter each token with anchored sensitivePatterns. Rationale: prevents PII/path fragments from slipping into searchable tags after delimiter splitting.

Learnt from: Horacehxw
Repo: manaflow-ai/cmux PR: 2043
File: Resources/bin/claude:0-0
Timestamp: 2026-04-03T07:49:14.139Z
Learning: Repo: manaflow-ai/cmux — In Resources/bin/claude, SELF_DIR is validated with a broad regex `[\"\\' $\`!;|&<>()*?\[\]]` before interpolating it into HOOKS_JSON command strings. Paths that match (unsafe chars including spaces and shell metacharacters) cause the wrapper to fall back to BASE_HOOKS_JSON and emit a warning. This guard-and-fallback is the intentional design; do not suggest adding shell quoting inside the JSON command strings — the guard approach is preferred.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2398
File: CLI/cmux.swift:0-0
Timestamp: 2026-04-01T09:50:23.728Z
Learning: Repo: manaflow-ai/cmux — In CLI/cmux.swift within CMUXCLI.buildInteractiveRemoteShellScript(...), never export CMUX_TAB_ID from the workspace UUID. CMUX_TAB_ID must be surface-scoped: only set it when a surface ID is available (map CMUX_TAB_ID to CMUX_SURFACE_ID). Rationale: tab-action/rename-tab resolve CMUX_TAB_ID before CMUX_SURFACE_ID; workspace-scoped values misroute or fail.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2514
File: CLI/cmux.swift:9691-9711
Timestamp: 2026-04-01T22:58:26.254Z
Learning: Repo: manaflow-ai/cmux — In CLI/cmux.swift (runClaudeTeams), custom Claude path resolution now trims whitespace and rejects paths that point to the cmux wrapper using isCmuxClaudeWrapper(), before falling back to PATH/bundled. The Resources/bin/claude wrapper also resolves the real path and compares against itself, requiring -f/-x to avoid recursion/self-reference.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2525
File: Sources/GhosttyTerminalView.swift:481-513
Timestamp: 2026-04-02T10:13:39.235Z
Learning: Repo: manaflow-ai/cmux — In Sources/GhosttyTerminalView.swift, terminal file-link resolution trims trailing unmatched closing delimiters “) ] } >” only when they are dangling (more closers than openers), preserving wrapped tokens like “(file:///tmp/a.png)”. Implemented via terminalFileLinkTrailingClosingDelimiters and count comparison inside trimTrailingTerminalFileLinkPunctuation(_:) and exercised by a regression test (PR `#2525`, commit 3f5c5b6d).

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-04-05T21:26:10.684Z
Learning: Repo: manaflow-ai/cmux — In Sources/Workspace.swift, applySessionPanelMetadata() must gate listeningPorts restoration on the per-panel snapshot.terminal?.isRemoteBacked flag (not workspace-wide remoteTerminalStartupCommand()), so that local panels are always eligible for port restore regardless of current SSH state. Fixed in commit 4d0fd871 (PR `#2545`).

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: Sources/TerminalController.swift:3620-3622
Timestamp: 2026-03-25T00:32:54.735Z
Learning: When validating or reporting workspace/tab colors in this repo, only accept and use 6-digit hex colors in the form `#RRGGBB` (no alpha, i.e., do not allow `#RRGGBBAA`). Ensure validation logic matches the existing behavior (e.g., WorkspaceTabColorSettings.normalizedHex(...) and TabManager.setTabColor(tabId:color:) as well as CLI/cmux.swift). Update any error/help text for workspace color to reference only `#RRGGBB` (not `#RRGGBBAA`).

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/AppDelegate.swift:3491-3493
Timestamp: 2026-03-04T14:06:12.296Z
Learning: In PRs affecting this repository, limit the scope of localization (i18n) changes to Japanese translations for the file Sources/AppDelegate.swift. Do not include UX enhancements (e.g., preferring workspace.customTitle in workspaceDisplayName() or altering move-target labels) in this PR. Open a separate follow-up issue to address any UX-related changes to avoid scope creep and keep localization review focused.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: CLI/cmux.swift:1958-1960
Timestamp: 2026-03-25T00:33:00.675Z
Learning: Repo: manaflow-ai/cmux — CLI/cmux.swift set-workspace-color intentionally accepts both "#RRGGBB" and "RRGGBB" for ergonomics, matching server-side WorkspaceTabColorSettings.normalizedHex/TabManager normalization. Do not suggest enforcing a mandatory leading '#'; at most, suggest clarifying help/usage text.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2514
File: Sources/GhosttyTerminalView.swift:3759-3761
Timestamp: 2026-04-01T22:57:41.165Z
Learning: Repo: manaflow-ai/cmux — In Sources/cmuxApp.swift, ClaudeCodeIntegrationSettings.customClaudePath(defaults:) trims surrounding whitespace and returns nil for empty/whitespace-only values; callers (e.g., TerminalSurface.createSurface(for:)) can safely set CMUX_CUSTOM_CLAUDE_PATH without additional trimming.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-03-04T14:05:42.574Z
Learning: Guideline: In Swift files (cmux project), when handling pluralized strings, prefer using localization keys with the ICU-style plural forms .one and .other. For example, use keys like statusMenu.unreadCount.one for the singular case (1) and statusMenu.unreadCount.other for all other counts, and similarly for statusMenu.tooltip.unread.one/other. Rationale: ensures correct pluralization across locales and makes localization keys explicit. Review code to ensure any unread count strings and related tooltips follow this .one/.other key pattern and verify the correct value is chosen based on the count.

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 954
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-05T22:04:34.712Z
Learning: Adopt the convention: for health/telemetry tri-state values in Swift, prefer Optionals (Bool?) over sentinel booleans. In TerminalController.swift, socketConnectable is Bool? and only set when socketProbePerformed is true; downstream logic must treat nil as 'not probed'. Ensure downstream code checks for nil before using a value and uses explicit non-nil checks to determine state, improving clarity and avoiding misinterpretation of default false.

Learnt from: moyashin63
Repo: manaflow-ai/cmux PR: 1074
File: Sources/AppDelegate.swift:7523-7545
Timestamp: 2026-03-09T01:38:24.337Z
Learning: When the command palette is visible (as in manaflow-ai/cmux Sources/AppDelegate.swift), ensure the shortcut handling consumes most Command shortcuts to protect the palette's text input. Specifically, do not allow UI zoom shortcuts (Cmd+Shift+= / Cmd+Shift+− / Cmd+Shift+0) to trigger while the palette is open. Do not reorder shortcut handlers (e.g., uiZoomShortcutAction(...)) to bypass this guard; users must close the palette before performing zoom actions. This guideline should apply to Swift source files handling global shortcuts within the app.

Learnt from: zlatkoc
Repo: manaflow-ai/cmux PR: 1368
File: Sources/Panels/BrowserPanel.swift:69-69
Timestamp: 2026-03-13T13:46:01.733Z
Learning: Do not wrap engine/brand name literals (e.g., displayName values such as Google, DuckDuckGo, Bing, Kagi, Startpage) in String(localized: ...). These are brand/product names that are not translatable UI text. Localization should apply to generic UI strings (labels, buttons, error messages, etc.). Apply this guideline across Swift source files under Sources/ (notably in BrowserPanel.swift and similar UI/engine-related strings) and flag only brand-name strings that are part of user-facing UI text appropriately for translation scope.

Learnt from: jt-hsiao
Repo: manaflow-ai/cmux PR: 1423
File: Sources/AppDelegate.swift:11220-11226
Timestamp: 2026-03-14T07:05:52.379Z
Learning: In Sources/AppDelegate.swift (within the manaflow-ai/cmux repo), ensure that NSWindow.cmux_performKeyEquivalent forwards to firstResponderWebView.performKeyEquivalent and returns its Bool unconditionally. This prevents re-entry into SwiftUI’s performKeyEquivalent path that can swallow keys when WKWebView has focus, and aligns with the Command-key routing chain implemented in Sources/Panels/CmuxWebView.swift: (1) route to NSApp.mainMenu.performKeyEquivalent when allowed, (2) fall back to AppDelegate.shared?.handleBrowserSurfaceKeyEquivalent(event) for non-menu shortcuts, (3) fall back to super.performKeyEquivalent. Non-Command keys should still call super directly.

Learnt from: jt-hsiao
Repo: manaflow-ai/cmux PR: 1423
File: Sources/AppDelegate.swift:11220-11226
Timestamp: 2026-03-14T07:05:52.379Z
Learning: In Sources/AppDelegate.swift for the manaflow-ai/cmux repository, Cmd+` (command-backtick) should not be routed directly to the main menu. Do not implement a window/main-menu bypass for this key. Tests will assert this behavior, so ensure routing logic respects this exclusion and that there is no path that bypasses the main menu for Cmd+`. If similar key-command bypass considerations exist in other files, apply the same explicit exclusion only where applicable.

Learnt from: kjb0787
Repo: manaflow-ai/cmux PR: 1461
File: Sources/GhosttyTerminalView.swift:5904-5905
Timestamp: 2026-03-15T19:22:32.330Z
Learning: In Swift files under the Sources directory that manage terminal/scroll behavior, ensure the following: when preserving scroll across workspace switches, save savedScrollRow only if the scrollbar offset is greater than 0 (indicating the user has scrolled up). On restore, call scroll_to_row only if savedScrollRow is non-nil; if it is nil, rely on synchronizeScrollView() to keep bottom-pinned sessions following new output. This pattern should be applied wherever GhosttyTerminalView-like views implement setVisibleInUI(_:) to maintain consistent user scroll state across workspace switches.

Learnt from: MaTriXy
Repo: manaflow-ai/cmux PR: 1460
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-16T08:02:06.824Z
Learning: In Swift sources, for any panel_id-only route handling in v2PanelMarkBackground(params:) and v2PanelMarkForeground(params:), first attempt v2ResolveTabManager(params:). Use the manager only if it actually owns the panelId; otherwise fall back to AppDelegate.shared?.locateSurface(surfaceId:) to locate the correct TabManager across windows. Apply this pattern to all panel_id-only routes to avoid active-window bias.

Learnt from: pratikpakhale
Repo: manaflow-ai/cmux PR: 2011
File: Resources/Localizable.xcstrings:15256-15368
Timestamp: 2026-03-23T21:39:50.795Z
Learning: When reviewing this repo’s Swift localization usage, do not flag missing `String.localizedStringWithFormat` for calls that use the modern overload `String(localized: "key", defaultValue: "...\(variable)")` (where `defaultValue` is a `String.LocalizationValue` built with `\(…)`). That overload natively supports interpolation and the xcstrings/runtime substitution handles the resulting placeholders automatically. Only require `String.localizedStringWithFormat` when using the older `String(localized:)` overload that takes a plain `String` (i.e., where format arguments must be passed separately), such as for keys like `clipboard.sshError.single`.

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 2545
File: Sources/GhosttyTerminalView.swift:3891-3903
Timestamp: 2026-04-02T21:37:21.463Z
Learning: In Swift source files like Sources/GhosttyTerminalView.swift, avoid logging raw startup commands or initialInput even in DEBUG (to prevent leaking sensitive paths/tokens and multiline content). If you need to diagnose startup/input, log only non-sensitive metadata such as (1) presence flags (e.g., hasStartupCommand/hasInitialInput), (2) byte counts, and (3) the relevant surface id (so issues can be correlated without exposing the underlying strings).

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/cmuxApp.swift:6439-6444
Timestamp: 2026-04-03T03:35:54.082Z
Learning: In this repo’s keyboard shortcut implementation, ensure `KeyboardShortcutSettings.setShortcut(...)` does nothing (no-op) when `KeyboardShortcutSettings.isManagedBySettingsFile(action)` returns `true` (i.e., the shortcut is managed via `settings.json`). This prevents writing back overrides into `UserDefaults` and keeps `settings.json` as the source of truth.

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 2505
File: Sources/AppDelegate.swift:5636-5642
Timestamp: 2026-04-06T07:18:38.685Z
Learning: In the AppDelegate.swift keyDown focus-repair path (and related surface keyboard-focus matching like GhosttySurfaceScrollView.swift), do not dereference NSTextView.delegate when it is unsafe-unretained. Instead, resolve the field-editor ownership via cmuxFieldEditorOwnerView(_). For keyboard-focus matching, prefer superview/nextResponder traversal or hostedView.responderMatchesPreferredKeyboardFocus(...), and ensure any matches are performed using the owned responder chain rather than reading an unsafe delegate pointer.

Copilot AI review requested due to automatic review settings April 3, 2026 07:48

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

🧹 Nitpick comments (2)
docs/examples/cmux-tab-sync.sh (2)

10-11: Consider validating CMUX_SURFACE_ID since it's used for the rename operation.

The script checks CMUX_WORKSPACE_ID but uses CMUX_SURFACE_ID at line 158. While both are set together by cmux (per GhosttyTerminalView.swift:3330-3331), adding a guard for CMUX_SURFACE_ID makes the script more self-documenting and defensive.

🛡️ Suggested fix
 [ -z "$CMUX_WORKSPACE_ID" ] && exit 0
+[ -z "$CMUX_SURFACE_ID" ] && exit 0
 command -v cmux &>/dev/null || exit 0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/examples/cmux-tab-sync.sh` around lines 10 - 11, Add a defensive check
for CMUX_SURFACE_ID alongside the existing CMUX_WORKSPACE_ID check: ensure the
script verifies that CMUX_SURFACE_ID is non-empty (similar to the existing [ -z
"$CMUX_WORKSPACE_ID" ] && exit 0 guard) before proceeding, and exit early if it
is unset, since CMUX_SURFACE_ID is later used for the rename operation; update
the initial conditional that checks command -v cmux to also validate
CMUX_SURFACE_ID to make the script self-documenting and prevent downstream
failures.

64-64: File path pattern only matches absolute paths.

The regex (/[a-zA-Z0-9._-]+){2,} requires paths to start with /. Relative paths like ./config/secrets.json or multi-segment paths like src/auth/tokens.py won't be redacted and could leak into tab names.

Based on learnings: "pre-redact sensitive spans... filesystem paths" should cover both absolute and relative paths.

🔒 Suggested fix to also catch relative paths
-    text = re.sub(r'(/[a-zA-Z0-9._-]+){2,}', '', text)  # file paths
+    text = re.sub(r'(?:\.{0,2}/)?(?:[a-zA-Z0-9._-]+/){1,}[a-zA-Z0-9._-]+', '', text)  # file paths (absolute and relative)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/examples/cmux-tab-sync.sh` at line 64, The current redaction only
matches absolute paths in the line with text = re.sub(r'(/[a-zA-Z0-9._-]+){2,}',
'', text); update the regex to also match relative paths (e.g., ./, ../, or bare
multi-segment paths) by allowing an optional leading ./ or ../ or / and
requiring at least two path segments, for example using a pattern like
r'(?:(?:\./|\.\./|/)?[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+){1,})' (apply this in
the same text = re.sub call) so both absolute and relative multi-segment
filesystem paths are redacted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/examples/cmux-tab-sync.sh`:
- Around line 10-11: Add a defensive check for CMUX_SURFACE_ID alongside the
existing CMUX_WORKSPACE_ID check: ensure the script verifies that
CMUX_SURFACE_ID is non-empty (similar to the existing [ -z "$CMUX_WORKSPACE_ID"
] && exit 0 guard) before proceeding, and exit early if it is unset, since
CMUX_SURFACE_ID is later used for the rename operation; update the initial
conditional that checks command -v cmux to also validate CMUX_SURFACE_ID to make
the script self-documenting and prevent downstream failures.
- Line 64: The current redaction only matches absolute paths in the line with
text = re.sub(r'(/[a-zA-Z0-9._-]+){2,}', '', text); update the regex to also
match relative paths (e.g., ./, ../, or bare multi-segment paths) by allowing an
optional leading ./ or ../ or / and requiring at least two path segments, for
example using a pattern like
r'(?:(?:\./|\.\./|/)?[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+){1,})' (apply this in
the same text = re.sub call) so both absolute and relative multi-segment
filesystem paths are redacted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fb3bf2e0-3ab8-428a-8f86-7d340c73c346

📥 Commits

Reviewing files that changed from the base of the PR and between 6c4a74a and 0e1ba6a.

📒 Files selected for processing (1)
  • docs/examples/cmux-tab-sync.sh

Copilot AI 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.

Pull request overview

This PR adds documentation + example Claude Code hook scripts for improved cmux tab/workspace naming, and also includes fixes to improve terminal portal geometry syncing during restore and to stabilize UI test app activation (plus CI retry logic for a known flake).

Changes:

  • Add docs and example hook scripts to auto-name tabs from transcript summaries and sync /rename to the cmux workspace name.
  • Fix restore-time portal geometry issues by scheduling an additional external geometry sync after binding, and add a regression test.
  • Improve UI test launch/foreground stabilization in-app, and add a CI retry for a known “Running Background” activation failure.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
Sources/TerminalWindowPortal.swift Queues an extra external geometry sync after bind to catch delayed restore/layout shifts.
Sources/AppDelegate.swift Adds retry-based UI test window/foreground stabilization to reduce launch flakes.
cmuxTests/TerminalAndGhosttyTests.swift Adds a lifecycle regression test covering queued layout shifts after bind.
.github/workflows/ci.yml Retries the display-resolution UI test once on a specific activation flake signature.
docs/claude-code-hooks-integration.md Documents hook setup and the naming/sync architecture.
docs/examples/cmux-tab-sync.sh Stop hook: transcript → keyword summary → cmux rename-tab.
docs/examples/cmux-rename-sync.sh UserPromptSubmit hook: /rename → cmux rename-workspace.
docs/examples/cmux-session-start.sh SessionStart hook: reset workspace name to cwd basename.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1133 to +1136
// Session/window restore can queue additional ancestor layout shifts (sidebar width,
// split positions) after the initial bind tick. Queue a later external sync so the
// portal catches that settled geometry instead of staying at the seeded frame.
scheduleExternalGeometrySynchronize()

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

PR title/summary indicate a docs-only change, but this PR also includes functional Swift code changes (portal geometry sync, UI test launch stabilization) and CI workflow tweaks. Please update the PR title and/or top-level description so reviewers and release notes reflect the non-doc behavior changes.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,159 @@
#!/bin/bash
# Hook: Sync Claude Code session focus to cmux tab/workspace name

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

The file header says this hook syncs the session focus to the "cmux tab/workspace name", but the script only calls cmux rename-tab (workspace renaming is handled elsewhere). Update the comment to avoid misleading readers about what this hook changes.

Suggested change
# Hook: Sync Claude Code session focus to cmux tab/workspace name
# Hook: Sync Claude Code session focus to cmux tab name

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +19
TRANSCRIPT=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('transcript_path',''))" 2>/dev/null || true)
[ -z "$TRANSCRIPT" ] && exit 0
[ -f "$TRANSCRIPT" ] || exit 0

LABEL=$(_TRANSCRIPT="$TRANSCRIPT" python3 -c "
import json, re, os, sys, collections

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

LABEL=$( ... python3 -c ... ) isn't guarded. With set -e, if python3 is missing or that command exits non-zero, the hook will exit non-zero and may disrupt Claude Code's hook pipeline. Consider adding command -v python3 >/dev/null || exit 0 and/or appending || true to the label extraction so the hook always no-ops safely.

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +23
# Fast: grep last custom-title from transcript tail
TITLE=$(tail -500 "$TRANSCRIPT" | grep '"custom-title"' | tail -1 | python3 -c "
import sys,json
try:
print(json.loads(sys.stdin.readline()).get('customTitle',''))
except: pass
" 2>/dev/null)

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

This script invokes python3 in the TITLE pipeline without guarding it. Because the script runs with set -e, a missing python3 (or a non-zero exit) will cause the hook to exit non-zero rather than silently no-oping. Add a command -v python3 >/dev/null || exit 0 check and/or make the TITLE extraction tolerant (e.g., ... 2>/dev/null || true).

Copilot uses AI. Check for mistakes.
Horacehxw added a commit to Horacehxw/cmux that referenced this pull request Apr 6, 2026
Address two CodeRabbit review issues (PR manaflow-ai#1980):

1. Replace didRequestFallbackUITestWindow bool with fallbackUITestWindow: NSWindow?
   Store a weak reference to the force-created window so registerMainWindow can
   close it when a real WindowGroup window appears, preventing two main windows.

2. Only call moveUITestWindowToTargetDisplayIfNeeded() on attempt == 0
   The helper already schedules its own 20-step retry; calling it on every outer
   loop pass fanned out into overlapping timers that raced each other.
@cubic-dev-ai

cubic-dev-ai Bot commented Apr 6, 2026

Copy link
Copy Markdown

This review could not be run because your cubic account has exceeded the monthly review limit. If you need help restoring access, please contact contact@cubic.dev.

austinywang and others added 6 commits April 27, 2026 16:50
…anaflow-ai#1973)

* test: cover queued restore-time terminal portal shift

* fix: resync terminal portal after restore-time bind
Add documentation and example hook scripts that enhance cmux's Claude
Code integration with:

- Auto-generated tab names from session transcript (Stop hook)
- Instant /rename → workspace name sync (UserPromptSubmit hook)
- Workspace name reset on new session (SessionStart hook)

These hooks complement cmux's native claude-hook integration by adding
concise, keyword-based tab summaries and ensuring /rename changes
propagate to cmux immediately.
- Add `|| true` to all python3 command substitutions under `set -e`
  to prevent hook failures from blocking UserPromptSubmit
- Fix misleading "no Python" comment in cmux-rename-sync.sh
- Replace f.readlines() with head/tail subprocess for true partial
  transcript reading (only first 300 + last 200 lines)
- Fix word-count cap: 2-5 words (was 2-4), matching documentation
…ction

Before tokenizing transcript content for tab naming, strip:
- UUIDs (session IDs, request IDs)
- Email addresses
- File paths (2+ segments)
- ENV_VAR=value assignments
- Long numeric sequences (6+ digits)

This prevents sensitive data from leaking into visible tab names.
Address two CodeRabbit review issues (PR manaflow-ai#1980):

1. Replace didRequestFallbackUITestWindow bool with fallbackUITestWindow: NSWindow?
   Store a weak reference to the force-created window so registerMainWindow can
   close it when a real WindowGroup window appears, preventing two main windows.

2. Only call moveUITestWindowToTargetDisplayIfNeeded() on attempt == 0
   The helper already schedules its own 20-step retry; calling it on every outer
   loop pass fanned out into overlapping timers that raced each other.
@Horacehxw
Horacehxw force-pushed the docs/claude-code-tab-sync-hooks branch from ea72ddc to ea32878 Compare April 27, 2026 08:52
@Horacehxw

Copy link
Copy Markdown
Author

Thanks for the review. A few notes:

P1 / P2 (set -e + JSON parse failure): Both cmux-rename-sync.sh:13 and cmux-session-start.sh:14 already handle this with 2>/dev/null || true — the || true ensures the command always exits 0, so set -e never aborts on a parse failure. This was the intentional pattern.

P3 (transcript memory): The latest version of cmux-tab-sync.sh (commit 6c4a74a8) already uses a bounded head -300 + tail -200 strategy via subprocess calls, not full in-memory load.

Also rebased the branch on upstream/main — the CI conflict in ci.yml is now resolved.

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

🧹 Nitpick comments (1)
docs/claude-code-hooks-integration.md (1)

107-115: Consider adding a language specifier to the fenced code block.

The architecture diagram would benefit from an explicit language identifier (e.g., text or plaintext) to satisfy markdownlint and improve rendering consistency across viewers.

📋 Suggested fix
-```
+```text
 Workspace name (sidebar):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/claude-code-hooks-integration.md` around lines 107 - 115, The fenced
code block containing "Workspace name (sidebar):" and "Tab name (tab bar):"
lacks a language specifier; update that block (the triple-backtick that opens
the architecture diagram) to include a plain-text language identifier such as
text or plaintext so markdownlint passes and rendering is consistent (e.g.,
change ``` to ```text at the start of the block that contains the listed
priorities and tab rules).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/claude-code-hooks-integration.md`:
- Around line 107-115: The fenced code block containing "Workspace name
(sidebar):" and "Tab name (tab bar):" lacks a language specifier; update that
block (the triple-backtick that opens the architecture diagram) to include a
plain-text language identifier such as text or plaintext so markdownlint passes
and rendering is consistent (e.g., change ``` to ```text at the start of the
block that contains the listed priorities and tab rules).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b6abf04a-5f7b-4ed6-9a0c-0809443d6182

📥 Commits

Reviewing files that changed from the base of the PR and between ea72ddc and ea32878.

📒 Files selected for processing (7)
  • Sources/AppDelegate.swift
  • Sources/TerminalWindowPortal.swift
  • cmuxTests/TerminalAndGhosttyTests.swift
  • docs/claude-code-hooks-integration.md
  • docs/examples/cmux-rename-sync.sh
  • docs/examples/cmux-session-start.sh
  • docs/examples/cmux-tab-sync.sh
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/examples/cmux-session-start.sh
  • docs/examples/cmux-rename-sync.sh
  • docs/examples/cmux-tab-sync.sh

@teamleaderleo teamleaderleo added area: docs README, docs pages, the site, contributor guides S4: cosmetic Wording or appearance, with no effect on what cmux does labels Sep 30, 2026

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

area: docs README, docs pages, the site, contributor guides S4: cosmetic Wording or appearance, with no effect on what cmux does

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants