Tanuki automatically executes realistic user scenarios against your Claude Code plugin and turns the recurring usability problems it discovers into concrete outcomes: the attended pipeline ends in a ranked proposal brief with a recorded disposition for each item, and the unattended overnight loop ends in a reviewable implementation batch that only you merge.
Unit tests and linters verify the code you wrote: functions return the right values, links resolve, schemas parse. They cannot tell you that a first-time user gets lost after step 2 of your README, that an error message gives no hint how to recover, or that one branch of your command asks a question nobody understands. Those problems only surface when someone actually uses the plugin — and collecting them normally means hours of manual dogfooding or waiting for user complaints. Tanuki automates the user.
The central idea: Claude acts as a simulated user, not as a code generator. For each scenario, Tanuki launches a headless Claude session on a deliberately less capable model that plays a persona — "a new user follows the quickstart", "a user picks the option you never test", "a user whose config file is broken" — inside a disposable clone of your repo. The weaker model is the point: a stronger model quietly works around rough edges, while a weaker one trips over them, which is exactly the signal a plugin author needs. The thing being tested is not the code — it's the experience of using it.
So if you've built a plugin that works when you use it — because you know exactly which buttons to press — Tanuki answers the questions you can't answer yourself: what happens to everyone else?
You stay in control. The attended pipeline is proposals-only: it runs against disposable clones and does not modify the target repo — its output is a brief for your review, and filing an issue is an explicit, per-item choice. The overnight loop writes only to its own integration branch inside a dedicated worktree — never your default branch, never your working tree — until the attended morning gate, where only you merge (on a PR-protected target the close pushes that integration branch to open one Draft PR; see Unattended overnight mode). This is the spec's standing constraint (docs/tanuki-spec.md), not a habit.
Full technical contract: docs/tanuki-spec.md.
Tanuki is itself a Claude Code plugin; this repository doubles as its marketplace. Inside Claude Code:
/plugin marketplace add tim-nish/tanuki
/plugin install tanuki@tanuki
Requirements: the claude CLI on PATH, git, Python 3. Nothing else — no
pip packages, no configuration files needed to start.
cd ~/work/my-plugin
/tanuki init # one-time onboarding: Tanuki reads your plugin's docs
# and proposes 4–6 test scenarios for your approval
/tanuki # run it — shows the plan first, drives after you approve
That's the whole loop. Every mode you can run afterward is in the Command index below.
One rule for the grammar: a bare word is a mode that doesn't drive
(init, decide, status, history, view, ingest, mine, generate,
configure, distill); flags modify a drive; the bare default is driving.
The older spellings — --brief, --status, --history, --ingest,
--mine-only — still work as aliases, so nothing in your fingers breaks.
Every run ends with a delta report — which known problems recurred and what's new — so you never need to remember previous runs.
Tanuki clones your plugin (and optionally a "host" repo it operates on) into a throwaway workspace and drives one simulated-user session per scenario. Each transcript is mechanically normalized into Events (tool errors, retries, user choices, friction notes). A mining pass turns events into Findings — deduplicated problems with a recurrence count, so a problem seen in three runs counts as chronic, not three separate complaints. Chronic findings get promoted into a brief: at most 10 ranked Proposals, each written as Problem → Proposed fix → Evidence. You then accept, dismiss, or defer each one in-session. (The stage-by-stage pipeline diagram is in Pipeline internals below; the full contract is docs/tanuki-spec.md.)
Tanuki has two user-facing command families. Everything else is internal machinery those two drive.
/tanuki— the attended pipeline you run and watch: drive a run, read the brief, decide each proposal. You are in the loop for every run./tanuki-loop— the unattended overnight loop: it repeats the whole cycle on its own integration branch and hands you one batch to review in the morning. The human gate is relocated here, not removed — only you merge.
After onboarding with /tanuki init, the flow branches into the two modes.
They deliberately end differently, because they answer different questions:
/tanuki finds problems and hands you proposals — its endpoint is a
recorded disposition for each one (accept / dismiss / defer) — while
/tanuki-loop fixes problems overnight and hands you a batch — the
morning gate ends at a delivery you review and merge: by default a
ready-for-review PR (integration → base), or a branch-only target leaves the
integration branch for you to merge. The loop never merges to main itself
(triage of #262/#263). What the two share is the
human gate /tanuki decide: it decides the attended run's proposals, and
only the loop's deferred judgment items return to it — everything the
loop implemented overnight goes through the morning gate instead.
flowchart TD
init["/tanuki init"]
init --> tanuki["/tanuki"]
init --> loop["/tanuki-loop"]
tanuki --> decide{{"/tanuki decide"}}
decide --> disposition(["disposition recorded"])
loop --> gate{{"morning gate"}}
gate --> deliver(["PR (default) or branch — you merge"])
gate -.->|deferred judgments| decide
classDef gatecls fill:#fde68a,stroke:#b45309,color:#000;
class decide,gate gatecls;
/tanuki init— one-time onboarding; writes the scenario file both modes run from./tanuki decide— the human gate for judgment, on both paths: each proposal ends with a recorded disposition — accept, dismiss, or defer. For an accepted finding you may optionally file the prepared GitHub issue (its own explicit confirmation; nothing is ever auto-filed). The loop's deferred spec/judgment items arrive here too, because the loop never decides those on its own.- morning gate — the loop's human gate, in summary: you review the
integration branch diff, run the final tests, and take delivery — approve +
merge the ready-for-review PR the loop opened (default), or merge the
branch-only target's integration branch yourself; the loop never merges to
main. The fixing already happened overnight on the loop's own branch, so this gate reviews and lands — it does not implement. The complete step-by-step specification is commands/tanuki-loop.md §2.
Everything the loop writes stays on its own integration branch — never in the repo under test — until you merge.
Every shipped command, with a link to its definition. The command file is
the authority — where a one-line description here and the command file
disagree, the command file wins; this index points into it rather than
restating it. Each tool also prints its complete option surface with --help.
/tanuki — attended pipeline (commands/tanuki.md)
| command | what it does |
|---|---|
/tanuki init |
one-time onboarding: read the plugin's docs, propose 4–6 scenarios for approval (details) |
/tanuki [target] |
drive a run — shows the plan, drives after you approve, ends in decisions |
/tanuki <t> <scenario-id>[,…] |
drive only the named scenarios |
/tanuki "<free text>" |
one-off ad-hoc scenario ("probe the present") (details) |
/tanuki <t> decide |
the human gate: record a disposition for everything pending; filing each prepared issue is optional (details) |
/tanuki <t> ingest "<text>" |
log friction you hit yourself, in plain words (details) |
/tanuki <t> status |
what's still waiting on your decision, plus generation-trigger advisories |
/tanuki <t> history [scenario] |
the long view: what's been explored, per-scenario on request |
/tanuki <t> view [name] |
the read-only view surface — picker, or jump to status, live, history, trajectory (details) |
/tanuki <t> mine <run-id> |
re-mine a crashed or interrupted run |
/tanuki <t> generate |
the regeneration pass: propose new charters at a plan gate (feature drift, empty pool, or on demand) — never automatic |
/tanuki <t> configure |
guided configuration over declared inputs and the built-in catalog — the primary way to change settings |
/tanuki <t> distill |
walk lesson candidates from the existing ledger into your configured knowledge hub |
Bare words (init, decide, status, history, view, ingest, mine,
generate, configure, distill) are modes that don't drive; a flag
modifies a drive; the bare default is driving. The older
--brief/--status/--history/--ingest/--mine-only spellings still
work as aliases.
/tanuki-loop — unattended loop (commands/tanuki-loop.md)
| command | what it does |
|---|---|
/tanuki-loop [target] |
run the overnight cycle until two quiet cycles or the iteration cap |
/tanuki-loop <t> --iterations N |
cautious first run (the cap is a ceiling, not the goal) |
/tanuki-loop <t> unresolved |
read-only: which integration branches never merged, and how stale (details) |
/tanuki-loop <t> reconcile [branch…] |
classify unmerged work per change, then land it behind a gate (details) |
The loop's internal steps — doctor, iter-start, iter-verify,
record-cycle, gate-check/gate-pr (gate-push is retired — the loop never
merges to main), finish, dispose,
reevaluate, site-record, dashboard — are driven by /tanuki-loop, not
typed by you (doctor and dashboard are the exceptions you may run by hand;
see Unattended overnight mode). Their
complete surface is tools/tanuki-loop --help.
For the full documentation map — specs, development history, and what is
authoritative today — see docs/README.md.
You'll keep using your own plugin, and you'll keep hitting things. Instead of a TODO list you'll lose, hand the observation to Tanuki in plain language:
/tanuki my-plugin ingest "The README says to look for the fact-sheet,
but I couldn't tell where it was written."
The one rule: you never classify. Don't decide whether it's a bug, a papercut, or a duplicate — that's Tanuki's bookkeeping. Your words are stored verbatim, then run through the same mining pass as an automated run: if it's semantically a problem Tanuki already knows, that finding's recurrence count goes up (your manual re-hit pushes it toward "chronic" like any other); if it's new, a new finding is created. Either way you get the same bumped-vs-new delta report. Handing over the note takes seconds — use it the moment friction bites.
A run doesn't end with "here's a report." Tanuki walks you through each promoted proposal — shown as Problem → Proposed fix, with evidence collapsed to a pointer line you'll rarely need — and records one disposition each.
It runs at the end of a normal run, or on its own with decide — which is
ledger-anchored, not brief-anchored: open findings and no recent run is a
normal way to start, not an error.
Nothing reaches the approval screen unanalyzed. Before the first question, the pass consolidates: findings describing the same defect from different scenarios merge into one item; findings whose fixes cannot both hold surface as ONE multi-outcome question naming each branch, never as two independent yes/no gates. That stage exists because the alternative shipped: two findings proposing opposite fixes for the same defect were filed as separate issues in one sitting, and the contradiction had to be reconciled by hand across two issue threads afterwards. Filing an issue whose conflict with another was detectable from the ledger is a defect of the tool, not of your attention.
Dispositions:
- accept — optionally file the prepared GitHub issue right then (its own
explicit confirmation; nothing is ever auto-filed). A filed issue carries
exactly one label,
tanuki:<kind>— the only machine-readable trace Tanuki leaves; the prefix inside the kind label is the provenance marker. - dismiss — it never resurfaces as new (but stays deduplicated against).
- defer — stays pending;
statuskeeps it visible until you decide.
Accepted findings keep their recurrence tracking, which gives you a free regression check: after you ship a fix, run Tanuki again — the finding's absence verifies the fix landed.
Once you trust the attended loop, /tanuki-loop runs the full cycle —
drive → mine → classify → implement → test → commit — repeatedly on a
dedicated integration branch in an isolated worktree, until two consecutive
quiet cycles (nothing new found, nothing fixed, and the exploration quota
met) or an iteration cap.
The human gate is relocated, not removed: the loop prepares a batch overnight; in the morning you review the integration diff, the deferred decisions, and the audit trail, and only you merge to the base branch. No phase of the loop ever merges, writes to the base branch, or files issues unattended. The one unattended push is scoped and deliberate: on a PR-protected target (below), the overnight close pushes the loop's own integration branch — never the base — to open the Draft PR. Full contract: spec-tanuki-loop.
If your default branch refuses direct pushes (required-check protection), set
"gate": "pr" in the scenarios "loop" block: instead of a local merge, the
overnight close pushes the integration branch and opens one Draft PR
(gate-pr) — that PR is the morning-gate review material. It is delivery,
not ratification; the loop still never merges. Review and merge the PR
yourself (a merge commit, not a squash, to keep the intent-scoped commits).
/tanuki-loop my-plugin # settings stored once in the scenarios file
/tanuki-loop my-plugin --iterations 2 # cautious first run (the cap is a ceiling, not the goal)
The per-target "loop" block in the scenarios file stores the settings, so a
normal run is zero-config. Any of them can be overridden per run:
--iterations N (the cap), --wall-time <dur> and --token-budget <N> (the
breakers that bound an unattended run — always set for an overnight run),
--phase 1|2|3 (supervision level; Phase 2/3 run headless). Before a headless
(Phase 2/3) run, validate readiness read-only — it writes nothing and runs no
pipeline, checking the required ceilings, base freshness, and that test_cmd
passes on the base tip:
<plugin-root>/tools/tanuki-loop --target my-plugin doctor \
--loop-repo <plugin-root> --scenarios ~/.tanuki/scenarios/my-plugin.scenarios.json
Watch it live with the dashboard (safe to run alongside the loop — it only
reads state files). --follow 10 re-renders every 10s; --live renders only
while a run is actually active (a closed run degrades to one historical line):
<plugin-root>/tools/tanuki-loop --target my-plugin dashboard --follow 10
It's an operational-status view, not a state dump: a one-line health verdict (OK / ATTENTION / DONE), the latest drive with each anomaly classified against the ledger (a known finding vs an unmatched one that needs your eyes), what this run changed, what the scheduler decided, and — when it stops — the exact decisions the morning gate will ask of you.
The loop needs one thing from you: a test_cmd — your repo's regression
gate, run after every iteration. A failing gate stops the loop immediately.
On the first run without one configured, Tanuki derives a candidate from
your repo and asks you to confirm it.
The morning gate has two outcomes, and the second one has a cost. If you
decline the merge — or merge some runs and not others — the integration
branch survives with real work on it and nowhere to go. Then main moves,
you fix the same things by hand, and eventually the branch's answer and
main's answer contradict each other. That is not a hypothetical: two
branches once accumulated 25 unmerged commits and had resolved two findings
in exactly the opposite direction to the attended sitting — discovered only
after both had shipped.
So the loop tells you, and then helps:
/tanuki-loop my-plugin unresolved # which branches never merged, and how stale
/tanuki-loop my-plugin reconcile # classify their work, then land it
/tanuki-loop mentions unresolved branches on its own when they exist (one
line, never blocking), and ~/.tanuki/<target>/unresolved.md is a brief you
can open cold.
reconcile is report-first: it produces a landing plan and stops until
you open the gate. It classifies per change, not per commit — one commit
routinely mixes work that already landed, work that's been superseded, work
that contradicts a decision you've made, and work still worth having, and its
subject line describes at most one of them. Clean, self-contained commits are
cherry-picked; entangled ones are ported by hand; anything that contradicts a
contract is reported for you to decide, never merged quietly.
The attended run, stage by stage — each stage's full contract is in docs/tanuki-spec.md:
preflight (lint, code) mechanical violations stop here — never
│ rediscovered by dogfooding
▼
plan gate (you approve) scenarios × estimated time, from run history
▼
DRIVER tanuki-drive per scenario: fresh clones of plugin+host →
(the simulated user, headless claude run under a charter →
a smaller model) Events (normalized mechanically) →
│ post-run pollution check
▼
MINER extraction subagent events → candidate findings (smaller model)
+ frontier dedupe candidates → ledger upserts: bump recurrence
│ vs create (frontier judgment, never delegated)
▼
CONSOLIDATOR promotion by thresholds (code), then the
(frontier + code) brief: ≤10 ranked proposals, watching list,
│ lesson candidates
▼
DECISION PASS (in-session) consolidate first (merge duplicates, surface
contradictory fixes as ONE choice), then each
item: accept / dismiss / defer — a run ends in
decisions, not a report
Three words carry the whole design; everything flows one way through them:
| term | meaning |
|---|---|
| Event | a raw fact from one run ("tool X errored at turn 12"). Never judged, never edited. |
| Finding | a judged, deduplicated problem with a recurrence count across runs. |
| Proposal | a finding that crossed the promotion bar and now awaits your decision. |
Events → Findings → Proposals → (your call) labeled GitHub issues. Nothing
skips a step, including your own ingest feedback.
| path | role |
|---|---|
commands/tanuki.md |
the /tanuki command — orchestrates the whole pipeline |
commands/tanuki-loop.md |
the /tanuki-loop command — unattended mode |
tools/tanuki-preflight |
mechanical lint of the plugin under test (runs before any scenario) |
tools/tanuki-drive |
the Driver: isolation, headless runs, event capture, live progress |
tools/tanuki-ledger |
the Findings ledger: ingest, dedupe support, promotion, status |
tools/tanuki-scheduler |
picks which scenarios each run should drive, adaptively |
tools/tanuki-config |
typed configuration substrate behind /tanuki <t> configure (show / check / set, doctor hooks) |
tools/tanuki-loop |
deterministic safety substrate for the unattended loop |
tools/tests/ |
test fixtures for the tools (for t in tools/tests/test-*; do "$t"; done) |
templates/example.scenarios.json |
starting point for your scenario file |
templates/config.example.json |
every config key with its default |
Each tool prints its complete subcommand and option surface with --help
(e.g. tools/tanuki-loop --help, tools/tanuki-ledger --help), and the
commands/*.md files are the authoritative command docs. This README covers
the surfaces you reach for by hand; the loop's internal steps (iter-start,
iter-verify, record-cycle, gate-check/gate-pr, finish, …) are
driven by /tanuki-loop, not typed by you — so a subcommand you see the loop
run but never invoke yourself is expected, not undocumented.
Everything Tanuki generates lives under ~/.tanuki/, never in your
repos:
| path | what it is |
|---|---|
~/.tanuki/scenarios/<target>.scenarios.json |
your per-target scenario matrix (/tanuki init writes it) |
~/.tanuki/<target>/briefs/<run>.md |
the deliverable — the ranked proposal brief |
~/.tanuki/<target>/ledger.json |
persistent findings (don't hand-edit; use tanuki-ledger set-status) |
~/.tanuki/<target>/events/<run>/ |
transcripts, normalized events, live progress.json |
~/.tanuki/<target>/ws/<run>/ |
the disposable clones — delete freely |
~/.tanuki/<target>/loop/<run>/ |
the overnight loop's run state — state.json, audit.md, the morning queue |
You don't need any configuration to start: beyond the scenario file
/tanuki init writes, every key has a built-in default.
When you want to change something, /tanuki <target> configure is the
primary path: it shows the effective configuration with each value's source
labeled, validates the new value against its declared type, previews exactly
what will be written, and runs the field's doctor check (if it declares one)
before persisting. Out of the box it edits the built-in catalog
(drive_model, drive_concurrency); a target that declares an inputs
block in its scenarios file adds its own typed fields to the same flow.
Editing the JSON directly is the advanced path — the backing files stay the
source of truth and hand-editing remains legal. Precedence:
~/.tanuki/config.json (global) < a "defaults" block in the target's
scenarios file < CLI flags.
Key defaults: driver_model (claude-sonnet-5 — the simulated user),
model_ceiling (sonnet — the most capable model Tanuki is allowed to launch;
the ceiling is enforced, and frontier-class models are refused),
max_scenarios (6 — cap on scenarios per run), max_turns (40 per
scenario). The full table with all keys lives in
docs/tanuki-spec.md, mirrored in
templates/config.example.json.
/tanuki init generates your scenario file, but the ideas travel: each
scenario is an exploratory-testing charter — a goal plus a persona plus
the specific branch being explored ("a first-time user follows the
quickstart", "a user whose config is missing"). Two scenarios that run the
same command but pin different answers to a question the command asks
(via "decision_points") are exploring different paths — that's breadth, and
breadth beats repetition. Re-run an unchanged scenario only to tell a chronic
problem from a flaky one.
If your plugin operates on a repository's content (say, a docs generator),
set "host" in the scenarios file and Tanuki clones that repo as the
workspace. Self-contained plugins omit it and get a fresh empty workspace per
scenario.
0.x prototype. Known limitations, spelled out in the spec: isolation is git-clone-plus-pollution-check, not a container, and the driver runs headless with permission prompts skipped inside its disposable clones — so point Tanuki only at plugins you trust.
