| name | learn |
| description | Use when the user says 'learn!', 'capture this', 'update the skill', 'remember this for next time', or when a session surfaces a non-obvious pitfall, a doc-vs-reality gap, or a missing step in a skill/rule that was in use. Routes session learnings back into this repo's persistent guidance — skills/<group>/<name>/SKILL.md (+ references/), claude/rules/*.md, CLAUDE.md — rather than auto-memory. Automatically identifies which skills and rules were loaded during the session, checks for existing coverage (especially fabric-gotchas), verifies the learning against official docs before encoding it, proposes the edit at the right heading as a diff for approval, then hands off to /commit. Never edits silently, never writes domain knowledge to memory. |
| model | fable |
| effort | max |
| disable-model-invocation | false |
Learn: capture session learnings into skills and rules
Turn something discovered during this session into a durable, verified
edit to the guidance that should have covered it. The goal is that the
next session never has to rediscover it.
This skill proposes; it does not commit. Edits land only after the
user approves the diff, and committing is handed to /commit.
The inverse skill is /author-skill: /learn folds a learning into
guidance that already exists, /author-skill creates guidance that has
no home yet. If the right destination for a learning turns out to be a
skill this repo does not have, that is an /author-skill job.
This skill is project scope: it lives in .claude/skills/ and fires only
in sessions inside this repo, so the paths and commands below (grep, the
lint script, /commit) run from the repo root. Learnings from other repos
arrive as notes in ~/handoff-inbox/ — see root CLAUDE.md.
Step 1 — Identify what was learned
Reflect on the session, not just the last message. Candidate learnings:
- A skill or rule said X; reality was Y (doc-vs-reality gap).
- A step was missing and cost debugging time.
- An error message whose cause was non-obvious.
- A constraint / limit / version change not documented anywhere here.
- A user correction that reveals a general principle.
For each candidate, state in one or two sentences: problem → root
cause → correct approach → generalization. Drop anything that is
one-off, already obvious from the code, or only matters to this
conversation. Confirm the list with the user before proceeding if it
contains more than one item or you're unsure which matters.
Step 2 — Identify which guidance was in use (automatic)
Do not ask the user which skill was used. Reconstruct it:
- Skills invoked this session — every
Skill tool call and every
skill whose content appears in context (the <command-name> /
loaded-skill blocks). Record the name: of each.
- Rules auto-loaded — any
claude/rules/*.md content present in
context, triggered by files in session scope (paths: globs). It
appears under its deployed path, ~/.claude/rules/.
- Global CLAUDE.md or rule sections relied on — e.g. the
uv
guidance (claude/CLAUDE.md) or the Fabric serialization rule
(claude/rules/fabric-git-serialization.md).
- Tools used — MCP servers / CLIs (
fab, pbir, fabric-cicd,
Fabric REST) point at the skill that owns them even if it wasn't
explicitly invoked. Map by the skill's description.
- If nothing was loaded but a skill should have triggered, that is
itself a learning: the fix is the skill's
description (trigger
phrases), not its body.
Output a short table: learning → owning skill/rule → section.
Step 3 — Map to the destination
| Learning is… | Destination |
|---|
| Domain procedure, API shape, syntax, gotcha for one product area | skills/<group>/<name>/SKILL.md at the heading where it belongs; detail or long examples go in skills/<group>/<name>/references/REFERENCE.md |
| This repo's own procedure — the drift pipeline, authoring, testing, landing | .claude/skills/<name>/SKILL.md — project scope, live on save, no linker step |
| Cross-product troubleshooting symptom (error text → cause) | skills/fabric/fabric-gotchas/SKILL.md and a one-line cross-reference from the owning skill |
| Language / style convention that should apply whenever a file type is open | claude/rules/coding-<lang>.md (path-scoped via paths:) |
| Environment or machine-wide constraint for every session | claude/CLAUDE.md — a copy, not live until the linker pushes it; see Step 7 |
| Skill didn't trigger when it should have | the skill's frontmatter description (≤ 1024 chars, see scripts/lint-frontmatter.py) |
| Fact about the user or their workflow preference | auto-memory (~/.claude/projects/.../memory/) — never domain knowledge |
Weave the learning into the existing structure. Do not append a
## Learnings changelog section — skills here are curated reference,
not logs. Update the relevant heading, table row, or gotcha entry so a
reader finds it where they'd look.
Step 4 — Check existing coverage
Before writing anything:
grep -rn -i "<key term>" skills/ .claude/skills/ claude/rules/ CLAUDE.md claude/CLAUDE.md
Grep one distinctive token, not a phrase. Prose here is hard-wrapped
at 76 columns, so a multi-word term is routinely split across lines and
grep cannot match it. On 2026-09-12 a search for "Microsoft 365 group"
reported absent what was sitting in the file, wrapped after "Microsoft
365" — a false "not covered" that would have landed as a duplicate.
When Step 3 left the destination genuinely ambiguous — two skills
both look like the owner — ask which of them already holds the
vocabulary rather than choosing by feel:
uv run --with pyyaml scripts/skill-overlap.py overlap --skill <candidate>
A high-scoring pair between the two candidates says they already compete
for the same requests, and that the learning belongs in whichever of them
the shared tokens came from. Only reach for this when the mapping is
actually unclear; where Step 3 gave one obvious owner, it adds nothing.
- Already covered correctly → nothing to do; say so.
- Covered but wrong or stale → the edit is a correction; quote the
current text in the proposal.
- Covered in
fabric-gotchas but missing from the owning skill (or
vice versa) → add the cross-reference only.
fabric-gotchas is the natural magnet for everything; guard against
duplicates there most carefully.
Step 5 — Verify before encoding
A thing that failed once is not yet a rule. Before proposing, confirm
at least one of:
- Official docs (
microsoft_docs_search / microsoft_docs_fetch, or
the library's README / changelog) state or corroborate it.
- A second reproduction in the session (different input, same result).
- The user explicitly confirms it's known behaviour, not a fluke.
If it can't be verified, still propose it but mark it clearly as
unverified in the text (e.g. "Observed Aug 2026 with v1.3; not yet
documented") so a future drift-audit can confirm or remove it.
Include the date and version where relevant — these learnings age.
Step 6 — Propose the edit
For each learning, show the user:
- Destination file and heading.
- The exact text to add / replace, as a diff or before/after block.
- Verification source (link, or "unverified — see note").
Keep the addition as short as a reader needs: typically 1–6 lines in
SKILL.md, with anything longer in references/. Match the surrounding
voice and formatting. If a description is edited, state the new
length.
Check description headroom before proposing a trigger phrase. Many
skills here sit within a few characters of the 1,024-char cap, so a new
phrase usually has to displace an existing one rather than extend the
line. Measure the current length first — lint-frontmatter.py only reports
the overflow after the edit is written. If the budget is tight, name
what to cut; if nothing can go, say so and leave the description alone
rather than silently dropping a trigger that already earns its place.
Wait for approval. Apply only what is approved, using Edit so the
rest of the file is untouched. Then run:
uv run --with pyyaml scripts/lint-frontmatter.py skills/<group>/<name>/SKILL.md
The same linter takes .claude/skills/<name>/SKILL.md and
claude/rules/<name>.md.
Step 7 — Hand off
Report what changed and where, then hand off to /commit (do not
commit yourself). Suggested subject shape:
docs(fabric-cicd): note parameter.yml regex is case-sensitive
fix(fabric-gotchas): correct cause of 24556 snapshot conflict
feat(rules): add KQL materialize() guidance
Anything under claude/ deploys by copy, so an edit there is not live
until the linker runs: remind the user to run
./scripts/link-claude.ps1 -SkillGroups workflow,social, with -Force
when claude/CLAUDE.md or claude/settings.json changed. Never the
bare form — without -SkillGroups it silently re-links every platform
skill (root CLAUDE.md). Root CLAUDE.md is the repo's only
project-scope instruction file — there is no AGENTS.md mirror — so
there is never a hand-sync step.
Example (illustrative — not a real fabric-cicd fact)
Session: user deployed with fabric-cicd; publish_all_items skipped a
Warehouse because the item folder name contained a space, which the
skill didn't mention. Docs confirm folder names must match item
display names exactly.
learning → owning skill → section
folder name with space skipped silently → fabric-cicd → "Per-item-type caveats"
Proposal:
skills/fabric/fabric-cicd/SKILL.md → ## Per-item-type caveats
+ - **Folder names must match the item display name exactly** — a
+ mismatch (including whitespace) is skipped with no error; check
+ `change_log_level("DEBUG")` output. (Docs: <link>, verified v1.3.)
Also add to fabric-gotchas under "Deployment" as a one-liner
pointing here.
Not a learning (skip): "the deploy took four minutes" — one-off,
not actionable.