End-of-track report for the 4 sandbox bugs hit by the first Tier 2
run (send_result_to_send_20260616) and the audit infrastructure
added to prevent regression. 5 fixes (4 bugs + 1 audit) shipped as
6 atomic commits on master.
See the report for:
- Per-fix description, root cause, and file:line refs
- Live clone state after the fixes
- 38 default-on + 3 opt-in test inventory
- 4 conventions established
- Next steps for the user (re-run, merge review branch, etc.)
- Known follow-ups NOT in this track
Tier 2 sandbox invariant: no production script under ./scripts/ may
write to the global %TEMP% directory (C:\\Users\\Ed\\AppData\\Local\\
Temp\\). All scratch / intermediate files must live in:
- ./tests/artifacts/ (for test artifacts)
- C:\\Users\\Ed\\AppData\\Local\\manual_slop\\tier2\\ (for app data)
Writing to %TEMP% breaks the sandbox boundary: the OpenCode session
fires the 'ask' prompt for paths outside the project root, halting
autonomous ops (the 2026-06-17 bug with audit_exception_handling.py
output being written to %TEMP% by the agent's shell redirection).
Convention enforcement (per conductor/workflow.md Audit Script Policy):
- scripts/audit_no_temp_writes.py: the canonical audit. Same shape
as scripts/audit_exception_handling.py: --json for machine output,
--strict for the CI gate (exits 1 on any violation). Patterns
cover tempfile module, os.environ['TEMP'], C:\Users\Ed\AppData\Local\Temp, %TEMP%,
/tmp/, etc. Excludes the throw-away archive at scripts/tier2/
artifacts/ and itself (so it can find its own pattern defs).
- tests/test_no_temp_writes.py: default-on regression test. Calls
the audit with --strict and asserts exit 0. If a new script
under ./scripts/ ever uses %TEMP%, the test fails and CI breaks.
Current state: CLEAN. All 36 tier2 tests pass (1 new + 16 slash
command spec + 13 failcount + 6 opt-in). Sanity-checked: dropping
a fake 'import tempfile' script into ./scripts/ triggered exit 1
with 'FOUND 1 matches: scripts/_test_temp_check/test_uses_temp.py:1:
import tempfile'.
Future: also add a corresponding deny rule to the sandbox bash
permission in a follow-up if needed (already added in 03c9df84 for
the agent's own bash). The audit + test is the structural guard.
The Tier 2 agent wrote audit_exception_handling.py output to
C:\\Users\\Ed\\AppData\\Local\\Temp\\audit_initial.json via shell
redirection. This is OUTSIDE the sandbox allowlist (which is
C:\\projects\\manual_slop_tier2 + C:\\Users\\Ed\\AppData\\Local\\
manual_slop\\tier2 + C:\\Users\\Ed\\AppData\\Local\\manual_slop\\
tier2_failures). The OpenCode session-level guard fires the 'ask'
prompt for paths outside the project root, which has no answer in an
autonomous session, so ops halted mid-track.
Fix (3 layers):
1. opencode.json.fragment: add bash deny rule
'*AppData\\Local\\Temp\\*': 'deny' to BOTH the top-level
permission.bash (for default agents) and the tier2-autonomous
agent's permission.bash. The agent physically cannot run shell
commands that target the global Temp dir.
2. conductor/tier2/agents/tier2-autonomous.md: add 'Temp files'
convention telling the agent to use
C:\\Users\\Ed\\AppData\\Local\\manual_slop\\tier2\\ for scratch
/ audit-output / intermediate files, NOT %TEMP%.
3. conductor/tier2/commands/tier-2-auto-execute.md: same convention
in the slash command so the agent sees it at slash-command time.
Tests (default-on):
- test_agent_denies_temp_writes: agent prompt has the Temp deny in
frontmatter bash + the app-data dir note
- test_config_fragment_denies_temp_writes: both top-level and agent
bash have the deny rule
All 16 tier 2 slash command tests pass.
Also: cleaned up the leaked audit_initial.json + audit.json +
audit_after*.json from %TEMP% (they were leftovers from a prior
run). Re-ran setup against the live clone; opencode.json's agent
bash and top-level bash both have the deny rule.
The clone's opencode.json inherited the main repo's top-level 'model'
field (zai/glm-5) via 'git clone'. The tier2-autonomous agent has its
own 'model: minimax-coding-plan/MiniMax-M3' override, so the default
agent path was technically correct, but any other agent spawned without
an explicit model (or if the user manually switched to build/plan)
would have used zai/glm-5 instead of MiniMax-M3.
Fix:
1. Add top-level 'model: minimax-coding-plan/MiniMax-M3' to
conductor/tier2/opencode.json.fragment.
2. setup_tier2_clone.ps1 merge now overrides 'model' from the fragment
(was only overriding agent, permission, default_agent).
3. Added test_config_fragment_has_top_level_model (default-on) to
assert the fragment's model field.
4. Added test_setup_script_overrides_model (opt-in TIER2_SANDBOX_TESTS=1)
to assert the merge code.
All 17 tests pass (14 default-on + 3 opt-in).
Verified: re-ran setup against the live clone; opencode.json's
top-level 'model' is now minimax-coding-plan/MiniMax-M3.
Sub-track 1 of the 5-sub-track result_migration_20260616 campaign.
Audit-driven research task: classify 43 ambiguous exception-handling sites
(24 UNCLEAR + 19 INTERNAL_RETHROW across 11 files) and update the
audit script's heuristics. No production code change.
Scope: 11 files, 43 sites, T-shirt S. The per-site decisions feed
sub-tracks 2-4 (small_files, app_controller, gui_2) as their starting
migration scope.
Files: spec.md, plan.md, metadata.json, state.toml under
conductor/tracks/result_migration_review_pass_20260617/. Row added
to conductor/tracks.md.
Follow-up to 9cd85364. The previous fix patched the OpenCode session-
level permission.read/write allowlist to include the sandbox clone
path, but Tier 2 was still hitting 'ACCESS DENIED' on clone paths.
Root cause: the MCP server has its OWN allowlist that's separate from
OpenCode's session-level permission. The MCP server's allowlist =
project_root (parent dir of the script) + extra_dirs from
mcp_paths.toml in the project root. The clone inherited the main
repo's mcp.manual-slop.command via 'git clone', which launched
C:\\projects\\manual_slop\\scripts\\mcp_server.py with
PYTHONPATH=C:\\projects\\manual_slop\\src. So the MCP server was
using the main repo's project_root + the main repo's mcp_paths.toml
(extra_dirs=['C:/projects/gencpp']) -- exactly the
'Allowed base directories are: gencpp, manual_slop' the user saw.
Fix: setup_tier2_clone.ps1 now overrides the clone's mcp.manual-slop
config to point at the CLONE's scripts/mcp_server.py and src/, and
replaces the clone's mcp_paths.toml with an empty extra_dirs list.
The MCP server's allowlist becomes [C:\\projects\\manual_slop_tier2]
only -- the sandbox boundary.
Added test_setup_script_overrides_mcp_server (text-based regression)
to assert the script contains the required overrides. Opt-in via
TIER2_SANDBOX_TESTS=1.
Verified: re-ran setup against the live clone. opencode.json now has
mcp.manual-slop.command pointing at C:\\projects\\manual_slop_tier2\\
scripts\\mcp_server.py with PYTHONPATH=C:\\projects\\manual_slop_tier2\\
src. mcp_paths.toml has 'extra_dirs = []'.
Replace positional args[3..5] assertions with assert_called_once_with using
rounding=/thickness=/flags= kwargs to match the existing add_rect call in
src/theme_nerv_fx.py:AlertPulsing.render and the parallel test in
tests/test_theme_nerv_fx.py:TestThemeNervFx.test_alert_pulsing_render.
Fixes test_alert_pulsing_render_active IndexError that surfaced when the
positional contract was asserted against the kwargs-shaped production call.
Regression: a Tier 2 session was denied access to
C:\\projects\\manual_slop_tier2\\scripts\\run_tests_batched.py
with 'Allowed base directories are: gencpp, manual_slop'. The
tier2-autonomous agent had a correct permission.read allowlist, but
the top-level permission block (inherited from the main repo's
opencode.json via 'git clone') had no read/write keys, and OpenCode
uses the top-level for the default agent path. The agent's
permission.read was merged but apparently not enforced for the
default-agent access check.
Fix:
1. Add a top-level 'permission' block to
conductor/tier2/opencode.json.fragment with:
- permission.edit: 'deny' (default agents locked down)
- permission.read: deny *, allow sandbox clone + app-data dirs
- permission.write: same
- permission.bash: deny *, allowlist of read-only git commands +
uv run python scripts/{run_tests_batched.py,tier2/*} + basic
shell commands. git push/checkout/restore/reset remain denied.
2. Update setup_tier2_clone.ps1 to also patch the top-level
'permission' block (was only merging the tier2-autonomous agent
block). The script preserves the user's mcp, model, instructions,
watcher, and plugin settings from the inherited opencode.json.
3. Update test_tier2_slash_command_spec.py:
- Rename test_command_fetches_origin_main -> ..._master (we
changed the slash command on 2026-06-17).
- Add test_config_fragment_has_top_level_permission to assert
the new top-level permission block has the right deny-all +
allowlist shape.
The tier2-autonomous agent's permission block is unchanged; it
overrides the top-level for that agent's tool calls.
User indicated they want tier 1 to investigate ('something feels
architecturally wrong'). Investigation summary:
ROOT CAUSE: imgui.set_window_focus('Response') called on the same
frame as the response render, when _trigger_blink is set by
_handle_ai_response. The native call exhausts the main thread's
1.94MB stack.
VERIFIED: disabling _trigger_blink and _autofocus_response_tab makes
the test PASS. The process survives, the response event arrives with
correct error text.
HISTORY CHECK (git log -S):
- _trigger_blink: pre-existing since March 2026 (c88330cc feat(hot-
reload) Exhaustive region grouping for module-level render funcs)
- _autofocus_response_tab: pre-existing since March 6 2026 (0e9f84f0
'fixing')
- set_window_focus in render_response_panel: pre-existing since
96a013c3 'fixes and possible wip gui_2/theme_2 for multi-viewport'
- response event flow: pre-existing since 68861c07 feat(mma):
Decouple UI from API calls using UserRequestEvent and AsyncEventQueue
- FR1 (send_result error routing): commit 24ba2499 (Jun 15 2026) in
public_api_migration_and_ui_polish_20260615 track
The jank is OLDER than the user thinks. The most likely explanation:
the test was never run as part of the regular tier-3 batch, so the
crash was masked by the Isolated-Pass Verification Fallacy.
QUESTIONS FOR TIER 1:
1. Is _trigger_blink a sound design?
2. Should imgui focus changes be deferred to next frame's idle phase?
3. Is there a general principle that no native imgui call should be
made during the same frame as a draw call?
PROPOSED MINIMAL FIX: defer set_window_focus to next frame's idle
phase via a _pending_focus_response flag handled in
_process_pending_gui_tasks (which runs before the render).
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.
Per user question about whether execution is properly isolated between
AppController and gui_2.py main thread.
Verified by reading the architecture contract (docs/guide_architecture.md
lines 12, 884-890) and the two click handlers in question:
- _handle_generate_send (btn_gen_send): self.submit_io(worker)
- _cb_plan_epic (btn_mma_plan_epic): self.submit_io(_bg_task)
BOTH click handlers return immediately after submitting work. The
heavy AI call (ai_client.send -> subprocess.Popen -> process.communicate)
runs on the io_pool worker thread. The execution isolation between
AppController and gui_2.py's main render thread IS being followed.
The crash (STATUS_STACK_OVERFLOW, 0xC00000FD) is NOT in the click
handler chain. It IS in the main thread's imgui-bundle render loop.
The render loop runs concurrently with the io_pool worker's subprocess
operations. imgui-bundle's per-frame C++ draw code can exceed the main
thread's 1.94 MB stack (verified via kernel32.GetCurrentThreadStackLimits).
What aspect of negative_flows triggers this: the error-response render
path. MOCK_MODE=malformed_json causes the adapter to raise, which
triggers _handle_request_event to emit a 'response' event with
status='error'. The render loop draws this error response on the next
frame, exhausting the main thread's stack.
test_visual_orchestration.py uses the same provider setup but does NOT
set MOCK_MODE, so the mock defaults to 'success' mode, the adapter
returns normally, no error event, no crash. Empirically PASSED in
11.01s.
The architecture's render-loop contract assumes imgui-bundle's C stack
usage is bounded. It's not. The architecture has no enforcement
mechanism (no stack guard, no per-frame stack measurement, no graceful
degradation).
Next step (post-compact): capture Windows crash dump via procdump to
identify the specific imgui-bundle draw call.