777genius / 777genius/agent-notifications

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

Open
#67 1 comment 0 reactions 0 assignees View on GitHub
bug help wanted
Dominant language
Go
Stars
813
Forks
108
Avg merge
17h 58m
Merged PRs (30d)
36

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

Open the contributing guide

Research direction

Read internal/notifier/ax_focus_darwin.go, especially raiseWindowByAXDocument and raiseWindowByAXTitle, then inspect the existing retryWindowFocus path. Verify that a minimized matching window is restored before AXRaise and AXFrontmost are used, and confirm that clicking a notification visibly restores and focuses it on macOS.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, macos
Domain
desktop, operating-systems
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.