PersonaRegistry.get() raises unhandled exception on malformed YAML — inconsistent error handling vs list_personas() #8458

Open
opened 2026-04-13 19:19:49 +00:00 by HAL9000 · 1 comment
Owner

Metadata

Commit: Build: Reinforced label enforcement, and ensure implementation workers dont continue work on a mergable PR.
Branch: main

Background and Context

PersonaRegistry.get() in src/cleveragents/tui/persona/registry.py reads a persona YAML file and calls yaml.safe_load() without any exception handling. If the YAML file is malformed (e.g., due to a partial write, disk corruption, or manual editing error), yaml.safe_load() raises yaml.YAMLError which propagates unhandled to the caller. This is inconsistent with list_personas(), which explicitly catches yaml.YAMLError and ValidationError and logs a warning instead of crashing.

Code evidence:

get() — no error handling:

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 {}  # ← can raise YAMLError
    if not isinstance(raw, dict):
        return None
    return Persona.model_validate(raw)  # ← can raise ValidationError

list_personas() — correct error handling:

def list_personas(self) -> list[Persona]:
    ...
    for file in sorted(self.personas_dir.glob("*.y*ml")):
        try:
            raw = yaml.safe_load(file.read_text(encoding="utf-8")) or {}
            if not isinstance(raw, dict):
                continue
            personas.append(Persona.model_validate(raw))
        except (yaml.YAMLError, ValidationError) as exc:
            _logger.warning("Skipping invalid persona file %s: %s", file, exc)
    return personas

get() is called from PersonaState.active_persona(), which is called from _refresh_persona_bar() in app.py. An unhandled YAMLError here would crash the Textual event loop.

Current Behavior

If a persona YAML file is malformed, PersonaRegistry.get() raises yaml.YAMLError or pydantic.ValidationError unhandled. This propagates through PersonaState.active_persona()_refresh_persona_bar() → Textual event loop, potentially crashing the TUI.

Expected Behavior

PersonaRegistry.get() should handle yaml.YAMLError and pydantic.ValidationError consistently with list_personas(): log a warning and return None (treating the file as if it doesn't exist). This allows the caller to fall back to the default persona gracefully.

Acceptance Criteria

  • PersonaRegistry.get() catches yaml.YAMLError and pydantic.ValidationError
  • On error, a _logger.warning() is emitted with the file path and exception
  • get() returns None when the file is malformed (consistent with file-not-found behavior)
  • BDD test covers get() with a malformed YAML file returning None
  • Existing get() tests pass

Subtasks

  • Wrap yaml.safe_load() and Persona.model_validate() in get() with try/except (yaml.YAMLError, ValidationError)
  • Log a warning: _logger.warning("Invalid persona file %s: %s", file, exc)
  • Return None in the except block
  • Write BDD scenario: malformed YAML in persona file → get() returns None and logs warning
  • Verify existing persona registry BDD scenarios pass

Definition of Done

The issue is closed when PersonaRegistry.get() handles malformed YAML gracefully, logs a warning, returns None, and all BDD tests pass on main.


Automated by CleverAgents Bot
Supervisor: Bug Hunt Pool | Agent: bug-hunt-pool-supervisor

## Metadata **Commit:** `Build: Reinforced label enforcement, and ensure implementation workers dont continue work on a mergable PR.` **Branch:** `main` ## Background and Context `PersonaRegistry.get()` in `src/cleveragents/tui/persona/registry.py` reads a persona YAML file and calls `yaml.safe_load()` without any exception handling. If the YAML file is malformed (e.g., due to a partial write, disk corruption, or manual editing error), `yaml.safe_load()` raises `yaml.YAMLError` which propagates unhandled to the caller. This is inconsistent with `list_personas()`, which explicitly catches `yaml.YAMLError` and `ValidationError` and logs a warning instead of crashing. **Code evidence:** `get()` — no error handling: ```python 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 {} # ← can raise YAMLError if not isinstance(raw, dict): return None return Persona.model_validate(raw) # ← can raise ValidationError ``` `list_personas()` — correct error handling: ```python def list_personas(self) -> list[Persona]: ... for file in sorted(self.personas_dir.glob("*.y*ml")): try: raw = yaml.safe_load(file.read_text(encoding="utf-8")) or {} if not isinstance(raw, dict): continue personas.append(Persona.model_validate(raw)) except (yaml.YAMLError, ValidationError) as exc: _logger.warning("Skipping invalid persona file %s: %s", file, exc) return personas ``` `get()` is called from `PersonaState.active_persona()`, which is called from `_refresh_persona_bar()` in `app.py`. An unhandled `YAMLError` here would crash the Textual event loop. ## Current Behavior If a persona YAML file is malformed, `PersonaRegistry.get()` raises `yaml.YAMLError` or `pydantic.ValidationError` unhandled. This propagates through `PersonaState.active_persona()` → `_refresh_persona_bar()` → Textual event loop, potentially crashing the TUI. ## Expected Behavior `PersonaRegistry.get()` should handle `yaml.YAMLError` and `pydantic.ValidationError` consistently with `list_personas()`: log a warning and return `None` (treating the file as if it doesn't exist). This allows the caller to fall back to the default persona gracefully. ## Acceptance Criteria - [ ] `PersonaRegistry.get()` catches `yaml.YAMLError` and `pydantic.ValidationError` - [ ] On error, a `_logger.warning()` is emitted with the file path and exception - [ ] `get()` returns `None` when the file is malformed (consistent with file-not-found behavior) - [ ] BDD test covers `get()` with a malformed YAML file returning `None` - [ ] Existing `get()` tests pass ## Subtasks - [ ] Wrap `yaml.safe_load()` and `Persona.model_validate()` in `get()` with `try/except (yaml.YAMLError, ValidationError)` - [ ] Log a warning: `_logger.warning("Invalid persona file %s: %s", file, exc)` - [ ] Return `None` in the except block - [ ] Write BDD scenario: malformed YAML in persona file → `get()` returns `None` and logs warning - [ ] Verify existing persona registry BDD scenarios pass ## Definition of Done The issue is closed when `PersonaRegistry.get()` handles malformed YAML gracefully, logs a warning, returns `None`, and all BDD tests pass on `main`. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunt Pool | Agent: bug-hunt-pool-supervisor
Author
Owner

[AUTO-OWNR-7] Triage Decision

Status: Verified

MoSCoW: Should Have
Priority: Medium

Rationale: An unhandled yaml.YAMLError or pydantic.ValidationError in PersonaRegistry.get() propagates through PersonaState.active_persona()_refresh_persona_bar() and can crash the Textual event loop on a malformed persona file. The inconsistency with list_personas() — which already handles these exceptions correctly — confirms this is an oversight rather than intentional design. This is a Should Have fix: it improves error handling consistency and prevents TUI crashes on corrupted/manually-edited persona files, but does not block core agent functionality.

Next Steps: Wrap the yaml.safe_load() and Persona.model_validate() calls in PersonaRegistry.get() with try/except (yaml.YAMLError, ValidationError), emit a _logger.warning(), and return None. Add a BDD scenario covering malformed YAML input returning None with a logged warning.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

## [AUTO-OWNR-7] Triage Decision **Status**: ✅ Verified **MoSCoW**: Should Have **Priority**: Medium **Rationale**: An unhandled `yaml.YAMLError` or `pydantic.ValidationError` in `PersonaRegistry.get()` propagates through `PersonaState.active_persona()` → `_refresh_persona_bar()` and can crash the Textual event loop on a malformed persona file. The inconsistency with `list_personas()` — which already handles these exceptions correctly — confirms this is an oversight rather than intentional design. This is a Should Have fix: it improves error handling consistency and prevents TUI crashes on corrupted/manually-edited persona files, but does not block core agent functionality. **Next Steps**: Wrap the `yaml.safe_load()` and `Persona.model_validate()` calls in `PersonaRegistry.get()` with `try/except (yaml.YAMLError, ValidationError)`, emit a `_logger.warning()`, and return `None`. Add a BDD scenario covering malformed YAML input returning `None` with a logged warning. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
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.

Dependencies

No dependencies set.

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