Skip to content

fix(windows): handle hwnd==0 gracefully for UAC/secure desktop#118

Merged
ErikBjare merged 1 commit intoActivityWatch:masterfrom
TimeToBuildBob:bob/fix-hwnd-zero-crash
Feb 27, 2026
Merged

fix(windows): handle hwnd==0 gracefully for UAC/secure desktop#118
ErikBjare merged 1 commit intoActivityWatch:masterfrom
TimeToBuildBob:bob/fix-hwnd-zero-crash

Conversation

@TimeToBuildBob
Copy link
Copy Markdown
Contributor

@TimeToBuildBob TimeToBuildBob commented Feb 27, 2026

Summary

  • Skip window polling when hwnd == 0 (no foreground window), which occurs during UAC prompts, lock screen, and secure desktop
  • Wrap the WMI fallback in try/except so if both win32api and WMI fail for elevated processes, we degrade to "unknown" instead of error spam

Problem

When the foreground window handle is 0 (e.g. during UAC prompts, lock screen, or secure desktop), OpenProcess fails with pywintypes.error: (87, 'OpenProcess', 'The parameter is incorrect.'). This causes continuous error logging every poll cycle.

Additionally, when the primary get_app_name() fails for elevated/admin processes (error 5: Access denied), the WMI fallback could also fail and propagate exceptions upward, causing more error spam.

Fix

  1. Early return for hwnd == 0: get_active_window_handle() returns 0 when there's no foreground window. We now return None early, which heartbeat_loop already handles gracefully (logs debug, skips heartbeat, tries again next poll).

  2. Wrapped WMI fallback: If both get_app_name() and get_app_name_wmi() fail, we now set app = None which resolves to "unknown" instead of propagating the exception.

Test plan

  • Verified heartbeat_loop already handles None return from get_current_window() (line 144-145 in main.py)
  • Manual test on Windows with UAC prompt active
  • Manual test running admin-elevated application (e.g. game with admin rights)

Fixes #90


Important

Handles hwnd == 0 gracefully in get_current_window_windows() and wraps WMI fallback to prevent error spam in lib.py.

  • Behavior:
    • get_current_window_windows() in lib.py returns None if window_handle is 0, skipping the poll cycle during UAC prompts or secure desktop.
    • Wraps WMI fallback in get_current_window_windows() to set app = None if both get_app_name() and get_app_name_wmi() fail, resolving to "unknown" instead of propagating exceptions.
  • Misc:
    • No changes to Linux or macOS functions in lib.py.

This description was created by Ellipsis for 0723a64. You can customize this summary. It will automatically update as commits are pushed.

When the foreground window handle is 0 (e.g. during UAC prompts,
lock screen, or secure desktop), Win32 API calls like OpenProcess
fail with "The parameter is incorrect" (error 87). This caused
continuous error spam in logs.

Fix:
- Return None early when hwnd==0, letting heartbeat_loop skip the
  poll cycle (it already handles None gracefully)
- Wrap WMI fallback in try/except so if both win32api and WMI fail
  for elevated processes, we degrade to "unknown" instead of crashing

Fixes ActivityWatch#90
Copy link
Copy Markdown

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed everything up to 0723a64 in 11 seconds. Click for details.
  • Reviewed 28 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_ugiXQEsElxrUhE24

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Feb 27, 2026

Greptile Summary

Adds defensive handling for scenarios where no foreground window exists (UAC prompts, lock screen, secure desktop). The fix prevents error spam by returning early when hwnd == 0 and gracefully degrading to "unknown" when both Win32 API and WMI methods fail to retrieve the app name for elevated processes.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The changes are defensive error handling improvements that prevent crashes. The early return for hwnd==0 is verified to be handled correctly by the existing heartbeat loop, and the None fallback is properly converted to "unknown" by existing code at lines 59-60
  • No files require special attention

Important Files Changed

Filename Overview
aw_watcher_window/lib.py Added early return for hwnd==0 and wrapped WMI fallback to prevent error spam during UAC prompts and secure desktop scenarios

Last reviewed commit: 0723a64

Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, no comments

Edit Code Review Agent Settings | Greptile

window_handle = windows.get_active_window_handle()

# hwnd 0 means no foreground window (e.g. during UAC prompt, lock screen,
# or secure desktop). Return None so heartbeat_loop skips this poll cycle.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should it skip poll cycle or lead to unknown?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good question — I'd argue skip the poll cycle is the right behavior here, for the same reason we already do it for non-Windows (the get_current_window functions return None and heartbeat_loop treats it as 'no window, try again').

The distinction:

  • hwnd == 0: There is literally no foreground window (lock screen, UAC prompt, secure desktop). Nothing to record.
  • app = None (from failed OpenProcess / WMI): There is a window, we just can't identify it → falls back to "unknown".

Recording "unknown" during a UAC prompt or lock screen would create misleading activity — the user isn't actually in any app. Skipping the heartbeat leaves a gap (or the previous heartbeat's pulsetime covers it), which is semantically correct.

That said, if you'd prefer "unknown" for continuity, happy to change it — it's a one-liner swap.

@ErikBjare
Copy link
Copy Markdown
Member

@TimeToBuildBob See #118 (comment)

@ErikBjare ErikBjare merged commit 61b482b into ActivityWatch:master Feb 27, 2026
6 checks passed
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.

[Windows] Exception thrown while trying to get active window

2 participants