Skip to content

fix: declare Win32 signatures to avoid 64-bit handle truncation#698

Merged
Orinks merged 4 commits into
devfrom
fix/single-instance-handle-truncation
May 28, 2026
Merged

fix: declare Win32 signatures to avoid 64-bit handle truncation#698
Orinks merged 4 commits into
devfrom
fix/single-instance-handle-truncation

Conversation

@Orinks
Copy link
Copy Markdown
Owner

@Orinks Orinks commented May 28, 2026

What

Declares explicit ctypes restype/argtypes for the Win32 calls in the single-instance startup path and in _force_foreground_window.

Why

ctypes defaults a function's restype/argtypes to 32-bit c_int. On 64-bit Windows a HANDLE/HWND is pointer-width, so handles were being silently truncated before being passed back to CloseHandle / ShowWindow / SetForegroundWindow. It usually works only because handles often happen to fit in the low 32 bits — exactly the kind of latent bug that surfaces intermittently in the field and never in tests (the test fakes return plain ints and can't exercise the real ctypes ABI).

Changes

  • single_instance.py: CreateMutexW/CloseHandle (kernel32) and FindWindowW/EnumWindows/GetWindowText*/ShowWindow/SetForegroundWindow (user32) now declare c_void_p signatures. Setup is wrapped in contextlib.suppress so the test fakes (plain methods, which reject attribute assignment) keep working unchanged.
  • app_activation.py: _force_foreground_window uses a local WinDLL with declared signatures instead of the shared ctypes.windll cache, so the fix doesn't mutate process-global prototypes.

Behavior

No behavior change on the happy path — handles simply round-trip intact. All existing single-instance and activation tests pass (30/30).

🤖 Generated with Claude Code

Orinks and others added 2 commits May 28, 2026 17:16
… truncation

ctypes defaults function restype/argtypes to 32-bit c_int, so the HANDLE
returned by CreateMutexW and the HWNDs from FindWindowW were truncated on
64-bit Windows before being passed back to CloseHandle/ShowWindow/
SetForegroundWindow. Declare c_void_p signatures for the kernel32 and user32
calls so handles round-trip intact. Signature setup is suppressed on the test
fakes (plain methods reject attribute assignment), so behavior is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same 64-bit handle-truncation issue as the single-instance mutex fix: the
HWND from frame.GetHandle() was passed to IsIconic/ShowWindow/
SetForegroundWindow through ctypes' default c_int restype/argtypes, truncating
the pointer on 64-bit Windows. Use a local WinDLL with declared c_void_p
signatures (rather than the shared ctypes.windll cache) so the handle round-
trips intact without mutating process-global function prototypes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Orinks and others added 2 commits May 28, 2026 17:49
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drives _force_foreground_window through fake user32/kernel32 DLLs so the
handle-signature path is exercised on non-Windows CI, plus a non-Windows case
asserting the frame.Raise() fallback. Satisfies the diff-coverage gate for the
rewritten ctypes block.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Orinks Orinks merged commit 8572ce8 into dev May 28, 2026
3 checks passed
@Orinks Orinks deleted the fix/single-instance-handle-truncation branch May 28, 2026 21:57
Orinks added a commit that referenced this pull request May 28, 2026
…ling (#699)

> **Stacked on #698** (base is `fix/single-instance-handle-truncation`,
not `dev`). Retarget to `dev` after #698 merges.

Follow-up cleanup to the single-instance activation path. No relation to
the ABI fix in #698 beyond touching the same files — kept separate so
each PR has one thesis.

## Changes

1. **Dedupe the fallback handoff (correctness).** When the named-pipe
IPC is unavailable, `request_existing_instance_show` used to *both*
write the handoff file *and* poke the window directly — so the primary
instance could act on the same request twice (once via the handoff poll,
once via the direct restore). Now the handoff is skipped for a plain
`generic_fallback` that a successful window restore already satisfied,
while `discussion`/`alert_details` intents — which the window poke can't
carry — are still handed off. Window-restore logic extracted into
`_show_existing_window`.
2. **Relax the handoff poll 750ms → 2000ms** via a named constant. The
file handoff is only a fallback to IPC, so it doesn't need sub-second
latency and shouldn't run a perpetual high-frequency timer for the whole
session.
3. **Document the "allow startup on any failure" policy** in
`try_acquire_lock`.

## Tests

Two new tests assert the dedupe: generic-on-success writes no handoff;
`discussion`-on-success still does. 32/32 single-instance + activation
tests pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant