---
schema_id: gotcha
schema_version: 1
applies_to: aiwiki/gotchas/**/*.md
filename_pattern: "{YYYY-MM-DD}-{slug}.md"
hard_cap_lines: 150
soft_target_lines: [50, 100]
required_frontmatter:
  schema_id: { type: string, equals: gotcha }
  schema_version: { type: integer }
  severity: { type: enum, values: [low, medium, high, critical] }
  date: { type: date }
  occurrences: { type: integer, default: 1 }
  status: { type: enum, values: [active, watch, promotion-pending, promoted-to-rule, rejected, retired] }
  proposed_rule: { type: object, optional: true }
required_sections:
  - "## What broke"
  - "## Reproducer"
  - "## Root cause"
  - "## Fix"
  - "## Prevention"
section_order: strict
citation_rule: required
---

# Schema: gotcha (recurring failure)

## Purpose

A gotcha records a failure pattern that has occurred and might recur. The page answers "have we hit this before? what's the fix? how do we avoid it next time?" The point is fast recall during debugging or code review, not a debugging narrative.

## When to write one

- A debugging session resolves an unexpected failure
- The same root cause has caused trouble before (search `aiwiki/gotchas/` first; bump `occurrences` if it matches)
- A library/framework misuse pattern is identified that's not obvious from the docs

Do NOT write a gotcha for: bugs in your own code that weren't framework- or pattern-related, one-off typos, problems already documented in `aiwiki/conventions/` (write the convention instead).

## File location and naming

- Path: `aiwiki/gotchas/{YYYY-MM-DD}-{slug}.md`
- Date: ISO format (`YYYY-MM-DD`)
- Slug: kebab-case, ≤8 words, describing the pattern (not the symptom)

Example: `2026-05-10-stub-logger-silently-drops-events.md`

## Required frontmatter

| Field | Type | Notes |
|---|---|---|
| `schema_id` | string | Must equal `gotcha` |
| `schema_version` | integer | — |
| `severity` | enum | `low` / `medium` / `high` / `critical` (impact + likelihood) |
| `date` | ISO date | First occurrence |
| `occurrences` | integer | Bumped each time AI hits this. N=2 flips status to `watch`. N=3 flips status to `promotion-pending`, triggers `proposed_rule:` auto-draft, and surfaces a session-start prompt to promote the gotcha into a project rule. |
| `status` | enum | Auto-promotion path: `active` (N=1) → `watch` (N=2) → `promotion-pending` (N=3, draft attached). Terminal states: `promoted-to-rule` (rule shipped), `rejected` (user declined promotion at session-start), `retired` (no longer applies — explain in body). See `support-gotcha` SKILL.md for state-transition rules. |
| `proposed_rule` | object (optional) | Auto-drafted at N=3 alongside `status: promotion-pending`; `{ rule_path: <target>, draft: <text>, drafted_at: <date> }` |

## Required sections

| Section | Purpose | Format |
|---|---|---|
| `## What broke` | One sentence — what failed and where | Cite the failure point |
| `## Reproducer` | Minimal steps or code link | Cite if linking to existing test |
| `## Root cause` | What was actually wrong | Cite the responsible code with `file:line@<sha7>` |
| `## Fix` | The resolution | Cite the fix code |
| `## Prevention` | Rule, check, or pattern that prevents recurrence | Cite a convention or rule if one exists |

## Line caps

- Hard cap: 150 lines (LINT fails above)
- Soft target: 50-100 lines

A gotcha that needs more than 150 lines is probably two gotchas, or it's narrative debug logs that don't belong here.

## Citation rules

- Every section has at least one citation (this is a stricter rule than ADRs)
- Code references use `file:line@<sha7>` or `symbol` form
- LINT auto-fills missing `@<sha7>` on first save
- If the cited code has been removed, `status: retired` and explain in body

## Skeleton

```markdown
---
schema_id: gotcha
schema_version: 1
severity: high
date: 2026-04-28
occurrences: 1
status: active
---

## What broke

Logger calls returned successfully but no events appeared in the log destination during integration tests. ([src/lib/logger.ts:14@a3f2bc1](src/lib/logger.ts:14))

## Reproducer

1. Initialize logger with default config
2. Call `logger.info("event")`
3. Inspect the configured destination — no events present

## Root cause

`createLogger()` returned a stub when no transport was configured ([src/lib/logger.ts:14@a3f2bc1](src/lib/logger.ts:14)). The stub silently dropped all events instead of failing or warning.

## Fix

Throw on missing transport configuration ([src/lib/logger.ts:14@b1c2d3e](src/lib/logger.ts:14)) — fail loud at boot rather than silently at runtime.

## Prevention

- Convention: every framework boundary that can no-op MUST throw or warn instead. See [aiwiki/conventions/no-silent-stubs.md](aiwiki/conventions/no-silent-stubs.md).
- Test pattern: integration tests assert their log assertions actually fire (use a recording transport, not a fake stub).
```
