Windows pet overlay hit region desynchronizes from the visible mascot over time
What version of the Codex App are you using?
Codex Desktop Windows package OpenAI.Codex_26.715.4045.0_x64__2p2nqsd0c76g0.
What subscription do you have?
Not relevant to the reproduction.
What platform is your computer?
Windows NT 10.0.22631.6199, x64.
What issue are you seeing?
The draggable/clickable hit region of the desktop pet can desynchronize from the visible mascot after Codex has been running for some time.
Immediately after a Windows restart, the pet normally responds across its full visible rectangular area. Later, only the upper part of the visible mascot may accept the mouse; the center/lower part passes clicks through, so the pet cannot be dragged from most of its body.
This affects both:
- bundled/default pets;
- a valid custom v2 pet (Burny).
Switching pet assets does not fix it. Restarting Windows temporarily restores the correct hit region, but the problem eventually returns. This makes the symptom look asset-specific at first, but it is shared by all pets.
Pet width is set to the supported minimum of 80 px through avatar-overlay-mascot-width-px.
Steps to reproduce
The transition is not fully deterministic yet:
- Start Windows and launch Codex Desktop.
- Wake/show a desktop pet.
- Confirm that the pet can initially be dragged from its full visible body.
- Leave Codex running and use it normally, including pet activity/notification transitions.
- Later, try to begin a drag from the center or lower half of the mascot.
Expected behavior
The full visible mascot area remains clickable and draggable for the lifetime of the overlay, for both bundled and custom pets.
Actual behavior
Only part of the visible mascot remains interactive while the rest of the transparent overlay passes the pointer through. A Windows restart temporarily resets the behavior.
Local technical observations
These observations come from inspecting the packaged desktop app, because the public repository does not appear to contain the desktop avatar renderer source.
Both native and non-native overlay renderers use the shared hit-region selectors:
[data-avatar-overlay-hit-region]
[data-avatar-mascot='true']
The floating-window pointer-interactivity code measures those DOM regions and dispatches avatar-overlay-pointer-interaction-changed. The Electron main process then toggles the transparent overlay window with setIgnoreMouseEvents(...).
Because this mechanism is shared by every pet, and because the visual mascot remains correct while only pointer interaction changes, this looks like a stale pointer-passthrough/native-composition state rather than sprite geometry.
The packaged app also contains an internal persisted flag named avatar-overlay-force-non-native-rendering. That suggests forcing the Chromium rendering path could be useful as a diagnostic fallback, but I have not treated that internal flag as a supported fix.
The installed custom atlas still matches its validated source hash and passes the v2 8x11 atlas validation, so no pet asset change correlates with the failure.
Possible fix direction
- Recompute and resend the interactive region after native-composition, layout, notification, display, DPI, sleep/wake, and overlay-bounds transitions.
- Ensure the native visual surface and Chromium
getBoundingClientRect()values stay in the same coordinate space. - Reapply the correct
setIgnoreMouseEventsstate when the overlay is re-hosted or its native surface changes. - Add a Windows regression test covering bundled and custom pets, pet size
80, notification transitions, and long-lived overlay state. - Consider automatically falling back to non-native rendering if native hit testing becomes inconsistent.
Related issues
- #21359 — dragging can fail after moving a pet to a secondary display.
- #21313 — the Windows pet overlay can lose correct native window behavior after wake/focus changes.
- #21864 — pet resizing should keep the visible and clickable areas aligned.
18 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Additional Windows data point on the current Microsoft Store package
OpenAI.Codex_26.715.4045.0_x64__2p2nqsd0c76g0.Environment:
262001536x1872, 8x9) selected; asset validation passes.Observed behavior:
This further points away from the custom asset or saved coordinates and toward the transparent overlay hit-testing / mouse-passthrough path. In this instance the failure appears to cover the full visible mascot, rather than only its lower portion.
I can reproduce the complete/full-overlay form of this issue.
non-native rendering do not restore dragging
Live inspection shows the overlay remains click-through
(WS_EX_TRANSPARENT). The pet overlay starts non-interactive and does not
transition out of mouse passthrough, preventing pointer-down and drag-start
events from reaching the renderer.
The Electron executable is identical between the working and failing
packages, so the regression most likely resides in the packaged Codex
JavaScript/resources or rollout state.
Definitive root-cause update (Windows, 2026-07-21)
The complete/full-overlay failure still reproduces after the Microsoft Store update to
OpenAI.Codex_26.715.8383.0_x64.The earlier “hit-region desynchronization” theory does not explain this full failure. Live native-window testing isolates the broken segment to Electron's Windows mouse-forwarding path used while the transparent overlay is click-through.
Reproduction and isolation
WS_EX_TRANSPARENTfor 478/478 samples; it became interactive 0 times.WM_MOUSEMOVEthat Electron's forwarder is intended to deliver directly to the overlay'sChrome_RenderWidgetHostHWNDat a coordinate inside the visible mascot immediately changed the top-level overlay from passthrough to interactive:0x800A8(WS_EX_TRANSPARENTset)0x80088(WS_EX_TRANSPARENTcleared)0x800A8.That experiment bypasses only the native forwarding stage. It demonstrates that the following downstream stages are functioning:
setIgnoreMouseEvents(false)transitionThe failing stage is therefore before the renderer: physical
WM_MOUSEMOVEevents are not being forwarded into the ignored Electron window.Packaged app behavior
The current packaged desktop code still has this state model:
setIgnoreMouseEvents(true, { forward: true });The resume path restores layout/display/bounds but does not explicitly re-arm the mouse forwarder. Consequently, lifecycle callbacks can run without invoking Electron again because the cached desired passthrough state has not changed.
Electron's Windows implementation of
forward: trueuses a globalWH_MOUSE_LLhook and also caches whether forwarding is enabled. Windows documents that a low-level hook can be silently removed after its timeout; the application cannot determine that the hook was removed. See:BrowserWindow.setIgnoreMouseEvents: https://www.electronjs.org/docs/latest/api/browser-windowLowLevelMouseProc: https://learn.microsoft.com/windows/win32/winmsg/lowlevelmouseprocThe remaining low-level ambiguity is only whether this instance failed during hook installation or the hook was later silently removed. Windows exposes no reliable signal for that distinction. Both cases have the same application-level defect: the desired-state booleans remain “forwarding enabled,” so Codex never retries or uses a fallback.
This also explains the intermittent history: recreating the overlay/window can make dragging work temporarily, then the hook/forwarder stops and the stale state prevents recovery.
Suggested robust fix
Do not make pet hit testing depend exclusively on the
WH_MOUSE_LLforwarding hook.screen.getCursorScreenPoint()at about 30–60 Hz (or use a raw-input equivalent).webContents.sendInputEvent({ type: "mouseMove", ... }).ready-to-show,show, resume/screens-wake, and display add/remove/metrics changes without allowing the desired-state cache to suppress the re-arm.Regression/acceptance matrix
This is no longer an asset, atlas, saved-bounds, DPI-coordinate, or drag-algorithm hypothesis: direct message injection verifies the renderer-side interaction chain end to end.
Definitive root-cause update (Windows, 2026-07-21)
The complete/full-overlay failure still reproduces after the Microsoft Store update to
OpenAI.Codex_26.715.8383.0_x64.The earlier “hit-region desynchronization” theory does not explain this full failure. Live native-window testing isolates the broken segment to Electron's Windows mouse-forwarding path used while the transparent overlay is click-through.
Reproduction and isolation
WS_EX_TRANSPARENTfor 478/478 samples; it became interactive 0 times.WM_MOUSEMOVEthat Electron's forwarder is intended to deliver directly to the overlay'sChrome_RenderWidgetHostHWNDat a coordinate inside the visible mascot immediately changed the top-level overlay from passthrough to interactive:0x800A8(WS_EX_TRANSPARENTset)0x80088(WS_EX_TRANSPARENTcleared)0x800A8.That experiment bypasses only the native forwarding stage. It demonstrates that the following downstream stages are functioning:
setIgnoreMouseEvents(false)transitionThe failing stage is therefore before the renderer: physical
WM_MOUSEMOVEevents are not being forwarded into the ignored Electron window.Packaged app behavior
The current packaged desktop code still has this state model:
setIgnoreMouseEvents(true, { forward: true });The resume path restores layout/display/bounds but does not explicitly re-arm the mouse forwarder. Consequently, lifecycle callbacks can run without invoking Electron again because the cached desired passthrough state has not changed.
Electron's Windows implementation of
forward: trueuses a globalWH_MOUSE_LLhook and also caches whether forwarding is enabled. Windows documents that a low-level hook can be silently removed after its timeout; the application cannot determine that the hook was removed. See:BrowserWindow.setIgnoreMouseEvents: https://www.electronjs.org/docs/latest/api/browser-windowLowLevelMouseProc: https://learn.microsoft.com/windows/win32/winmsg/lowlevelmouseprocThe remaining low-level ambiguity is only whether this instance failed during hook installation or the hook was later silently removed. Windows exposes no reliable signal for that distinction. Both cases have the same application-level defect: the desired-state booleans remain “forwarding enabled,” so Codex never retries or uses a fallback.
This also explains the intermittent history: recreating the overlay/window can make dragging work temporarily, then the hook/forwarder stops and the stale state prevents recovery.
Suggested robust fix
WH_MOUSE_LLforwarding hook.screen.getCursorScreenPoint()at about 30–60 Hz (or use a raw-input equivalent).webContents.sendInputEvent({ type: "mouseMove", ... }).ready-to-show,show, resume/screens-wake, and display add/remove/metrics changes without allowing the desired-state cache to suppress the re-arm.Regression/acceptance matrix
This is no longer an asset, atlas, saved-bounds, DPI-coordinate, or drag-algorithm hypothesis: direct message injection verifies the renderer-side interaction chain end to end.
Follow-up: physical click-target capture
I captured physical left-button transitions while reproducing the complete drag failure on Codex Windows
26.715.8383.0.A read-only probe sampled the pet overlay's extended style and used
WindowFromPointat each physical button-down. Multiple attempted drags began on the visible mascot and were confirmed to start inside the overlay bounds.Sanitized result:
For every captured in-overlay press:
WS_EX_TRANSPARENTset (0x2800A8);This is physical-input confirmation that click-through persists through button-down, so the renderer never receives the
pointerdownneeded to begin the existing drag path. Combined with the earlier directWM_MOUSEMOVEinjection result, this further isolates the failure to the pre-renderer Windows mouse-forwarding/interactivity handoff.I also tested continuously clearing
WS_EX_TRANSPARENTexternally. That did not reliably restore dragging, indicating that changing the native style bit alone is not equivalent to re-arming Electron's internal ignore/forwarding state.The raw diagnostic log is intentionally not attached because it contains unrelated window titles, process IDs, native handles, timestamps, and screen coordinates.
Conclusion: The pet assets and drag code are functioning. On Windows, physical mouse movement is not reaching the ignored Electron overlay, so it remains click-through and sends clicks to the window underneath. Only whether the forwarding hook failed initially or was later removed remains unresolved.
So this requires a Codex/Electron Windows fix; changing pet assets or saved positions cannot solve it.
Additional reproduction: remote-desktop display transitions are a reliable trigger.
Environment
OpenAI.Codex_26.715.10079.0_x642560x14401024x768, then restores the local layout when the session endsSteps to reproduce
Actual behavior
After the display transition back to the local layout, the pet remains visible but no longer accepts click or drag input. The symptom recurs on subsequent UU sessions that change the resolution.
The persisted overlay state contained a per-resolution entry for
1024x768with stale-looking bounds. Removing onlyelectron-avatar-overlay-boundsfrom Codex's persisted global state and relaunching the app made Codex regenerate bounds for both2560x1440and1024x768, and restored interaction for this instance.Expected behavior
On every display-metrics/resolution transition, the pet overlay should re-evaluate its saved bounds and re-arm its Windows pointer-forwarding / interactivity state. A remote-desktop resolution round trip should not leave a visible pet click-through or non-draggable.
This is a narrow remote-desktop trigger that may help exercise the same display-change lifecycle described in this issue.
Additional confirmation on a newer Windows package:
OpenAI.Codex 26.715.10079.0.26.715.7063.0.That hover-vs-drag distinction suggests the visible mascot hit region is still receiving pointer interaction, while the drag-start/native window-drag transition fails. Inspection of the packaged Windows renderer shows it calls
compositionHost.performWindowDrag()and skips renderer-side fallback movement when the native call reports success, so native drag handling or success reporting may be involved.Full reproduction details were also recorded in the now-consolidated duplicate: https://github.com/openai/codex/issues/34309#issuecomment-5056932758
Additional Windows reproduction and workaround evidence from Codex Desktop package 26.715.10079.0:
The workaround is intentionally external and reversible; no signed WindowsApps files were modified.
Additional Windows reproduction from a custom v2 pet:
spriteVersionNumber: 2andspritesheet.webppet.json.This initially looked like a mistake in the v2 custom pet, but the symptom matches the overlay hit-region/pointer-passthrough regression described here. Expected behavior is for the full visible pet area, including transparent padding around the sprite, to remain clickable and draggable.
Additional confirmation on a newer Microsoft Store build:
OpenAI.Codex 26.721.4979.0on Windows x64.New reliable trigger:
Restart result:
WS_EX_TRANSPARENT(0x002800A8) and physical interaction did not recover.Current packaged-code observation:
mousePassthroughEnabledand suppresses repeatedsetIgnoreMouseEvents(...)calls while that boolean is unchanged.handleDisplayChangedrestores/reclamps layout and persisted bounds but does not force a pointer-forwarding re-arm.handleResumeclears the suspended flag and restores/switches display geometry, but likewise does not invalidate the passthrough cache or reapplysetIgnoreMouseEvents(true, { forward: true }).This extends the earlier UU remote-resolution reproduction: ordinary monitor power/display wake transitions are sufficient, and process recreation is not a reliable workaround. It also shows the defect remains present in the newer
26.721.4979.0package.Additional reproduction on Windows with
OpenAI.Codex 26.727.6591.0.The two visible controls below the pet - the voice button and the collapse button - sometimes stop responding to clicks. They remain visible, but mouse input seems to pass through the overlay.
Reproduction
Pet asset check
I reproduced this with two versions of the same custom pet:
Both atlases:
1536x2288;spriteVersionNumber: 2;The smaller version has the same problem, so sprite size, padding, and transparency do not appear to be the cause.
Packaged app observation
While inspecting the packaged Windows app, I noticed that a custom avatar is temporarily resolved as
nullwhile its data is loading. During this state, the overlay sends:avatar-overlay-pointer-interaction-changedwithisInteractive: false;avatar-overlay-composition-changedwith a null state.After the custom avatar finishes loading, the controls may become visible while the overlay is still non-interactive and passing clicks through.
This may be another trigger for the stale Windows pointer-forwarding state already described in this issue.
Expected behavior: after loading a custom avatar, switching pets, or tucking and waking the pet, Codex should rebuild the interactive regions and restore mouse forwarding before displaying the controls.
Additional reproducible observation: size-dependent hit region near the upper-left
A user found a reliable temporary way to drag the Pet after the normal visible sprite stops accepting pointer input:
This workaround does not permanently repair the state; the input problem returns after resizing back down.
This appears distinct from the complete Windows mouse-forwarding failure where no part of the overlay reacts at all. In this case, hover and drag are still functional inside a tiny, offset region, and increasing the Pet's visual size makes that region large enough to locate.
Expected: the clickable/draggable hit region should remain aligned with the visible mascot and scale linearly with the Pet size setting.
Possible direction (inference): the hit-region bounds or scale may be stale, anchored to the upper-left, or receiving the size factor more than once. Recompute the mascot bounds and native pointer-interaction region after every size-slider change, and add a regression test covering minimum → maximum → minimum sizing while verifying that the full visible mascot remains draggable.
补充验证(2026-08-24,Windows):
Current-version mixed-DPI correlation and recovery (Windows, 2026-08-24)
I can reproduce the non-interactive pet state on Codex Desktop 26.818.5229.0 with mixed display scaling.
Environment
Observed behavior / recovery
The exact transition that initially breaks interaction has not yet been isolated, but the recovery is strongly correlated with the per-monitor scale/DPI change.
Local native-window observations
--device-scale-factor=1.5, matching the 150% display rather than the 200% display.This suggests a mixed-DPI coordinate conversion or stale input-shape/hit-test region when the overlay is hosted on a differently scaled monitor. It may be worth recomputing and reapplying the input shape and pointer-interactivity state on
WM_DPICHANGED, monitor moves, and display-scale changes.Current workarounds
Minimal size-matrix follow-up with a custom v2 pet (Windows, 2026-08-25)
I ran the size leg locally on Codex Desktop 26.818.5229.0 with the custom
yukie-head/ “Yukie Head” pet (spriteVersionNumber: 2). The installed spritesheet matches the workspace source (SHA-256:1C92859C0CB66480F00EB44F29F6E3614D598A68B4EEC700C6ED04160E1F8C8C) and passes the v2 atlas validation.The renderer is launched with
--device-scale-factor=1.5. This machine currently exposes only one monitor (DISPLAY1, 2560×1600 physical) at 150% scaling / DPI 144, so I could not execute the 200% or cross-monitor legs. I am marking those legs as blocked rather than treating them as passes.| Leg | Result |
|---|---|
| 150%, minimum → maximum → minimum | Executed: slider read back
80.0 → 224.0 → 80.0. The pet remained visually rendered at all three stages; the UIA image boxes were120×131,336×365, and120×131. || 200%, minimum → maximum → minimum | Blocked: no DPI-192 monitor is available on this machine. |
| 150% → 200% → 150% move | Blocked: no second monitor is available. |
For each 150% stage I sampled native
WindowFromPointat the top, middle, and bottom of the visible Yukie image. All 9/9 samples resolved to the mainChatGPTwindow, not the pet overlay; an additional spot check of the overlay controls also resolved to the main window. The pet stayed visible in the screenshots, so this run reproduced a complete mouse-passthrough state rather than a size-dependent recovery.In the clean rerun, the 80 px image box was at the same horizontal position (
X=2225) before and after the round trip, so I did not get a repeatable size/layout drift from resizing alone.This local run does not isolate the transition that first enters the non-interactive state, and it does not establish the 200% mixed-DPI behavior because the required hardware is unavailable. It does show that the current version can still reach a visible-but-fully-passthrough state at 150% with a valid custom v2 pet after a minimum → maximum → minimum size sequence.
Confirmed on newer Windows package 26.825.3734.0 (single display, 100% scale)
This still reproduces on the current Microsoft Store package:
OpenAI.Codex_26.825.3734.0_x64__2p2nqsd0c76g0Ok, Store-signedObserved behavior
Native-window evidence
The visible avatar overlay is present as a layered, topmost tool window. I sampled the complete overlay with Win32
WindowFromPointon an 8-pixel grid:WS_EX_TRANSPARENTwas presentThe current packaged code measures
[data-avatar-overlay-hit-region], sends regions throughsetPointerRegions, and applies them throughBrowserWindow.setInputShape(...), withsetIgnoreMouseEvents(true, { forward: true })as the fallback. In this reproduction the effective native input region is empty, and the fallback never transitions the overlay out of mouse passthrough.This is a full-overlay failure on a single 100%-scaled display, so it is not limited to mixed-DPI or multi-monitor setups. No personal logs, chat text, or pet artwork are attached.