Skip to content
Back to skills

Gotcha Memory

ASecurity

Operational learning loop — records command failures, detects recurrence and injects the lesson as a preamble before the next execution of the same task. Use to query/seed/debug the project's gotcha memory.

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 24, 2026
developmentpythonrustgobashdebuggingbackend

Works with

  • cli

Security analysis

A100/100

Scanned September 25, 2026

npx -y skills add rusharlabs/house-party-protocol --skill gotcha-memory --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Gotcha Memory?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Gotcha Memory
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/rusharlabs-gotcha-memory/badge)](https://www.skillsdirectory.com/skills/rusharlabs-gotcha-memory)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: gotcha-memory
description: Operational learning loop — records command failures, detects recurrence and injects the lesson as a preamble before the next execution of the same task. Use to query/seed/debug the project's gotcha memory.
---

> **Auto-Trigger:** When the user asks "why does this always fail?", "what have we already learned about this task?", wants to seed curated rules, or wants to inspect/clean the project's failure memory.
> **Keywords:** "gotcha", "gotchas", "recurring failure", "recurring error", "lesson learned", "failure memory", "preamble", "curated", "rule seed", "operational learning"
> **Priority:** MEDIUM
> **Tools:** Bash, Read

## When NOT to Activate
- Debugging ONE new, one-off failure — this module handles RECURRENCE (>= N identical failures within a window); for the first occurrence, debug normally.
- Managing conversational/semantic memory (session context, RAG) — this module is memory of EXECUTION FAILURES, not of knowledge.

## Contract

**INPUT:** Bash failure events (recorded automatically by the `gotcha_postflight.py` hook) + your curated lessons (YAML/JSONL seed). Config in `gotchas.*`/`paths.gotcha_store` of `operator-profile.yaml`.

**OUTPUT:** `failures.jsonl` + `curated.jsonl` in the store (default `.claude/gotchas/`) and a "⚠️ GOTCHAS" preamble on the preflight's stderr when a lesson matches the task. On `--export-unknown` (step 5), one `hpp.decision-suite/v1` file.

**EXIT CODES:**

| Exit | Meaning |
|---|---|
| 0 | always (hooks and CLI, except the `--export-unknown` refusal below) — the learning loop never breaks the caller's flow |
| 1 | only in `--self-test` when an assertion fails |
| 2 | only in `--export-unknown` when the output file already exists (it may hold your labels): nothing is written |

**STATE IT TOUCHES:**

| Resource | Reads/Writes | Purpose |
|---|---|---|
| `operator-profile.yaml` (`gotchas.*`, `paths.gotcha_store`) | Reads | config (window, min_count, top, store) |
| `<store>/failures.jsonl` | Writes (append) | failure events classified by family |
| `<store>/curated.jsonl` | Reads/Writes (idempotent append) | your always-on rules |
| `<store>/failures*.jsonl` → your suite file | Reads / Writes once (exclusive create; `--export-unknown` only) | offline relabel of `unknown` (step 5) |

## Process
1. **Install the hooks** (via plugin or manual wire — see README): `PreToolUse Bash → gotcha_preflight.py` and `PostToolUse` + `PostToolUseFailure Bash → gotcha_postflight.py`, all `timeout: 30`, WARN-only. The two exit events point to the same script because the host emits `PostToolUseFailure` when Bash fails — the case this memory exists to record.
2. **Seed your rules** (optional, idempotent):
   ```bash
   python "${CLAUDE_PLUGIN_ROOT}/_lib/gotchas_memory.py" --seed curated.seed.example.yaml
   ```
3. **Query what the system has learned** about a task:
   ```bash
   python "${CLAUDE_PLUGIN_ROOT}/_lib/gotchas_memory.py" --preamble "run the site deploy"
   ```
4. **See the whole loop working** (demo with a temporary store, zero effect on the project):
   ```bash
   python "${CLAUDE_PLUGIN_ROOT}/_lib/gotchas_memory.py"
   ```
5. **Relabel `unknown` — offline, run by a person** (optional; requires the HPP core, `python -m hpp`).
   A message no family matched stays `unknown`: the hooks never relabel it and never call a decider.
   To find a family worth adding, export the store's `unknown` messages as an `hpp.decision-suite/v1`
   (redacted, secret-shaped ones left out, repeats merged into `count`; the options are the families
   without `unknown`, so an abstention maps back to it), label every case, measure your decider (a
   command that reads `{"question", "state"}` JSON on stdin and prints one `hpp.decision/v1`
   record), and keep its per-message records beside the store:
   ```bash
   python "${CLAUDE_PLUGIN_ROOT}/_lib/gotchas_memory.py" --export-unknown unknown-suite.json
   # label every case: "expected": "<family>" or "abstain" (an unlabelled case is refused, exit 2)
   python -m hpp decide eval unknown-suite.json --decider-command '["python", "my_decider.py"]'
   STORE=.claude/gotchas                  # or your paths.gotcha_store
   python - unknown-suite.json "$STORE/decisions" '["python", "my_decider.py"]' <<'PY'
   import json, pathlib, subprocess, sys
   suite = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8"))
   out = pathlib.Path(sys.argv[2]); out.mkdir(parents=True, exist_ok=True)
   failed = []
   for case in suite["cases"]:
       request = json.dumps({"question": suite["question"], "state": case["state"]}, ensure_ascii=True)
       answer = subprocess.run(json.loads(sys.argv[3]), input=request, capture_output=True, text=True)
       if answer.returncode != 0:  # a failed decider leaves no record behind
           failed.append(case["id"]); print(f"decider failed on {case['id']} (exit {answer.returncode})", file=sys.stderr)
           continue
       (out / (case["id"] + ".json")).write_text(answer.stdout, encoding="utf-8")
   sys.exit(1 if failed else 0)
   PY
   for r in "$STORE"/decisions/*.json; do [ -e "$r" ] || continue; python -m hpp decide validate "$r"; done
   ```
   The export never overwrites its output. The records are advisory: nothing in the kit reads them.
   Trust a decider only after `decide eval` passes on your own labels (selective accuracy, zero
   confident errors, instrument failures counted apart). A new signal enters the family table in
   `${CLAUDE_PLUGIN_ROOT}/_lib/error_strategy.py` only as a code change made by a person, with the
   incident cited in that family's `why` — never from a record.

## Executed examples

The three below were genuinely run (2026-09-19) against a temporary store via
`GOTCHA_STORE_DIR` — zero effect on the project. The output is the real one, with the store
path abbreviated to `<store>`.

**1 · Seed the curated rules (first time):**

```
$ GOTCHA_STORE_DIR=<store> python "${CLAUDE_PLUGIN_ROOT}/_lib/gotchas_memory.py" --seed curated.seed.example.yaml
Seeded 5 curated gotchas into <store>/curated.jsonl
$ echo $?
0
```
<!-- executed: 2026-09-22 · exit=0 -->

**2 · Seed the SAME file again — the idempotency proof:**

```
$ GOTCHA_STORE_DIR=<store> python "${CLAUDE_PLUGIN_ROOT}/_lib/gotchas_memory.py" --seed curated.seed.example.yaml
Seeded 0 curated gotchas into <store>/curated.jsonl
```
<!-- executed: 2026-09-22 · exit=0 -->

Zero, not five again: re-seeding does not duplicate. That is what allows leaving the seed in a
project's setup and running it in every session.

**3 · Query what the system knows about a task:**

```
$ GOTCHA_STORE_DIR=<store> python "${CLAUDE_PLUGIN_ROOT}/_lib/gotchas_memory.py" --preamble "run the site deploy"
⚠️ GOTCHAS (do not repeat these — learned from earlier failures):
  • [rule] Deploy/routing: prove the BACKEND changed (a route exclusive to the new version + a build marker), not just the auth gate or the process status.
$ echo $?
0
```
<!-- executed: 2026-09-22 · exit=0 -->

The rule that appeared is a **curated** one (seeded in example 1) — it matched by substring
on "deploy". A **learned** lesson (from recurring failures) would appear in the same list
with the `[Nx]` counter in front.

## Proof

```bash
python "${CLAUDE_PLUGIN_ROOT}/_lib/gotchas_memory.py" --self-test     # -> self-test OK ✓  · exit 0
python "${CLAUDE_PLUGIN_ROOT}/_lib/error_strategy.py" --self-test     # -> self-test OK    · exit 0
python "${CLAUDE_PLUGIN_ROOT}/hooks/gotcha_preflight.py" --self-test  # -> self-test OK
python "${CLAUDE_PLUGIN_ROOT}/hooks/gotcha_postflight.py" --self-test # -> self-test OK
```

The four self-tests cover: classification by family (with word boundary —
an isolated `ENOTFOUND` is network, `ModuleNotFoundError` is dependency), recurrence that becomes a
gotcha after `min_count` failures within the window, seed idempotency, the hooks'
conservative detection (ambiguous = not a failure), and the relabel export (only `unknown`,
secret-shaped left out, an existing output never overwritten).

**Operational success criterion:** after 3 identical failures recorded in < 24h,
`--preamble "<same task>"` returns a non-empty preamble with `[3x]`.

## Known failures / limits
- Failure detection is CONSERVATIVE: only exit code != 0, `is_error: true` or a non-empty `error` field count. A "silent" failure (exit 0 with wrong output) is not captured — by design (it does not invent failures).
- Covers only the Bash tool (the hooks' matcher). Other tools would require new matchers.
- YAML seed requires PyYAML; without PyYAML, use a `.jsonl` seed (stdlib).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.