A malformed state.toml in conductor/tracks/<track>/state.toml (e.g.,
from an interrupted previous run) caused tomllib.load() to raise
TOMLDecodeError, which propagated up and crashed App.__init__
during init_state() -> _load_active_project() -> _refresh_from_project()
-> get_all_tracks() -> load_track_state().
This manifested as test failures in tests/test_layout_reorganization.py,
tests/test_auto_slices.py, tests/test_hooks.py, and the tier-3-live_gui
batch (all triggered by the same malformed mcp_architecture_refactor_20260606
state.toml).
The fix wraps tomllib.load() in a try/except for (OSError,
tomllib.TOMLDecodeError) and returns None (matching the file-not-found
behavior). This is consistent with the data-oriented convention:
corrupt state is a recoverable failure, not a programmer error.
Tests verified:
- tests/test_track_state_persistence.py (1 test) PASS
- tests/test_layout_reorganization.py (4 tests) PASS
- tests/test_auto_slices.py (3 tests) PASS
- tests/test_hooks.py (3 tests) PASS
The Phase 5 batch had 3 files that are already compliant:
- src/theme_2.py:282 - already narrows to (ImportError, AttributeError)
which matches heuristic #19 (catch + log pattern). Compliant.
- src/theme_models.py:166 - the RAISE in load_theme_file is the
'try/except + raise ValueError for domain-level exception
conversion' pattern. The function catches low-level TOML
exceptions and re-raises as ValueError with a descriptive
message. Keep as-is; the audit heuristic gap is a follow-up
improvement (the 'dict lookup miss + raise' pattern should be
INTERNAL_PROGRAMMER_RAISE).
- external_editor.py:47, 56 - already narrow (FileNotFoundError).
Compliant per BOUNDARY_SDK heuristic.
The audit reports src/vendor_capabilities.py:42 as INTERNAL_RETHROW
(suspicious) because the function raises KeyError when no
capabilities are registered for the requested vendor/model.
Decision: keep the raise pattern. This is a legitimate runtime
validation signal (caller asked for unregistered vendor/model).
8 callers in src/{app_controller,gui_2,ai_client}.py use the
returned caps object directly without checking; migrating to
Optional or Result would cascade into 8 caller updates.
The audit heuristic gap (raise KeyError after dict lookup miss
should be INTERNAL_PROGRAMMER_RAISE per the validation-raise
pattern) is noted as a follow-up improvement.
The post-Phase-1 audit reports all 3 files have 0 violations,
0 suspicious, 0 unclear, and 3 compliant sites each.
Per-site decision: all 9 sites are compliant (likely try/finally
or BOUNDARY_IO patterns for TOML I/O); no migration needed.
Migrates the 2 try/except sites in LogRegistry:
1. save_registry() - line 132: was except Exception: print(...)
Now except OSError: and returns Result[bool] with ErrorInfo on
failure. Removed the print() diagnostic.
2. update_auto_whitelist_status() - line 246: was except Exception: pass
Now except OSError: (narrowed). No return value change since
the method returns None anyway.
Both sites narrowed from broad except Exception to specific stdlib
I/O exceptions. Callers of save_registry() (register_session,
update_session_metadata) ignore the Result return value.
Tests verified:
- tests/test_log_registry.py (5 tests) PASS
- tests/test_logging_e2e.py (1 test) PASS
- tests/test_auto_whitelist.py (4 tests) PASS
The post-Phase-1 audit reports src/paths.py has 0 violations,
0 suspicious, 0 unclear, and 3 compliant sites.
Per-site decision: all 3 sites are compliant (likely try/finally
cleanup or BOUNDARY_IO patterns for filesystem path resolution);
no migration needed.
The post-Phase-1 audit reports src/performance_monitor.py has 0
violations, 0 suspicious, 0 unclear, and 1 compliant site.
Per-site decision: the 1 site is compliant (likely a try/finally
or BOUNDARY_IO pattern); no migration needed.
The post-Phase-1 audit reports src/log_pruner.py has 0 violations,
0 suspicious, 0 unclear, and 2 compliant sites (the 2 try/except
sites already use the canonical cleanup pattern or BOUNDARY_IO
heuristic matching).
Per-site decision: both sites are compliant; no migration needed.
The 2 sites (likely try/finally cleanup patterns) are not flagged
as migration-targets by the audit.
Migrates the 4 try/except sites in SummaryCache:
1. load() - line 39: was `except Exception: self.cache = {}`
Now `except (OSError, json.JSONDecodeError):` and returns
Result[bool] with ErrorInfo on failure.
2. save() - line 48: was `except Exception: pass`
Now `except OSError:` and returns Result[bool] with ErrorInfo on
failure.
3. clear() - line 91: was `except Exception: pass`
Now `except OSError:` and returns Result[bool] with ErrorInfo on
failure.
4. get_stats() - line 100: was `except Exception: pass`
Now `except OSError:` and returns Result[dict] with default empty
size_bytes on failure.
All 4 sites narrowed from broad `except Exception` to specific stdlib
I/O exceptions (OSError, json.JSONDecodeError). Methods that previously
returned None now return Result[bool]; get_stats() now returns
Result[dict] instead of dict.
Callers (app_controller.py:_handle_clear_summary_cache, _cb_clear_summary_cache,
summarize.py) ignore the return value, which is backwards-compatible.
Tests verified:
- tests/test_summary_cache.py (3 tests) PASS
- tests/test_ui_cache_controls_sim.py (1 live_gui test) PASS
The per-file list was truncated to top 15 by default. Files below
the top-15 violation ranking (e.g., the 4 UNCLEAR sites in
outline_tool.py, summarize.py, conductor_tech_lead.py,
openai_compatible.py) were hidden from the per-file output.
The fix changes the default --top from 15 to 200, which exceeds
the current project file count (65 src/ files) and leaves room
for future growth. Users can still pass --top 15 if they want a
truncated view.
The render_json filter excluded INTERNAL_COMPLIANT findings from the
per-file list in non-verbose mode:
if f.category in VIOLATION_CATEGORIES or f.category in ("UNCLEAR", "INTERNAL_RETHROW")
This meant the 25 newly-classified compliant sites from the review
pass were not visible in the per-file output. Totals were correct
but the per-file list was incomplete.
The fix removes the filter so all findings appear in the per-file
list. The totals already match (they are computed from r.findings
before the per-file filter).
The audit script's visit_Try had a bug where the
\or child in handler.body\ loop was OUTSIDE the
\or handler in node.handlers\ loop. So \handler\ was bound
to the LAST handler, and only the last handler's body was walked.
Raises in non-last except handlers were missed (e.g.,
src/rag_engine.py:31 was not in the audit findings).
The fix moves the inner loop inside the outer loop so each
handler's body is walked. Both the FIRST and LAST handler raises
are now detected.
Adds tests/test_audit_exception_handling_bug_fixes.py with 2
tests for the walker behavior (first-handler raise, middle-handler
raise in a 3-handler try).
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.