Skip to content

fix: prevent Cmd+Shift+U escape sequence leak in kitty protocol mode - #2237

Closed
anthhub wants to merge 1 commit into
manaflow-ai:mainfrom
anthhub:fix/cmd-shift-u-escape-sequence
Closed

anthhub wants to merge 1 commit into
manaflow-ai:mainfrom
anthhub:fix/cmd-shift-u-escape-sequence

Conversation

@anthhub

@anthhub anthhub commented Mar 27, 2026 •

Copy link
Copy Markdown

Fixes #2198

When no unread notifications exist, pressing Cmd+Shift+U would output the escape sequence [12629;10u into the terminal. This happened because the kitty keyboard protocol encodes key events differently, causing matchShortcut to fail character matching and letting the event pass through to the terminal.

Root Cause

In matchShortcut, the layout-character match (shortcutLayoutCharacterProvider, which uses keyCode → keyboard layout translation) was placed after an early-return guard that blocked further matching for letter shortcuts when charactersIgnoringModifiers was non-empty ASCII but didn't match the shortcut key. In kitty protocol mode, charactersIgnoringModifiers carries a synthetic value, so the guard fired before the reliable keyCode-based translation had a chance to run.

Fix

Move the shortcutLayoutCharacterProvider match before the guard. The keyCode → layout translation reliably identifies the physical key (e.g. keyCode 32 → "u") regardless of keyboard protocol mode. The guard is preserved after both character sources have been tried, so cross-layout collision protection for letter shortcuts remains intact.

Test plan

  • Press Cmd+Shift+U with no unread notifications → should do nothing (no escape sequence output)
  • Press Cmd+Shift+U with unread notifications → should jump to latest unread
  • Test with kitty keyboard protocol enabled (echo -e '\e[>1u')
  • Verify other letter shortcuts (Cmd+Shift+P, Cmd+N, etc.) still work correctly on non-US keyboard layouts

Summary by cubic

Fixes #2198. Stops Cmd+Shift+U from printing a kitty keyboard protocol escape sequence when there are no unread notifications by matching the keyCode→layout character before the early guard.

  • Bug Fixes
    • Move layout-character match ahead of the ASCII guard so letter shortcuts are recognized in kitty protocol mode; Cmd+Shift+U no longer passes through.
    • Keep the guard after both character sources to avoid cross-layout collisions; other shortcuts still work on non‑US layouts.

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

Summary by CodeRabbit

  • Bug Fixes
    • Improved keyboard shortcut matching for Cmd-based letter shortcuts to enhance character recognition and behavior across different keyboard layouts.

…anaflow-ai#2198)

When the kitty keyboard protocol is active, charactersIgnoringModifiers
may carry a synthetic value that does not match the shortcut key. The
previous code checked this guard before attempting the keyCode-based
layout translation, causing matchShortcut to return false for letter
shortcuts (like Cmd+Shift+U) even though the keyCode and modifiers were
correct. The event then fell through to the terminal, producing the
escape sequence [12629;10u.

Fix by moving the layout-character match (shortcutLayoutCharacterProvider)
before the early-return guard. The keyCode→layout translation reliably
identifies the physical key regardless of keyboard protocol mode. The
guard is preserved after both character sources have been tried, so the
cross-layout collision protection for letter shortcuts remains intact.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Mar 27, 2026

Copy link
Copy Markdown

@anthhub 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 Mar 27, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adjusted the shortcut matching algorithm in AppDelegate to modify when keyCode-based fallback is permitted for Cmd-letter shortcuts. The layout-translation path now executes first, and a new blocking rule prevents keyCode fallback for Cmd-letter shortcuts when character matching is required.

Changes

Cohort / File(s) Summary
Cmd-Letter Shortcut Matching Logic
Sources/AppDelegate.swift
Reorganized keyboard event matching to always attempt layout-based translation first. Removed early rejection guard for Cmd shortcuts with ASCII characters. Added new blocking rule post-layout-matching that prevents keyCode fallback for Cmd-letter shortcuts when hasEventChars && eventCharsAreASCII && flags.contains(.command) && !flags.contains(.control) and character matching is required. The removal of hasUsableEventChars intermediate state alters conditions for ANSI keyCode fallback in Cmd shortcuts.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

A rabbit hops through keyboard keys, 🐰
Where Cmd and letters dance with ease,
No more escape sequences in the way,
Unread notifications saved the day!
Layout-first, the shortcuts say hooray! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically identifies the main change: preventing Cmd+Shift+U escape sequence leak in kitty protocol mode, which aligns with the primary objective in the PR.
Description check ✅ Passed The description provides a comprehensive summary of the problem, root cause, and solution, with detailed test plan; however it lacks evidence of local testing and bot review requests in the checklist sections.
Linked Issues check ✅ Passed The code changes directly address the core requirement of issue #2198 by reordering character matching logic to ensure Cmd+Shift+U is reliably intercepted regardless of kitty protocol mode, preventing escape-sequence leakage.
Out of Scope Changes check ✅ Passed All changes are confined to AppDelegate.swift shortcut-matching logic and directly target the kitty protocol escape sequence issue; no unrelated modifications are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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.

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

No issues found across 1 file

@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)
Sources/AppDelegate.swift (1)

10683-10703: Consider caching the translated layout character once per event.

handleCustomShortcut(event:) probes matchShortcut(event:shortcut:) many times for the same NSEvent. Now that this lookup runs before the Cmd-letter guard, each non-matching Cmd-letter probe repeats the same keyboard-layout translation work. Precomputing the translated character once in handleCustomShortcut(event:) and threading it through here would keep the fix while trimming that overhead.

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

In `@Sources/AppDelegate.swift` around lines 10683 - 10703, The code repeatedly
calls shortcutLayoutCharacterProvider for the same NSEvent causing redundant
layout translations when handleCustomShortcut(event:) invokes
matchShortcut(event:shortcut:) multiple times; modify
handleCustomShortcut(event:) to compute the translated layout character once
(e.g., call shortcutLayoutCharacterProvider(event.keyCode, event.modifierFlags)
and store it) and pass that precomputed character into
matchShortcut(event:shortcut:) (and/or change matchShortcut signature or add an
overload/parameter to accept a precomputed layoutCharacter) so that the existing
Cmd-letter guard block (which compares eventCharacter: layoutCharacter) uses the
cached value instead of recomputing it each probe.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Sources/AppDelegate.swift`:
- Around line 10683-10703: The code repeatedly calls
shortcutLayoutCharacterProvider for the same NSEvent causing redundant layout
translations when handleCustomShortcut(event:) invokes
matchShortcut(event:shortcut:) multiple times; modify
handleCustomShortcut(event:) to compute the translated layout character once
(e.g., call shortcutLayoutCharacterProvider(event.keyCode, event.modifierFlags)
and store it) and pass that precomputed character into
matchShortcut(event:shortcut:) (and/or change matchShortcut signature or add an
overload/parameter to accept a precomputed layoutCharacter) so that the existing
Cmd-letter guard block (which compares eventCharacter: layoutCharacter) uses the
cached value instead of recomputing it each probe.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b03ff716-c2a1-41ba-93ad-325ffd688baa

📥 Commits

Reviewing files that changed from the base of the PR and between bcf2180 and fbb7d8f.

📒 Files selected for processing (1)
  • Sources/AppDelegate.swift

@greptile-apps

greptile-apps Bot commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a Cmd+Shift+U escape sequence leak in kitty keyboard protocol mode by reordering the matchShortcut function in AppDelegate.swift: the shortcutLayoutCharacterProvider (keyCode → keyboard layout) match is moved before the early-return guard that blocks further matching for letter shortcuts when charactersIgnoringModifiers is non-empty ASCII. In kitty protocol mode, charactersIgnoringModifiers carries a synthetic ASCII value (e.g. [12629;10u) that does not match the shortcut key, causing the old guard to fire and return false before the reliable layout-based translation could identify the physical key.\n\nKey changes:\n- shortcutLayoutCharacterProvider match now runs before the cross-layout collision guard — correctly identifying the key from keyCode regardless of what kitty mode puts in charactersIgnoringModifiers\n- The guard is preserved after both character sources are tried, maintaining collision protection on non-US layouts\n- The local hasUsableEventChars is removed; its use in allowANSIKeyCodeFallback is replaced by !hasEventChars, which is semantically equivalent (the !eventCharsAreASCII case is already covered by the preceding hasEventChars && !eventCharsAreASCII sub-condition)\n- Per CLAUDE.md, bug fixes should include a regression test in a two-commit structure. This PR omits a unit test, despite the scenario being directly testable via the existing NSEvent harness and shortcutLayoutCharacterProvider override already used in AppDelegateShortcutRoutingTests

Confidence Score: 4/5

Safe to merge; the reordering is logically correct and the hasUsableEventChars removal is semantically equivalent, but a regression test should accompany the fix per project policy.

The fix correctly moves the layout-character match before the cross-layout guard, which is exactly the minimal change needed to unblock the kitty protocol path. The hasUsableEventChars → hasEventChars substitution is provably equivalent. Non-US layout protection and ANSI fallback behaviour are preserved. The only remaining action is adding a regression test per CLAUDE.md policy, which is practical with the existing AppDelegateShortcutRoutingTests harness.

Sources/AppDelegate.swift (matchShortcut, lines 10652–10727) — the sole changed file; logic is correct but lacks regression test coverage.

Important Files Changed

Filename Overview
Sources/AppDelegate.swift Reorders matchShortcut so keyCode→layout translation runs before the early-return guard; also removes the now-redundant hasUsableEventChars local. Fix logic is sound but lacks a regression unit test.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[matchShortcut called] --> B{flags == shortcut.modifierFlags?}
    B -- No --> Z[return false]
    B -- Yes --> C{shortcutKey == Return?}
    C -- Yes --> D{keyCode 36 or 76?}
    D -- Yes --> T[return true]
    D -- No --> Z
    C -- No --> E[Try eventCharsIgnoringModifiers match]
    E -- Match --> T
    E -- No match --> F[Compute hasEventChars / eventCharsAreASCII]
    F --> G[Try layoutCharacter match - keyCode to layout translation]
    G -- Match --> T
    G -- No match --> H{hasEventChars AND ASCII AND Cmd AND letter shortcut?}
    H -- Yes --> Z
    H -- No --> I{allowANSIKeyCodeFallback? Ctrl / non-letter-Cmd / non-ASCII / empty+no-layout}
    I -- Yes --> J{keyCode == expectedKeyCode?}
    J -- Yes --> T
    J -- No --> Z
    I -- No --> Z
    style G fill:#d4edda,stroke:#28a745,color:#000
    style H fill:#fff3cd,stroke:#ffc107,color:#000
Loading

Reviews (1): Last reviewed commit: "fix: prevent Cmd+Shift+U escape sequence..." | Re-trigger Greptile

Comment thread Sources/AppDelegate.swift
Comment on lines 10683 to +10703
@@ -10699,6 +10690,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
return true
}

// For command-based shortcuts, once both character sources have been tried, block
// further keyCode fallback for letter shortcuts to avoid physical-key collisions
// across layouts. Non-ASCII characters (Russian, Korean, CJK, etc.) cannot match
// a Latin shortcut key, so always allow keyCode fallback in that case.
if hasEventChars,
eventCharsAreASCII,
flags.contains(.command),
!flags.contains(.control),
shouldRequireCharacterMatchForCommandShortcut(shortcutKey: shortcutKey) {
return false
}

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 Missing regression test for kitty protocol fix

CLAUDE.md requires a two-commit regression test structure for bug fixes: the test is added first (CI goes red), then the fix (CI goes green). This scenario is directly unit-testable using the existing harness — AppDelegateShortcutRoutingTests already overrides shortcutLayoutCharacterProvider and constructs synthetic NSEvents (see the Cyrillic-layout tests around line 3022). A companion test could look like:

// Kitty keyboard protocol: charactersIgnoringModifiers carries a synthetic ASCII
// escape sequence, but the keyCode-based layout translation resolves the physical key.
appDelegate.shortcutLayoutCharacterProvider = { keyCode, _ in
    keyCode == 32 ? "u" : nil  // kVK_ANSI_U
}
guard let event = NSEvent.keyEvent(
    with: .keyDown,
    location: .zero,
    modifierFlags: [.command, .shift],
    timestamp: 0,
    windowNumber: window.windowNumber,
    context: nil,
    characters: "[12629;10u",
    charactersIgnoringModifiers: "[12629;10u",  // synthetic kitty value
    isARepeat: false,
    keyCode: 32
) else { return }

// matchShortcut should return true (event consumed, no escape leak)
XCTAssertTrue(appDelegate.matchShortcut(event: event, shortcut: /* jumpToUnread shortcut */))

Without a regression test, a future refactor could silently reintroduce the ordering bug.

Context Used: CLAUDE.md (source)

@anthhub

anthhub commented Mar 27, 2026

Copy link
Copy Markdown
Author

Closing — this conflicts with PR #2216 (physical keyCode refactor by @lawrencecchen) which touches the same matchShortcut function with a larger rewrite. Will rebase on top of #2216 if the fix is still needed after that lands.

@anthhub anthhub closed this Mar 27, 2026
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.

Cmd+Shift+U outputs escape sequence when no notifications exist

1 participant