---
name: gavin844-fail-loud
title: fail loud
kind: skill
description: >
  Make failures visible instead of silent. Use when writing exception handling,
  truthiness checks, resource lifecycles, or any lookup that can return nothing.
  Prevents bugs that present as working code.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/fail-loud
brought_by: kt
license: MIT
---

# Fail loud

**The expensive bugs are not the ones that crash. They are the ones that return a
plausible answer.** A crash costs a turn. A silent wrong answer costs whatever was
decided on it.

Each pattern below is one I shipped this week.

## 1. Never `except: pass` in a data path

```python
try:
    ids = _starter_ids(day)      # needs `requests`
except Exception:
    pass                         # <- swallowed a NameError: requests not imported
```

The recent-form blend became a **no-op that still printed confident, unchanged
numbers**. It looked exactly like working code. I only found it because a probability I
expected to move did not.

```python
except Exception as e:           # noqa: BLE001
    print(f"[ttsf] recent-form blend unavailable: {e}")
```

If a failure is genuinely acceptable, say so out loud, once, with the reason. If it is
not acceptable, let it raise. `pass` is neither.

## 2. `if x:` is not `if x is not None:`

```python
if h3:                    # 0.0 is a VALID recent ERA
    era = blend(season, h3)
```

A pitcher with three scoreless starts had his form silently ignored, and I only noticed
because two runs of the "same" analysis disagreed. For any numeric that can legitimately
be `0` (ERA, edge, count, price, delta), use `is not None`.

Same family: `x or default` discards a legitimate `0`, `""`, `[]`, and `False`.

## 3. A lookup that finds nothing must not look like one that found something

```python
def game_pk(gid):
    if gid.startswith("mlb-"):
        return gid.split("-")[-1]
    return None            # every hash-form id, silently unresolvable
```

Every bet on a game whose id came from the other ingest path became **permanently
ungradeable**: never settled, never reported, never fed back into the model. No error
anywhere. Either handle the other shape or make the caller state what it did with
nothing.

## 4. Resources: write then commit, and do not use what you closed

- **`kv_set` without a transaction never persisted.** Every run re-discovered an
  exhausted API quota with a real request, because the note saying "exhausted" was never
  committed. Check whether your helper commits; if not, wrap it.
- **Building a payload dict after `con.close()`** threw `Cannot operate on a closed
  database`. Compute values while the resource is open; assemble afterwards.

## 5. Do not hardcode a list where a glob is correct

```python
PROP_PAGES = ["book_mlb_player_props.html", ...]     # three naming schemes exist
```

The readiness check read **17** rows while **246** sat in files whose names were not in
the list, and blocked a good card. Twice, because I fixed the list rather than the
approach. When names are produced by another program, **enumerate, then filter**, and
add the freshness filter you now need because you are seeing every file.

## 6. A display label is not a key

```python
m = models.get(_MODEL_KEY.get(_mkt(c)))   # _mkt returns "1ST TEAM TO SCORE"
```

Returned `{}` for every market, so a bias correction ran on nothing and reported
success. Keys and labels are different types even when both are strings; the moment
there is a human-readable name, the machine name must come from the raw field.

## The habit

After writing any `except`, any `if <numeric>`, any `.get()`, any lookup that can miss:

> **If this silently did nothing, would I be able to tell?**

If not, add the print, or the raise, or the `is not None`. The check costs one line. The
bug it hides costs a day and, here, real money.

## Related skills

Checked 2026-08-21 against the ~100 skills installed here and the public collections
before writing this one, after the user asked whether well-made skills already existed.
They did for two of the six I drafted, and those were folded into the existing skills
rather than duplicated:

- `verification-before-completion` - proving a claim before making it. Covers the
  ground my draft "read-the-artifact" would have; the two failure modes it was missing
  (grepping your own output for success, and treating a dry run as the artifact) were
  added to it instead.
- `systematic-debugging` - root cause before fixes.
- `karpathy-guidelines` - surfacing assumptions, surgical changes.
- `ponytail` - whether the thing needs to exist at all. Which is the check I skipped
  when I wrote six skills without looking for prior art first.

Nothing installed or public covered freshness-of-inputs, config-that-nothing-reads,
silent-failure patterns, or shell-escaping corruption when authoring files, which is
why those four exist.
