Skip to content

Commit 4233c1b

Browse files
authored
Merge pull request #91 from d-morrison/add-reprexes-skill
Add the "reprexes" skill (.claude/skills/reprexes)
2 parents 2a6103d + f9cae3f commit 4233c1b

1 file changed

Lines changed: 148 additions & 0 deletions

File tree

.claude/skills/reprexes/SKILL.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
---
2+
name: reprexes
3+
description: >
4+
Isolate a technical problem into a minimal reproducible example ("reprex")
5+
and iterate fixes on that instead of inside the full application. Use when
6+
debugging a bug whose cause isn't obvious after a first look, when a failure
7+
only surfaces deep in a large pipeline / app / render, when the full-context
8+
test loop is slow, or before filing an upstream issue. Invoke explicitly
9+
with /reprexes.
10+
user-invocable: true
11+
allowed-tools:
12+
- Bash
13+
- Read
14+
- Write
15+
- Edit
16+
---
17+
18+
# reprexes
19+
20+
When a technical problem is hard to pin down, **don't debug it in the full
21+
context of the motivating application.** Extract a minimal, self-contained
22+
reproduction of the phenomenon, iterate candidate fixes on *that* (a fast,
23+
clean loop), then port the working fix back to the real code.
24+
25+
Reference: <https://r4ds.hadley.nz/workflow-help.html#making-a-reprex>.
26+
The payoff is real: often, the act of building a thorough reprex surfaces the
27+
cause on its own — the noise you strip away was hiding it.
28+
29+
## When this fires
30+
31+
- A bug whose cause isn't obvious after a first read of the code.
32+
- A failure that only shows up deep inside a large pipeline, app, or a full
33+
`quarto render` — where each reproduction attempt is slow.
34+
- Iterating on a fix that's expensive to test in the full context.
35+
- Before asking a human for help or filing an upstream/package issue — a
36+
reprex is what they'll ask for anyway.
37+
38+
Don't bother for a one-line obvious fix, or a problem you can already see and
39+
test cheaply in place.
40+
41+
## The two requirements
42+
43+
A reprex is only useful if it is BOTH:
44+
45+
1. **Reproducible** — it captures *everything* needed to trigger the
46+
phenomenon: every `library()` call, and code that creates every object it
47+
uses. Someone (or a fresh session) can run it cold and see the same thing.
48+
2. **Minimal** — everything unrelated to the phenomenon is stripped out:
49+
small or built-in data instead of the real dataset, only the lines that
50+
matter.
51+
52+
The tension between these two is the whole game: include enough to reproduce,
53+
but nothing more.
54+
55+
## Procedure
56+
57+
1. **Hypothesize the minimal trigger.** What is the smallest combination of
58+
data + operation you believe causes the phenomenon?
59+
2. **Create a standalone scratch file** outside the repo tree (e.g.
60+
`/tmp/reprex.R`, or a tiny `/tmp/reprex.qmd` for a render bug; on a
61+
non-Unix machine use `tempfile(fileext = ".R")` / `tempdir()` for a
62+
portable path). Put in it, in order:
63+
- the package loads (`library(...)`),
64+
- the minimal data (see tactics below),
65+
- the minimal code that triggers the phenomenon, with a comment marking
66+
where it goes wrong.
67+
3. **Run it in a clean session** and confirm it reproduces. For R, run with
68+
`Rscript /tmp/reprex.R` (a fresh process — no stale `globalenv()` state
69+
masking or faking the bug). For a Quarto page, render just that file:
70+
`quarto render /tmp/reprex.qmd --to html`, not the whole site.
71+
4. **Minimize.** Remove pieces until the phenomenon disappears — the last
72+
removal that "fixes" it implicates that piece. (Or build up from nothing
73+
until it appears.) Keep the data as small as it can be while still failing.
74+
5. **Iterate fixes on the reprex**, not the full app. This is the fast loop
75+
the whole technique exists to create.
76+
6. **Port the fix back** to the real code and verify it there.
77+
7. **Clean up.** Delete the scratch file (it lives in `/tmp`, so it never
78+
touches the repo). If the bug was subtle, consider promoting the reprex
79+
into a real regression test (`testthat`) instead of discarding it.
80+
81+
## Minimizing the data
82+
83+
- Prefer a **built-in dataset** (`mtcars`, `mpg`) or a hand-built
84+
tiny frame over the real data.
85+
- If you must use a slice of real data, serialize the minimal slice with
86+
`dput()` so the reprex recreates it inline — no external file dependency.
87+
- Shrink to the fewest rows/columns that still show the phenomenon.
88+
89+
## R / Quarto specifics
90+
91+
- In R packages and Quarto projects, reprexes are usually short R snippets or
92+
a single standalone `.qmd` page. Respect the repo's lint config if the
93+
reprex code will be ported back.
94+
- The **`reprex` package** (tidyverse, <https://reprex.tidyverse.org/>)
95+
formats a reprex for sharing: it runs your code in a clean, separate R
96+
session (via `callr` since reprex 2.0) and emits code **plus actual
97+
output**.
98+
Copy the code and call `reprex::reprex()` (reads the clipboard by default),
99+
or point it at a file with `reprex(input = "/tmp/reprex.R")` (or a
100+
`tempfile(fileext = ".R")` path on non-Unix machines) — handy from a
101+
non-interactive CLI session where there's no clipboard. Use it when the
102+
output is destined for a PR comment or an upstream issue. Useful arguments:
103+
- `venue =` — output format:
104+
- `"gh"` — GitHub-flavored Markdown (default)
105+
- `"so"` / `"ds"` — Stack Overflow / Discourse
106+
- `"slack"` — Slack message
107+
- `"r"` — runnable R script with commented output
108+
- `"html"` — HTML
109+
- `"rtf"` — rich text for presentations
110+
- `session_info = TRUE` — append `sessionInfo()` / `sessioninfo::session_info()`,
111+
so versions travel with the reprex (set this when the bug may be
112+
version-dependent).
113+
- `std_out_err = TRUE` — capture stdout/stderr too (e.g. `system()` /
114+
subprocess or C-level output that doesn't come back as normal R results).
115+
- `wd =` — set the working directory when the code needs one.
116+
- Validation bonus: because `reprex()` runs in a fresh session, if it errors
117+
on a missing object or package, your example wasn't actually
118+
self-contained — fix that before sharing.
119+
120+
Companion helpers handle "wild-caught" reprexes (all exported in reprex
121+
2.x): `reprex_clean()` (strip the `#>` output markers from a rendered/pasted
122+
reprex, leaving runnable code), `reprex_rescue()` (recover code from
123+
R-console output with `>`/`+` prompts), and `reprex_invert()` (the inverse
124+
of `reprex()` — recover the input code from a rendered reprex).
125+
- When the bug might be **version-dependent**, capture `sessionInfo()` (or set
126+
`session_info = TRUE` above) in the reprex so versions are part of the
127+
record. If you suspect *stale* packages are the cause,
128+
`tidyverse::tidyverse_update()` outside the reprex can rule that out — but it
129+
updates packages, it doesn't
130+
record versions, so don't put it in the reprex itself.
131+
- Build artifacts (`_site/`, `_freeze/`, `.quarto/`) are common confounders
132+
for "it renders differently" bugs — a clean standalone render sidesteps
133+
stale freeze caches.
134+
135+
## Before declaring the reprex good
136+
137+
- A fresh session runs it top-to-bottom and shows the phenomenon (and nothing
138+
else breaks first).
139+
- It references no object, file, or option it didn't create itself.
140+
- It's as small as you can make it and still reproduce.
141+
142+
## Don't
143+
144+
- Don't paste the entire app/module — that's the opposite of a reprex.
145+
- Don't commit scratch reprex files; keep them in `/tmp` (or `tempdir()` on
146+
non-Unix machines) or a gitignored scratch path.
147+
- Don't iterate fixes in the slow full-context loop once you have a reprex
148+
that reproduces.

0 commit comments

Comments
 (0)