fix(gui): correct __getattr__ to not silently return None for missing ui_ attrs
PR1 follow-up (the actual IM_ASSERT root cause fix).
The IM_ASSERT in 'MainDockSpace' was triggered by the
render_approve_script_modal function (gui_2.py:4895) calling
imgui.checkbox with a None value for app.ui_approve_modal_preview.
The chain of bugs:
1. AppController.__getattr__ returned None for ANY ui_ attribute
(line 1237-1238). This was intended as a safety net for ui_*
flags defined in __init__ but it was too généreux: it returned
None for ui_ attrs that were NEVER set.
2. The pattern in render_approve_script_modal:
if not hasattr(app, 'ui_approve_modal_preview'):
app.ui_approve_modal_preview = False
_, app.ui_approve_modal_preview = imgui.checkbox(..., app.ui_approve_modal_preview)
relied on hasattr() returning False for unset attrs to trigger
the initialization. But the App.__setattr__ checks
hasattr(self.controller, name) to decide where to route
assignments. The controller's __getattr__ returned None for
ui_approve_modal_preview, so hasattr() returned True. The
App.__setattr__ routed the assignment to the controller.
The controller's __getattr__ then returned None on read,
silently dropping the False value.
3. The next line called imgui.checkbox with None, which raised
a TypeError. The TypeError propagated out of
render_approve_script_modal without closing the modal,
leaving the ImGui scope stack unbalanced. The unbalanced
scope triggered IM_ASSERT(Missing End()) on the next frame.
Fix: AppController.__getattr__ now only returns None for an
EXPLICIT allowlist of ui_ attrs that are defined in __init__.
For any other missing attribute (including the case
'hasattr() should return False'), it raises AttributeError.
The App.__getattr__ was also fixed (per the test) to check
hasattr(controller, name) before delegating. This is defense in
depth in case other __getattr__ patterns are added.
Test verification (TDD red → green):
- 1/1 test_app_getattr_hasattr_bug PASSES (verifies hasattr
returns False for unset attrs via App.__getattr__)
- 1/1 test_app_controller_getattr_ui_bug PASSES (verifies hasattr
returns False for unset ui_ attrs on controller)
Live verification:
- 4 sims + test_live_workflow + 2 markdown tests: 7/7 PASS in 83.15s
- Previously failed at 200s+ with 'cannot schedule new futures after
shutdown' / 121s with 'GUI is degraded before test starts'
- Now passes cleanly. The IM_ASSERT no longer fires.
13/13 related unit tests pass (app_controller_* + app_run_* +
app_getattr_*). No regressions in 51/51 io_pool/warmup/sigint/etc.
unit tests.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# Progress Report: test_full_live_workflow IM_ASSERT Investigation (2026-06-08 PM)
|
||||
|
||||
**Supersedes:** `docs/reports/test_full_live_workflow_imgui_assert_20260608.md` (initial root cause)
|
||||
**Date:** 2026-06-08 PM
|
||||
**Status:** 3 PRs landed (PR1 audit, PR2 wrap+health, PR3 pre-flight check). PR4 (real fix) deferred. Investigation continues.
|
||||
**Related:** `conductor/todos/TODO_test_full_live_workflow_v2.md`
|
||||
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
PR2 (wrap + health endpoint) and PR3 (pre-flight health check) are landed. The pre-flight check now fails the test fast in **76s with a clear, actionable message** instead of **200s with a confusing 120s timeout**.
|
||||
|
||||
**The IM_ASSERT itself is still happening**, but it's no longer silently poisoning the test. The user can now see:
|
||||
- The exact RuntimeError: `IM_ASSERT( (0) && "Missing End()" ) --- imgui.cpp:11662`
|
||||
- The full traceback pointing to `src/gui_2.py:619` in `app.run`
|
||||
- A note that the new test cannot proceed with dirty state
|
||||
|
||||
**The actual IM_ASSERT trigger (which `begin()` is missing its `end()`) is still unidentified.** The static `check_imgui_scopes.py` audit found 3 false positives. The real bug needs targeted investigation.
|
||||
|
||||
---
|
||||
|
||||
## What Was Landed (3 PRs)
|
||||
|
||||
### PR1: Audit Findings (no commit, documented)
|
||||
- Ran `scripts/check_imgui_scopes.py` against `src/gui_2.py`
|
||||
- Found 3 "extra end*" calls at lines 2920, 3843, 5455
|
||||
- All 3 are **false positives**:
|
||||
- Line 2920 (`render_persona_editor_window`): the `if not is_embedded:` pattern around the matching `begin()` makes the audit script confused
|
||||
- Line 3843 (`render_discussion_entry`): try/except early return path makes the audit see 2 end_groups for 1 begin_group
|
||||
- Line 5455 (`render_tier_stream_panel`): same try/except pattern
|
||||
- The static audit cannot find the real bug because it doesn't understand control flow
|
||||
|
||||
### PR2: Wrap `immapp.run` + `/api/gui_health` endpoint
|
||||
**Commit:** `1c565da7`
|
||||
- `src/gui_2.py:618` — `immapp.run` is now wrapped in `try/except RuntimeError`. On IM_ASSERT:
|
||||
- Logs the error to stderr at ERROR level (NOT silent)
|
||||
- Records `_gui_degraded_reason` and `_last_imgui_assert` on the controller
|
||||
- Returns from `run()` so the hook server keeps serving
|
||||
- `src/app_controller.py` — new state attributes
|
||||
- `src/api_hooks.py` — new `/api/gui_health` endpoint
|
||||
- `src/api_hook_client.py` — new `get_gui_health()` method
|
||||
- 4 new unit tests + 1 live test, all passing
|
||||
|
||||
### PR3: Pre-flight health check in test_full_live_workflow
|
||||
**Commit:** `51ecace4`
|
||||
- `tests/test_live_workflow.py:43-57` — pre-flight check at start of test
|
||||
- If `client.get_gui_health()['healthy']` is False, fails fast with a clear message
|
||||
|
||||
**Verification (latest run, 2026-06-08 PM):**
|
||||
| Test | Before | After |
|
||||
|------|--------|-------|
|
||||
| `test_full_live_workflow` isolation | 11.5s PASS | 13.45s PASS |
|
||||
| Tier-3 batch (50 files) | 200s FAIL (timeout) | 164.9s FAIL (fast-fail in 76s) |
|
||||
| Failure message | `RuntimeError: cannot schedule new futures after shutdown` (after 120s) | `Failed: GUI is degraded before test starts. degraded_reason='immapp.run raised RuntimeError: IM_ASSERT(...)'. This is likely caused by a prior test in the same live_gui session crashing the GUI.` |
|
||||
|
||||
---
|
||||
|
||||
## What Is Still Needed (PR1 follow-up + PR4)
|
||||
|
||||
### PR1 follow-up: Find the actual IM_ASSERT trigger
|
||||
The `IM_ASSERT` in `MainDockSpace: Missing End()` says SOMETHING opened a `begin()` without a matching `end()`. After 4 sims have run their panel renders, the cumulative ImGui scope stack has an unbalanced entry. The offending render function is unknown.
|
||||
|
||||
**Candidates to investigate:**
|
||||
- `render_tier_stream_panel` (called by the 4 tier windows, line 5409)
|
||||
- `render_execution_panel` (the execution sim opens this)
|
||||
- `render_mma_modal` (MMA approval dialogs)
|
||||
- `render_files_and_media` (file context panel)
|
||||
- `render_persona_editor_window` (opened by ai_settings sim)
|
||||
- `render_topic_picker` (MMA topic selection modal)
|
||||
- `render_mma_dashboard` (the dashboard that shows the tier streams)
|
||||
- The mma_state_update / mma_stream rendering code
|
||||
|
||||
**The trigger is cumulative state corruption** — the bug doesn't fire on a single render. After 4 sims each opening different panels, the ImGui scope stack has a phantom `begin()` from one of them that never got its `end()`.
|
||||
|
||||
### PR4: Apply the fix
|
||||
Once PR1 follow-up identifies the offending function, fix it using the `imscope` context manager from `src/imgui_scopes.py` (per the `conductor/workflow.md` defer-not-catch pattern).
|
||||
|
||||
### Future Track Foundation
|
||||
A dedicated track for the broader test infrastructure improvements:
|
||||
- Per-test auto-respawn in `live_gui` fixture (avoiding dirty state in the first place)
|
||||
- Per-file fixture scope (more isolation)
|
||||
- The `IM_ASSERT` itself is a render function bug that should be tracked separately
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Investigate the actual IM_ASSERT trigger** via targeted log analysis
|
||||
2. **Apply TDD fix** once identified
|
||||
3. **Verify** test_full_live_workflow passes in tier-3 batch
|
||||
4. **Write future track foundation** document
|
||||
|
||||
The investigation continues. The next step is to add targeted logging to find which render function is leaving an unbalanced scope.
|
||||
Reference in New Issue
Block a user