777genius/claude-notifications-go

macOS: click-to-focus fails silently when target terminal window is minimized (AXRaise is a no-op)

Open

#67 opened on Apr 10, 2026

View on GitHub
 (1 comment) (0 reactions) (0 assignees)Go (100 forks)auto 404
bughelp wanted

Repository metrics

Stars
 (759 stars)
PR merge metrics
 (PR metrics pending)

Description

Summary

On macOS, clicking a notification to focus a terminal window silently fails when the target window is minimized to the Dock. The focused app activates but the target window stays minimized, so nothing visible happens.

Affects both code paths in internal/notifier/ax_focus_darwin.go:

  • raiseWindowByAXDocument (Ghostty via AXDocument / OSC 7 file URL)
  • raiseWindowByAXTitle (other terminals via AXTitle folder matching)

Environment

  • macOS 14.5 (Sonoma, Darwin 23.5.0)
  • Plugin v1.36.6 (installed via marketplace)
  • Terminal: Ghostty (com.mitchellh.ghostty), but same bug applies to every terminal path that ends in AXRaise
  • Accessibility and Screen Recording permissions granted

Steps to reproduce

  1. Open Claude Code in Ghostty at e.g. ~/work.
  2. Minimize the Ghostty window (yellow traffic light).
  3. Trigger a notification (e.g., Stop event on task completion).
  4. Click the notification banner.

Expected: Ghostty activates AND the minimized window is restored (un-minimized) and raised.

Actual: Ghostty activates (app icon bounces / menu bar switches) but the window stays minimized in the Dock. Notification click has no visible effect.

Root cause

Both raiseWindowByAXDocument and raiseWindowByAXTitle iterate AXWindows, match the target window, then call:

```c AXUIElementPerformAction(w, CFSTR("AXRaise")); AXUIElementSetAttributeValue(appEl, CFSTR("AXFrontmost"), kCFBooleanTrue); ```

On macOS, AXRaise is a no-op on a window whose AXMinimized attribute is true. A minimized window must be un-minimized first via:

```c AXUIElementSetAttributeValue(w, CFSTR("AXMinimized"), kCFBooleanFalse); ```

NSRunningApplication activateWithOptions: (called in `activateByPID`) also does not un-minimize windows; it only brings the app's non-minimized windows forward.

Proposed fix

Before calling `AXRaise`, check and clear `AXMinimized` in both helpers. Minimal diff:

```diff --- a/internal/notifier/ax_focus_darwin.go +++ b/internal/notifier/ax_focus_darwin.go @@ raiseWindowByAXDocument @@ if (ok && strcmp(buf, fileURL) == 0) {

  •   	CFTypeRef minRef = NULL;
    
  •   	if (AXUIElementCopyAttributeValue(w, CFSTR(\"AXMinimized\"), &minRef) == kAXErrorSuccess && minRef) {
    
  •   		if (CFBooleanGetValue((CFBooleanRef)minRef)) {
    
  •   			AXUIElementSetAttributeValue(w, CFSTR(\"AXMinimized\"), kCFBooleanFalse);
    
  •   		}
    
  •   		CFRelease(minRef);
    
  •   	}
      	AXUIElementPerformAction(w, CFSTR(\"AXRaise\"));
      	AXUIElementSetAttributeValue(appEl, CFSTR(\"AXFrontmost\"), kCFBooleanTrue);
      	found = 1;
      }
    

@@ raiseWindowByAXTitle @@ if (matched) {

  •   	CFTypeRef minRef = NULL;
    
  •   	if (AXUIElementCopyAttributeValue(w, CFSTR(\"AXMinimized\"), &minRef) == kAXErrorSuccess && minRef) {
    
  •   		if (CFBooleanGetValue((CFBooleanRef)minRef)) {
    
  •   			AXUIElementSetAttributeValue(w, CFSTR(\"AXMinimized\"), kCFBooleanFalse);
    
  •   		}
    
  •   		CFRelease(minRef);
    
  •   	}
      	AXUIElementPerformAction(w, CFSTR(\"AXRaise\"));
      	AXUIElementSetAttributeValue(appEl, CFSTR(\"AXFrontmost\"), kCFBooleanTrue);
      	found = 1;
      	break;
      }
    

```

Setting `AXMinimized = false` triggers the Dock un-minimize animation identical to a manual Dock icon click. After that completes, `AXRaise` + `AXFrontmost` work as usual. No extra permissions needed (Accessibility is already a prerequisite for the existing calls).

A small delay (e.g. `usleep(150000)`) between the un-minimize and `AXRaise` may be needed on slower machines while the un-minimize animation plays; the existing `retryWindowFocus` backoff in Go (150/250/400 ms) should cover this.

Note on the other fallback path

`findSwitchAndActivate` + `switchToWindowSpace` uses `CGSCopySpacesForWindows` / `CGSManagedDisplaySetCurrentSpace` to cross Spaces, but `CGWindowListCopyWindowInfo` by default returns the minimized window's off-screen bounds, and the same `AXRaise` no-op applies afterwards. The same un-minimize step would fix that path as well.

Happy to open a PR with the full patch and tests if that's welcome.

Contributor guide