Tasks 7.2 + 7.3: Replace inline try/except with sys.stderr.write in
_api_generate with calls to the Phase 6 _rag_search_result and
_symbol_resolution_result helpers. Errors are now carried in
self._last_request_errors instead of being logged silently.
Per Phase 7 spec 22.5.1 + 22.5.2:
- L242 (RAG): calls controller._rag_search_result(user_msg)
- L256 (symbols): calls controller._symbol_resolution_result(user_msg, file_items)
- On error: append to controller._last_request_errors (with op name)
- On error: stderr.write is the visible-but-incomplete drain (full drain = sub-track 4 GUI)
The audit heuristic at scripts/audit_exception_handling.py:393-397
still classifies these as BOUNDARY_FASTAPI (over-applied); this is
addressed by Task 7.6 (audit heuristic tightening).
TIER-2 READ conductor/code_styleguides/error_handling.md end-to-end
before this commit.
Replaces per-provider logging.debug body with _list_models_for_provider_result
SDK-boundary helper. Aggregates per-provider failures into self._model_fetch_errors
and returns Result with aggregated errors. Stderr summary on partial failure.
The SDK boundary (ai_client.list_models call) is the canonical place to
catch vendor exceptions and convert to ErrorInfo(kind=NETWORK), per
error_handling.md §'Boundary Types'.
Audit: INTERNAL_SILENT_SWALLOW for src/app_controller.py: 23 -> 22.
Replaces logging.debug bodies in mark_first_frame_rendered (L1355)
and _on_warmup_complete_for_timeline (L1451) with proper Result[T]
propagation:
- _write_first_frame_timeline_result() -> Result[None]
- _write_warmup_complete_timeline_result() -> Result[None]
- _record_startup_timeline_error(op_name, result): stderr write +
append to self._startup_timeline_errors for sub-track 4 GUI
The instance list is the durable data plane; the stderr write is the
best-effort visible drain (user-confirmed acceptable terminal sink
until sub-track 4 lands GUI-side error display).
Audit: INTERNAL_SILENT_SWALLOW for src/app_controller.py: 28 -> 26.
Replaces the silent-swallow logging.debug bodies in _on_sigint and
_install_sigint_exit_handler with proper Result[T] propagation:
- _shutdown_io_pool_result() -> Result[None]: wraps io_pool.shutdown
with OSError/RuntimeError/ValueError -> ErrorInfo(original=e)
- _install_signal_handler_result(handler) -> Result[None]: wraps
signal.signal() with ValueError/OSError -> ErrorInfo(original=e)
- _install_sigint_exit_handler stores result.errors[0] on
self._signal_handler_error: Optional[ErrorInfo] for sub-track 4 GUI
The os._exit(0) inside the signal handler IS the drain (Pattern 3:
intentional termination per error_handling.md:419). The stderr write
before os._exit is part of the termination pattern (Heuristic D match).
TIER-2 READ conductor/code_styleguides/error_handling.md before Phase 6.
Audit: INTERNAL_SILENT_SWALLOW for src/app_controller.py: 30 -> 28.
Adds 5 tests to lock in the data-oriented error handling contract for
src/app_controller.py:
1. test_offload_entry_payload_returns_dict
- Shape contract: _offload_entry_payload returns a dict.
2. test_migrated_method_returns_result_on_success
- Pattern template: methods migrated to Result[T] return Result[None]
with no errors on the success path. Currently FAILS because
_handle_custom_callback returns None implicitly.
3. test_migrated_method_returns_result_with_error_on_failure
- Pattern template: methods migrated to Result[T] return Result
with errors when the underlying call raises. Currently FAILS for
same reason.
4. test_app_controller_does_not_use_broad_except
- Static AST check: no 'except Exception:' clauses left in
src/app_controller.py after migration. Currently FAILS (32 sites).
5. test_offload_entry_payload_preserves_unchanged_payload
- Verifies the no-op path for non-tool entries.
The 3 currently-failing tests will turn green as the 32 INTERNAL_BROAD_CATCH
sites are migrated across Phase 2's 4 batches. The 2 currently-passing
tests verify the existing shape contract.
Refs: spec.md FR6, plan.md Task 2.1