From 694cfd2b700237d8b7f57be41cf48b61ce4b2b76 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 17 Jun 2026 13:22:38 -0400 Subject: [PATCH] diag(tier2): isolate the jank - _trigger_blink in render_response_panel User asked: 'what does negative flows cause in the imgui procedural dag graph that would cause a recursive processing of the stack?' Tested 4 hypotheses: 1. PYTHONSTACKSIZE env var to bump main thread stack: IGNORED. Main thread stays at 1.94MB regardless of env var or PE header (PE header SizeOfStackReserve is 4TB but Windows OS uses its own default for the main thread commit size). 2. -X faulthandler: doesn't capture native STATUS_STACK_OVERFLOW (faulthandler only catches Python-level signals). 3. Editbin /STACK: editbin not installed on this system. 4. PE header patching with ctypes: SizeOfStackReserve is 4TB but the OS commits only 1.94MB for the main thread and Python doesn't honor any env var to change it. The breakthrough: monkey-patched _handle_ai_response via sitecustomize to disable _trigger_blink and _autofocus_response_tab. Result: WITHOUT _trigger_blink: process survives 60s, response event arrives with status='error' and correct error text. The test WOULD PASS. WITH _trigger_blink (default): process dies with 0xC00000FD (STATUS_STACK_OVERFLOW) within 1s of click. The jank: in src/gui_2.py:render_response_panel (line 5537), the _trigger_blink flag triggers imgui.set_window_focus('Response') on the SAME frame as the response render. This native imgui call apparently triggers imgui-bundle to do extra C++ draw work that exhausts the main thread's 1.94MB stack. Why negative_flows specifically: it's the ONLY tier-3 test where the error response triggers the _trigger_blink path. Success responses also trigger _trigger_blink but don't crash (perhaps because imgui- bundle's layout calculations for an error overlay are heavier than for a normal text response). User predicted: 'i wont solve it but just pad out until failure'. Confirmed - bumping stack didn't fix it (couldn't bump anyway, but the prediction about recursion-related behavior is on track). The fix (per user's framing 'needs to be guarded'): wrap the set_window_focus call in render_response_panel in a try/except or add a stack-depth guard before calling it. Or move the _trigger_blink logic to a deferred frame to avoid the same-frame race with the response render. --- .../bump_stack.py | 77 ++++++++++ .../check_pystack.py | 9 ++ .../diag_faulthandler.py | 86 +++++++++++ .../diag_noblink.py | 136 ++++++++++++++++++ 4 files changed, 308 insertions(+) create mode 100644 scripts/tier2/artifacts/send_result_to_send_20260616/bump_stack.py create mode 100644 scripts/tier2/artifacts/send_result_to_send_20260616/check_pystack.py create mode 100644 scripts/tier2/artifacts/send_result_to_send_20260616/diag_faulthandler.py create mode 100644 scripts/tier2/artifacts/send_result_to_send_20260616/diag_noblink.py diff --git a/scripts/tier2/artifacts/send_result_to_send_20260616/bump_stack.py b/scripts/tier2/artifacts/send_result_to_send_20260616/bump_stack.py new file mode 100644 index 00000000..fd6ee9e6 --- /dev/null +++ b/scripts/tier2/artifacts/send_result_to_send_20260616/bump_stack.py @@ -0,0 +1,77 @@ +"""Temporarily bump python.exe's main thread stack size from 1.94MB to 4MB via PE header patch.""" +import struct +import shutil +import os +import sys +from pathlib import Path + +PY = Path(os.environ.get("PYTHON_EXE", r"C:\projects\manual_slop_tier2\.venv\Scripts\python.exe")) +BACKUP = PY.with_suffix(".exe.stackbackup") + +# PE header structure (simplified for stack size fields) +# DOS header -> e_lfanew at offset 0x3C -> NT headers +# NT headers: signature (4), FileHeader (20), OptionalHeader +# OptionalHeader: Magic (2), MajorLinkerVersion (1), MinorLinkerVersion (1), +# SizeOfCode (4), SizeOfInitializedData (4), SizeOfUninitializedData (4), +# AddressOfEntryPoint (4), BaseOfCode (4), BaseOfData (4), +# ImageBase (4 for 32-bit PE, 8 for 64-bit), SectionAlignment (4), +# FileAlignment (4), ... then at offset 0x48 (for 64-bit): +# SizeOfStackReserve (8), SizeOfStackCommit (8) + +def get_pe_stack_reserve(python_path: Path) -> int: + with open(python_path, "rb") as f: + data = f.read() + e_lfanew = struct.unpack_from(" None: + with open(python_path, "rb") as f: + data = bytearray(f.read()) + e_lfanew = struct.unpack_from("