BUG-HUNT: [error-handling] PersonaRegistry.get() does not catch pydantic.ValidationError — a corrupted persona YAML file crashes the TUI on startup with an unhandled exception #6609

Open
opened 2026-04-09 22:13:50 +00:00 by HAL9000 · 0 comments
Owner

Bug Report: Error Handling — Uncaught ValidationError in PersonaRegistry.get()

Severity Assessment

  • Impact: If any persona YAML file on disk contains structurally invalid data (e.g. missing actor field, bad cycle_order value, actor without a / namespace separator), PersonaRegistry.get() raises a pydantic.ValidationError that propagates uncaught through PersonaState.active_persona()_refresh_persona_bar()on_mount(), crashing the TUI on every startup. The TUI becomes completely unusable until the corrupt file is manually repaired or deleted.
  • Likelihood: Medium — persona files are user-editable YAML; any hand-edit mistake triggers this.
  • Priority: High

Location

  • File: src/cleveragents/tui/persona/registry.py
  • Function/Class: PersonaRegistry.get
  • Lines: 118–123

Description

PersonaRegistry.list_personas() correctly wraps Persona.model_validate() in a try/except that catches ValidationError:

# registry.py — list_personas() — SAFE
for file in sorted(self.personas_dir.glob("*.y*ml")):
    try:
        raw = yaml.safe_load(file.read_text(...)) or {}
        personas.append(Persona.model_validate(raw))
    except (yaml.YAMLError, ValidationError) as exc:   # ← correctly caught
        _logger.warning("Skipping invalid persona file %s: %s", file, exc)

However, PersonaRegistry.get() — the method used to load the active persona — does not catch ValidationError:

# registry.py — get() — UNSAFE
def get(self, name: str) -> Persona | None:
    file = self.persona_path(name)
    if not file.exists():
        return None
    raw = yaml.safe_load(file.read_text(encoding="utf-8")) or {}
    if not isinstance(raw, dict):
        return None
    return Persona.model_validate(raw)   # ← ValidationError propagates uncaught!

Crash chain on TUI startup:

_TextualCleverAgentsTuiApp.on_mount()
  → self._refresh_persona_bar()
    → self._persona_state.active_persona(session_id)
      → self.registry.get(name)
        → Persona.model_validate(raw)   ← ValidationError raised

The exception bubbles up unhandled, crashing the Textual app with a traceback.

Evidence: Triggers

A persona YAML file would fail validation if:

  • actor field missing: Persona.validate_actor_ref raises ValueError
  • actor has no /: e.g. actor: "claude" — validator requires namespace/name
  • name contains path separators: name: "../other"
  • cycle_order is negative
  • argument_presets lacks a "default" entry, or has multiple "default" entries

All of these would be silently skipped by list_personas() but would crash get().

Expected Behavior

get() should catch pydantic.ValidationError (and yaml.YAMLError for consistency) and return None, matching the behavior of list_personas(). PersonaState.active_persona() already handles the None case by falling back to ensure_default().

Actual Behavior

Any validation error in the active persona file crashes the TUI on startup with an unhandled pydantic.ValidationError.

Suggested Fix

def get(self, name: str) -> Persona | None:
    file = self.persona_path(name)
    if not file.exists():
        return None
    try:
        raw = yaml.safe_load(file.read_text(encoding="utf-8")) or {}
    except yaml.YAMLError as exc:
        _logger.warning("Skipping invalid persona file %s: %s", file, exc)
        return None
    if not isinstance(raw, dict):
        return None
    try:
        return Persona.model_validate(raw)
    except ValidationError as exc:
        _logger.warning("Skipping invalid persona file %s: %s", file, exc)
        return None

Category

error-handling

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: @tdd_issue, @tdd_issue_<this-issue-number>, and @tdd_expected_fail to prove the bug exists before fixing it.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: bug-hunter

## Bug Report: Error Handling — Uncaught ValidationError in PersonaRegistry.get() ### Severity Assessment - **Impact**: If any persona YAML file on disk contains structurally invalid data (e.g. missing `actor` field, bad `cycle_order` value, actor without a `/` namespace separator), `PersonaRegistry.get()` raises a `pydantic.ValidationError` that propagates uncaught through `PersonaState.active_persona()` → `_refresh_persona_bar()` → `on_mount()`, crashing the TUI on every startup. The TUI becomes completely unusable until the corrupt file is manually repaired or deleted. - **Likelihood**: Medium — persona files are user-editable YAML; any hand-edit mistake triggers this. - **Priority**: High ### Location - **File**: `src/cleveragents/tui/persona/registry.py` - **Function/Class**: `PersonaRegistry.get` - **Lines**: 118–123 ### Description `PersonaRegistry.list_personas()` correctly wraps `Persona.model_validate()` in a try/except that catches `ValidationError`: ```python # registry.py — list_personas() — SAFE for file in sorted(self.personas_dir.glob("*.y*ml")): try: raw = yaml.safe_load(file.read_text(...)) or {} personas.append(Persona.model_validate(raw)) except (yaml.YAMLError, ValidationError) as exc: # ← correctly caught _logger.warning("Skipping invalid persona file %s: %s", file, exc) ``` However, `PersonaRegistry.get()` — the method used to load the **active persona** — does **not** catch `ValidationError`: ```python # registry.py — get() — UNSAFE def get(self, name: str) -> Persona | None: file = self.persona_path(name) if not file.exists(): return None raw = yaml.safe_load(file.read_text(encoding="utf-8")) or {} if not isinstance(raw, dict): return None return Persona.model_validate(raw) # ← ValidationError propagates uncaught! ``` **Crash chain on TUI startup:** ``` _TextualCleverAgentsTuiApp.on_mount() → self._refresh_persona_bar() → self._persona_state.active_persona(session_id) → self.registry.get(name) → Persona.model_validate(raw) ← ValidationError raised ``` The exception bubbles up unhandled, crashing the Textual app with a traceback. ### Evidence: Triggers A persona YAML file would fail validation if: - `actor` field missing: `Persona.validate_actor_ref` raises `ValueError` - `actor` has no `/`: e.g. `actor: "claude"` — validator requires `namespace/name` - `name` contains path separators: `name: "../other"` - `cycle_order` is negative - `argument_presets` lacks a `"default"` entry, or has multiple `"default"` entries All of these would be silently skipped by `list_personas()` but would crash `get()`. ### Expected Behavior `get()` should catch `pydantic.ValidationError` (and `yaml.YAMLError` for consistency) and return `None`, matching the behavior of `list_personas()`. `PersonaState.active_persona()` already handles the `None` case by falling back to `ensure_default()`. ### Actual Behavior Any validation error in the active persona file crashes the TUI on startup with an unhandled `pydantic.ValidationError`. ### Suggested Fix ```python def get(self, name: str) -> Persona | None: file = self.persona_path(name) if not file.exists(): return None try: raw = yaml.safe_load(file.read_text(encoding="utf-8")) or {} except yaml.YAMLError as exc: _logger.warning("Skipping invalid persona file %s: %s", file, exc) return None if not isinstance(raw, dict): return None try: return Persona.model_validate(raw) except ValidationError as exc: _logger.warning("Skipping invalid persona file %s: %s", file, exc) return None ``` ### Category error-handling ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: `@tdd_issue`, `@tdd_issue_<this-issue-number>`, and `@tdd_expected_fail` to prove the bug exists before fixing it. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-09 22:25:28 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
cleveragents/cleveragents-core#6609
No description provided.