---
title: "Silent Exception Swallow + Falsy Guard — Double Silent Failure"
description: "Function catches exception and returns '' (empty string); caller uses 'if result:' guard that evaluates falsy on '' — silently skips the save/action with no trace"
tags: [python, silent-failure, exception-handling, debugging]
source: daily/2026-05-08.md
created: 2026-05-08
updated: 2026-05-08
---

# Silent Exception Swallow + Falsy Guard — Double Silent Failure

## The Pattern

Two anti-patterns combine to produce a completely silent failure:

1. A function catches an exception and returns a falsy value (empty string `""`, `None`, `0`, `[]`)
2. The caller checks the result with a truthiness guard (`if result:`) before taking action

When an error occurs, the function silently returns `""`, the caller silently skips the action, and nothing is logged.

```python
# WRONG — double silent failure
class TranslationService:
    def _translate_text(self, text: str, lang: str) -> str:
        try:
            return self._call_translation_api(text, lang)
        except Exception as e:
            logger.error("Translation failed: %s", e)   # logged but...
            return ""                                    # ...swallowed here

# Caller — silently skips save when translation fails
translated_text = service._translate_text(text, "nl")
if translated_text:                    # evaluates False on ""
    vtt_storage.save(translated_text)  # never called on error
```

## Real-World Incident

In `video-accessibility`, Dutch (NL) VTT files were never saved. The translation service caught API errors and returned `""`. The pipeline runner checked `if translated_text:` before saving — falsy check skipped the save silently. The job appeared to succeed with no errors in logs. The NL VTT simply never appeared in storage.

## The Fix

Raise the exception instead of swallowing it. Let the caller decide error handling:

```python
# CORRECT — raise, don't swallow
class TranslationService:
    def _translate_text(self, text: str, lang: str) -> str:
        try:
            return self._call_translation_api(text, lang)
        except Exception as e:
            logger.error("Translation failed for lang=%s: %s", lang, e, exc_info=True)
            raise   # ← propagate so caller knows something went wrong

# Caller — now sees the error
try:
    translated_text = service._translate_text(text, "nl")
    vtt_storage.save(translated_text)
except Exception:
    # Mark translation as failed, not silently skip it
    job.mark_translation_failed(lang="nl")
```

## Key Rules

- `exc_info=True` in `logger.error()` captures the full stack trace — without it, you only get the message
- Functions that return a "result or empty" should be treated with suspicion: callers must handle the empty case explicitly, not via truthiness
- Prefer `Optional[str]` with explicit `None` check over empty string as a sentinel — `None` is more obviously "no value" than `""`

## Related

- [[wiki/concepts/mongodb-cross-collection-id-confusion]] — another silent None pattern
- [[wiki/concepts/fastapi-response-model-silent-field-strip]] — FastAPI silently strips fields
