---
status: complete
priority: p1
issue_id: "001"
tags: [code-review, type-safety, python]
---

# Mistral response content typed `str` but is `str | None`

## Problem Statement

`response.choices[0].message.content` is typed by the Mistral SDK as `str | None`, but `summarize_text()` declares `-> str` and returns it directly without a None check. If Mistral ever returns empty content (rate limit edge case, model refusal), this causes an `AttributeError` or `TypeError` at the caller, mid-generator, with no helpful error message.

## Findings

**File:** `condensr/summarizer.py:69`

```python
return response.choices[0].message.content  # str | None — annotated as str
```

This is a silent type contract violation. The function promises `str` but can return `None`.

## Proposed Solutions

### Option A: Assert non-None (Recommended)
Add a guard that raises a clear, actionable error:

```python
content = response.choices[0].message.content
if content is None:
    raise ValueError(
        f"Mistral returned no content for chapter '{chapter_title}'. "
        "Check your API key and model name."
    )
return content
```

**Pros:** Correct annotation, clear error message, O(1) effort.
**Cons:** None.
**Effort:** Small. **Risk:** None.

### Option B: Widen return type to `str | None`
Change signature to `-> str | None` and let callers handle it.

**Pros:** Technically honest.
**Cons:** Forces every caller to handle `None`; degrades API ergonomics. Removes the library's responsibility to give a helpful error.
**Effort:** Small. **Risk:** Breaking change for callers relying on `-> str`.

## Recommended Action

Option A.

## Technical Details

- **File:** `condensr/summarizer.py`
- **Line:** 69

## Acceptance Criteria

- [ ] `response.choices[0].message.content` is checked for `None` before return
- [ ] A clear `ValueError` is raised if content is `None`
- [ ] Return type annotation remains `-> str`
- [ ] Existing tests still pass
