URL scheme to deep-link a notification click to a specific conversation

Open 💬 2 comments Opened Jun 12, 2026 by athorel35

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

Use case
Power-user workflow with multiple parallel Claude Code conversations (several worktrees, several projects). I run hooks that fire macOS notifications via terminal-notifier:

Notification hook → "Claude is waiting for your input" (permission prompt or idle)
Stop hook → "Turn finished"
SubagentStop hook → "Subagent finished"
When the notification arrives, I'd like a click on the notification to bring Claude.app to the front AND select the exact conversation that fired the event, regardless of which conversation/section is currently active.

Current state
claude:// URL scheme exists, but only for claude://claude.ai/mcp-auth-callback/sdk and claude://cowork/shared-artifact?uuid=… (verified by grepping app.asar).
tell application "Claude" to activate brings the app forward (Apple Events permission already granted).
Hooks receive session_id and transcript_path in the JSON payload — so the identity of the originating conversation is available at notification time.
Limitations encountered
No URL scheme to target a session. I'd expect something like claude://session/<session_id> or claude://transcript?path=<urlencoded_path> that activates the app AND switches to that conversation. open claude://session/abc123 from a shell would resolve the whole "deep-link a notification" problem in one line.
The Cmd+K switcher defaults Enter to "New chat". Even when the user types text that exactly matches an existing conversation, the highlighted/Enter-default action is New chat "<typed text>", pushing the matching Résultats de recherche item to a lower row. Scripting "open the matching conversation" via keystrokes is therefore fragile: blind Down navigation is racy in Electron, the number of Actions rapides varies (3 when empty, 2 when searching, sometimes 1), and a misfire creates an unwanted new chat (named after the search term, which then appears in history).
All conversations live inside a single Electron webview, so AX APIs (System Events) only see one window titled Claude — no way to discriminate between sessions from the outside.
Proposal (in priority order)
Primary — URL scheme:

claude://session/<session_id>
claude://transcript?path=<file:URL-encoded transcript_path>
Both should bring Claude.app to the front and switch to that conversation. Matching by session_id is cleanest; transcript_path is a useful fallback because hooks already provide it verbatim.

Secondary — minor switcher UX:

When Résultats de recherche contains at least one match, make Enter select the first search result by default (rather than New chat). Cmd+Enter could remain the explicit "New chat with this text" escape hatch.

Why it matters
This is the standard pattern across desktop apps that integrate with OS notifications: Slack (slack://channel?team=…&id=…), Discord (discord://…), VSCode (vscode://file/…), Notion (notion://…/<page_id>). Without it, Claude Code hooks can notify the user, but the click is a dead end — they have to manually find the right conversation in a long sidebar.

Hooks already know which session_id originated the event, so the data is there — only an exposed URL handler is missing.

My current workaround
terminal-notifier notification with the conversation's customTitle (or aiTitle) shown as the subtitle, click → tell application "Claude" to activate. The user then identifies the conversation visually in the sidebar and clicks it. Functional but a clear 2-click gap that a URL scheme would close.

Happy to share the full hook setup if useful for repro / examples.

Proposed Solution

Add a claude:// URL scheme that opens Claude.app on a specific conversation:

claude://session/<session_id> — opens the conversation matching that session id
claude://transcript?path=<URL-encoded transcript file path> — same, identified by transcript path (already provided to hooks)
When opened (e.g. open claude://session/abc123 from a shell, or clicking a notification whose action URL is one of these), Claude.app should:

Come to the foreground (same as open -a Claude).
Switch to the conversation matching the id/path, regardless of which conversation or section is currently active — including across sections (Chat, Claude Code, Cowork).
If the id/path is unknown (deleted, never existed), fall back to plain activate. No error dialog, no new chat created.
The hook payload already exposes both session_id and transcript_path in the Notification, Stop, and SubagentStop events — so on macOS a hook can do:

SESSION_URL="claude://session/$session_id"
terminal-notifier \
-title "Claude · $custom_title" \
-message "Permission requested" \
-open "$SESSION_URL"
…and clicking the notification deep-links to the exact originating conversation.

Secondary UX tweak (orthogonal but related): in the Cmd+K conversation switcher, when the typed query has at least one matching item under Résultats de recherche, make Enter select that first search result by default (today it defaults to New chat "<typed text>", which causes accidental new chats when scripts try to keystroke their way to an existing conversation). Cmd+Enter could remain the explicit "create new chat with this text" shortcut.

Alternative Solutions

terminal-notifier + customTitle in subtitle. I parse transcript_path from the hook payload, read the customTitle (or aiTitle fallback) from the first lines of the .jsonl, and pass it to terminal-notifier -subtitle "$title". The notification reads e.g. "🔐 Claude demande une permission · OUTILS TF-TOOL". Click triggers tell application "Claude" to activate only — the user then visually scans the sidebar and clicks the conversation themselves. Works, but it's a 2-click gap.
Tried scripting the Cmd+K switcher via AppleScript keystrokes. Sequence: tell application "Claude" to activate → wait → keystroke "k" using command down → keystroke "<conversation title>" → key code 125 (Down) ×N → keystroke return. This created accidental new chats in practice, because:
New chat "<typed text>" is the default Enter target even when a search result matches exactly.
The number of Actions rapides rows varies with context (3 when search is empty, 2 when searching), so a fixed Down count is unreliable.
Electron + arrow key timing leads to race conditions where the selection state didn't update before Enter fired.
Result: 4 unintended "OUTILS TF-TOOL" chats appeared in my history during testing. Abandoned this approach as unsafe.

Looked for an existing scheme. grep'd claude:// patterns in Claude.app/Contents/Resources/app.asar — only found claude://claude.ai/mcp-auth-callback/sdk and claude://cowork/shared-artifact?uuid=. No conversation deep-link exists.
Considered window-title matching via System Events. Claude.app exposes a single AX window titled "Claude" regardless of how many conversations are open (everything is rendered inside one Electron webview). No way to discriminate sessions from outside the webview.

Priority

Medium - Would be very helpful

Feature Category

Developer tools/SDK

Use Case Example

Example scenario:

I'm working on multiple Claude Code conversations in parallel — 3 different worktrees of the same project, each handling a separate task (data migration in one, planning a refactor in another, debugging in a third). My setup uses Notification, Stop, and SubagentStop hooks that fire macOS notifications via terminal-notifier, so I can leave Claude in the background while reading docs, Slack, or my email.
I need to click a notification and land directly on the originating conversation, not on whatever conversation Claude.app last had focused. The notification already knows which session_id fired it (it's in the hook payload) — I just need the OS-level handler to act on that.
With this feature, I could pass claude://session/$session_id as the -open argument to terminal-notifier. Clicking the notification would:
Bring Claude.app to the foreground
Switch to the exact conversation that fired the event, across sections (Chat / Claude Code / Cowork)
No keystroke scripting, no race conditions, no risk of accidentally creating a new chat
This would save me time because today, after each notification, I have to: foreground Claude → scan the sidebar → find the right conversation among 5+ recents → click it. ~4 clicks, ~4 seconds. Repeated 30–50× per day with parallel sessions, that's a measurable chunk of context-switching tax. A URL scheme cuts it to 1 click and ~0.3 s, and removes the cognitive load of "which one was that again?".

Additional Context

<img width="1104" height="735" alt="Image" src="https://github.com/user-attachments/assets/bbdf5832-4cb6-46cb-95bd-4bc75890ac0e" />

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗