Private
Public Access
0
0

fix(app_controller): catch RuntimeError from FR1 audit hook in fallback save

The _load_active_project fallback save was wrapped in try/except for
(OSError, IOError, PermissionError) only. The FR1 audit hook raises
RuntimeError('TEST_SANDBOX_VIOLATION...') when a test tries to write
outside ./tests/. Add RuntimeError to the caught exception list so tests
that do App() / AppController() directly (without setting
active_project_path) don't crash — the empty fallback is silently skipped
and the app continues operating.

Also update tests/test_app_controller_offloading.py:tmp_session_dir
fixture to re-initialize paths after reset_paths() so paths.get_logs_dir()
honors the SLOP_LOGS_DIR env var instead of raising RuntimeError.
This commit is contained in:
2026-06-19 12:40:26 -04:00
parent 63e91198ac
commit cb68d86f23
2 changed files with 9 additions and 4 deletions
+3 -3
View File
@@ -2218,15 +2218,15 @@ class AppController:
self.active_project_path = fallback_path self.active_project_path = fallback_path
if fallback_path not in self.project_paths: if fallback_path not in self.project_paths:
self.project_paths.append(fallback_path) self.project_paths.append(fallback_path)
except (OSError, IOError, PermissionError) as e: except (OSError, IOError, PermissionError, RuntimeError) as e:
logging.getLogger(__name__).debug( logging.getLogger(__name__).debug(
"Could not save fallback project to %s: %s", fallback_path, e, "Could not save fallback project to %s: %s", fallback_path, e,
extra={"source": "app_controller._load_active_project.fallback_save"}, extra={"source": "app_controller._load_active_project.fallback_save"},
) )
# The save is best-effort; the app can still operate without persisting # The save is best-effort; the app can still operate without persisting
# the empty fallback (e.g., when the test sandbox FR1 guard blocks # the empty fallback (e.g., when the test sandbox FR1 guard blocks
# writes to the project root). active_project_path stays empty; # writes to the project root via the sys.addaudithook RuntimeError).
# the next save will use a proper path. # active_project_path stays empty; the next save will use a proper path.
self.preset_manager = presets.PresetManager(Path(self.active_project_path).parent if self.active_project_path else None) self.preset_manager = presets.PresetManager(Path(self.active_project_path).parent if self.active_project_path else None)
self.tool_preset_manager = tool_presets.ToolPresetManager(Path(self.active_project_path).parent if self.active_project_path else None) self.tool_preset_manager = tool_presets.ToolPresetManager(Path(self.active_project_path).parent if self.active_project_path else None)
from src.personas import PersonaManager from src.personas import PersonaManager
+6 -1
View File
@@ -18,7 +18,12 @@ def tmp_session_dir(tmp_path, monkeypatch):
monkeypatch.setenv("SLOP_LOGS_DIR", str(logs_dir)) monkeypatch.setenv("SLOP_LOGS_DIR", str(logs_dir))
monkeypatch.setenv("SLOP_SCRIPTS_DIR", str(scripts_dir)) monkeypatch.setenv("SLOP_SCRIPTS_DIR", str(scripts_dir))
paths.reset_paths() # v3 paths.py: reset_paths() clears the singleton. Re-initialize with an
# empty config (no [paths] section) so the SLOP_LOGS_DIR env var is honored
# via _resolve_path. Without this, paths.get_logs_dir() raises RuntimeError.
empty_config = tmp_path / "empty.toml"
empty_config.write_text("# no [paths] section\n")
paths.initialize_paths(empty_config)
# Ensure session_logger is clean # Ensure session_logger is clean
with patch("src.session_logger._comms_fh", None): with patch("src.session_logger._comms_fh", None):