A collection of PostHog skills for enhancing AI-assisted workflows. Add this repo as a Claude Code plugin marketplace to get access to all PostHog skills: Then install individual plugins: Or browse available plugins: Copy any skill directory to .claude/skills/ in your project: Any directory under skills/ that contains a .claude-plugin/plugin.json is automatically discovered and added to the market
Add this skill
npx mdskills install PostHog/working-with-skills@PostHog? Sign in with GitHub to claim this listing.Comprehensive decision-tree guide for efficiently using MCP skill tools with strong versioning and editing patterns
1---2name: working-with-skills3description: >-4 Best practices for agents managing PostHog skills via the MCP `llma-skill-*` tools —5 how to discover, read, create, update, and refactor skills efficiently, especially6 large skills with many bundled files. Use whenever you are about to call any7 `llma-skill-*` tool, asked to author or edit a shared skill, or troubleshoot8 why a skill write was rejected. Pairs with `skills-store` (which covers the9 raw tool surface) by adding the decision-tree, efficiency, and pitfall guidance.10---1112# Working with PostHog skills1314This skill teaches agents how to use the `llma-skill-*` MCP tools well — minimum15context, minimum round-trips, minimum mistakes. If you are not yet familiar with16the tool surface itself, read the `skills-store` skill first for the catalog.17This document is about _how to choose between the tools_ and _how to scale the18workflow_ when skills get big.1920## Operating principles21221. **Progressive disclosure is non-negotiable.** Lists return descriptions, get23 returns body + manifest, file-get returns one file. Never preload bundled24 files "just in case" — every preloaded script is wasted context for the25 actual task.262. **Pick the smallest write primitive that does the job.** A targeted `edits`27 or `file_edits` is cheaper, safer, and clearer in version history than a28 full body or full bundle replacement.293. **Reads are cheap; concurrent overwrites are not.** Always have a recent30 `version` from `llma-skill-get` (or from the response of the previous write)31 before calling any write tool, and pass it as `base_version`.324. **Authoring follows the [Agent Skills spec](https://agentskills.io/specification).**33 Keep `name` kebab-case, descriptions trigger-rich, body short, bulky34 material in bundled files.3536## Decision tree: which tool do I call?3738```text39Need to know what's available?40 └─► llma-skill-list (names + descriptions only)4142Need to use / inspect a specific skill?43 └─► llma-skill-get (body + file manifest, NO file contents)44 └─► llma-skill-file-get (one file, on demand, only as referenced)4546Authoring a brand new skill?47 └─► llma-skill-create (body + all initial files in one call)4849Editing an existing skill?50 ├─ Body change?51 │ ├─ Substantial rewrite ............. update(body=...)52 │ └─ Surgical tweak .................. update(edits=[{old, new}, ...])53 ├─ Bundled file content change?54 │ └─ update(file_edits=[{path, edits:[...]}, ...])55 ├─ Add / remove / rename a file?56 │ ├─ Add ............................. llma-skill-file-create57 │ ├─ Delete .......................... llma-skill-file-delete58 │ └─ Rename .......................... llma-skill-file-rename59 └─ Wholesale bundle reset (rare!) ....... update(files=[...]) # replaces ALL files6061Want a fork as the starting point?62 └─► llma-skill-duplicate (then update the copy)6364Done with a skill entirely?65 └─► llma-skill-archive (hides ALL versions; cannot be undone)66```6768If you find yourself reaching for `update(body=...)` plus a sprawling `files=[...]`69to change one paragraph and one script, stop — that's two narrower calls70(`update(edits=[...])` plus `update(file_edits=[...])`) or even a single71`update` carrying both `edits` and `file_edits`.7273## Discover before you fetch7475```json76posthog:llma-skill-list77{ "search": "fractal" }78```7980`llma-skill-list` is the right tool to "find a skill" — it returns names and81descriptions only. Reading the descriptions is the entire point: pick the right82skill before pulling any body. If `search` doesn't narrow it enough, list83without it and scan, but do not start fetching candidate bodies blindly.8485`llma-skill-get` should be called **once per skill per task**, not per question.86Cache the body in your working memory; fetch again only if you suspect the87skill changed under you (e.g. a `409` on write — see "Concurrency" below).8889## Reading a large skill efficiently9091Big skills (long body, many bundled files) are the case where lazy loading92matters most.93941. `llma-skill-get(skill_name=...)` — read `body` + `files[]` manifest.952. Scan the body's table of contents / headings. The body should already tell96 you which file goes with which task — that's why bodies stay short and97 reference files by path.983. For each file the body explicitly points at for _the current task_, call99 `llma-skill-file-get(file_path=...)`. Skip everything else.1004. If the body references "see scripts/X for the rare case Y" and you are not101 in case Y, do not fetch `scripts/X`.102103When in doubt, fewer files. You can always fetch one more on the next turn.104105## Authoring a new skill106107Use a single `llma-skill-create` call with body **and** initial files — the108skill lands at `version: 1` complete. Do not create the skill empty and then109make N follow-up `llma-skill-file-create` calls; that's N extra versions and N110extra round-trips for no benefit.111112```json113posthog:llma-skill-create114{115 "name": "my-skill",116 "description": "What it does AND when to use it. Include trigger keywords.",117 "body": "# my-skill\n\n## When to use\n...\n## Workflow\n...",118 "license": "MIT",119 "compatibility": "Requires Python 3.10+",120 "allowed_tools": ["Bash", "Write"],121 "metadata": { "author": "me", "category": "..." },122 "files": [123 { "path": "scripts/foo.py", "content": "...", "content_type": "text/x-python" },124 { "path": "references/primer.md", "content": "...", "content_type": "text/markdown" }125 ]126}127```128129### Authoring rules of thumb130131- **`description` is the discovery surface.** It is the only thing132 `llma-skill-list` returns. Make it trigger-rich (what the user might say) and133 scope-honest (what the skill does and does not do).134- **`name`** — kebab-case, max 64 chars, no leading/trailing/consecutive135 hyphens. The spec validator rejects anything else.136- **Body ≤ ~500 lines.** Long preambles, exhaustive SQL, full example payloads,137 and runnable code belong in `references/`, `assets/`, or `scripts/`. The body138 should _route_ to those files, not inline them.139- **File layout convention** — `scripts/` for executable code, `references/`140 for prose docs and examples, `assets/` for templates / data. Agents can rely141 on this for orientation when they only have the manifest.142- **`allowed_tools`** lists the MCP / built-in tools the skill expects to be143 callable. Be honest — under-declaring causes silent failures, over-declaring144 is a security smell.145146## Updating an existing skill147148The single most common mistake is using `update(body=..., files=[...])` for a149small change. That works, but it round-trips the entire skill, makes the diff150unreadable in version history, and risks dropping files if `files` was151incomplete. Use the smallest primitive instead.152153### Always read first, capture `version`154155```json156posthog:llma-skill-get157{ "skill_name": "my-skill" }158```159160Note the returned `version` — pass it as `base_version` on every write. After a161successful write, the response contains the new `version`; chain further writes162with that.163164### Body: full replacement vs incremental edits165166Full replacement when you are restructuring the body:167168```json169posthog:llma-skill-update170{ "skill_name": "my-skill", "body": "# my-skill\n\nNew body...", "base_version": 7 }171```172173Incremental edits when you are tweaking a few lines (preferred for small174changes — easier to review, lower error surface):175176```json177posthog:llma-skill-update178{179 "skill_name": "my-skill",180 "edits": [181 { "old": "Use Pillow for rendering.", "new": "Use Pillow ≥10.0 for rendering." },182 { "old": "## Old section title", "new": "## New section title" }183 ],184 "base_version": 7185}186```187188Each `edits[].old` must match exactly once in the current body, and `body` and189`edits` are mutually exclusive in one call.190191### Bundled file content edits192193`file_edits` patches one or more existing files in place — non-targeted files194carry forward unchanged. This is the right primitive when you are tweaking195script logic or fixing a typo in a reference doc:196197```json198posthog:llma-skill-update199{200 "skill_name": "my-skill",201 "file_edits": [202 {203 "path": "scripts/foo.py",204 "edits": [{ "old": "ITERATIONS = 100", "new": "ITERATIONS = 250" }]205 },206 {207 "path": "references/primer.md",208 "edits": [{ "old": "## Outdated header", "new": "## Updated header" }]209 }210 ],211 "base_version": 7212}213```214215`file_edits` cannot **add**, **remove**, or **rename** files — only patch216existing ones. For structural changes, use the per-file tools.217218### Combining edits in a single call219220You can combine `edits` (body) and `file_edits` (existing files) in one221`llma-skill-update` call to publish a single coherent version when a change222spans both:223224```json225posthog:llma-skill-update226{227 "skill_name": "my-skill",228 "edits": [{ "old": "## Configuration", "new": "## Setup" }],229 "file_edits": [230 { "path": "scripts/run.py", "edits": [{ "old": "DEBUG = False", "new": "DEBUG = True" }] }231 ],232 "base_version": 7233}234```235236### File-path parameter naming (read this before guessing)237238The same concept — a bundled file's path — is named differently depending on239**where it travels in the request**, and this trips up agents working from240memory. There is one rule:241242- **`file_path`** — when the path is part of the **URL** (`llma-skill-file-get`,243 `llma-skill-file-delete`). These read/delete one file addressed by its path.244- **`path`** — when the path is a **body field**: `llma-skill-file-create`, the245 `files=[{path, content, content_type}]` array, and `file_edits=[{path, edits}]`.246- **`old_path` / `new_path`** — body fields on `llma-skill-file-rename`.247248Mnemonic: `path` is the field name on a file _object_ (it sits next to249`content`), so everything that carries a file object uses `path`; the two250tools that address a file by URL use `file_path`. When unsure, check the251tool's input schema rather than guessing — passing `path` to file-get yields a252`/files/undefined/` 404.253254### Adding, removing, renaming files255256Each is its own call, each publishes a new version:257258```json259posthog:llma-skill-file-create260{ "skill_name": "my-skill", "path": "scripts/julia.py", "content": "...", "base_version": 7 }261```262263```json264posthog:llma-skill-file-delete265{ "skill_name": "my-skill", "file_path": "scripts/old.py", "base_version": 8 }266```267268```json269posthog:llma-skill-file-rename270{ "skill_name": "my-skill", "old_path": "scripts/julia.py", "new_path": "scripts/julia_set.py", "base_version": 9 }271```272273`llma-skill-file-rename` is a true move — it carries the existing content274forward without resending it. Always prefer it over delete + create when the275content is unchanged.276277### When to use `update(files=[...])` (rare)278279Passing `files` to `llma-skill-update` **replaces the entire bundle** —280anything not in the array is dropped. This is the right tool only when you are281intentionally wiping and reseeding the bundle (e.g. importing a fresh local282SKILL.md tree). For almost every other case, prefer `file_edits` plus per-file283CRUD.284285## Working with large multi-file skills286287Skills with many files (10+) require extra discipline:288289- **Treat the manifest as the index.** `llma-skill-get`'s `files[]` is your map.290 Match each task step to one file and fetch only that one.291- **Group structural changes into a sequence, not a fork.** If you are renaming292 three files, do them sequentially: `rename → rename → rename`, each chained293 via the previous response's `version`. That gives you three small reviewable294 versions instead of one giant `update(files=[...])` blob.295- **Keep edits localised.** A single `llma-skill-update` with `file_edits`296 targeting five files is fine. A single `update(files=[...])` carrying ten297 full file bodies is almost always a sign you should have used `file_edits`.298- **Refactor the body itself first.** If the body has grown past ~500 lines,299 the right next step is usually to split content into new bundled files300 before adding more material.301302## Concurrency: `base_version`303304Every write tool accepts `base_version`. Always pass it.305306- The server compares `base_version` to the current latest version. If they307 match, the write succeeds and the new version is `base_version + 1`.308- If they differ, the write is rejected (someone else updated the skill).309 Re-run `llma-skill-get`, reconcile your changes against the new body, and310 retry with the fresh `version`.311- After a successful write, the response includes the new `version`. Chain312 further edits with that — do not re-`get` between back-to-back writes you313 control.314315Skipping `base_version` does _not_ speed things up — it just turns a clean316"someone else won the race" error into a silent overwrite of their work.317318## Common pitfalls319320- **Calling `llma-skill-list` with no search and then fetching every body** —321 defeats progressive disclosure. Read the descriptions first.322- **Pre-fetching every bundled file after `llma-skill-get`** — same mistake on323 the inner level. Fetch on demand from the body's directives.324- **Using `update(body=..., files=[...])` for a one-line fix** — round-trips325 the entire skill, makes diffs unreadable, and risks dropping files. Use326 `edits` / `file_edits`.327- **Using `update(files=[...])` when you meant to add one file** — drops every328 file you didn't include. Use `llma-skill-file-create` instead.329- **Delete + create instead of rename** — loses content history and costs an330 extra version bump.331- **Stale `base_version` after chained writes** — read the `version` from the332 previous write's response, not from your initial `get`.333- **Leaving `base_version` off** — accepts a silent overwrite. Always include334 it once you've done a `get`.335- **Empty / vague `description`** — the skill becomes effectively undiscoverable336 via `llma-skill-list` search. Treat the description as the trigger contract.337- **Long body + no bundled files** — when a body crosses ~500 lines, refactor338 into `references/` and `scripts/` rather than letting it grow.339- **Mixing `body` and `edits` in one update call** — they're mutually exclusive.340 Pick one.341- **Guessing `path` vs `file_path`** — file-get and file-delete take `file_path`342 (it's in the URL); create, rename (`old_path`/`new_path`), `files`, and343 `file_edits` take `path` (it's a body field). See "File-path parameter344 naming" above.345346## Archiving a skill347348`llma-skill-archive` hides **every** active version of a skill by name. It is349not version-scoped and **cannot be undone** — the skill drops out of350`llma-skill-list` and `llma-skill-get` for the whole team.351352```json353posthog:llma-skill-archive354{ "skill_name": "my-skill" }355```356357Before archiving, `llma-skill-get` the skill if you need to inspect or copy it358first. Archiving is the right tool for retiring a skill entirely; to remove a359single bundled file use `llma-skill-file-delete`, and to roll back content360publish a new version rather than archiving.361362## Porting a local SKILL.md tree into PostHog363364When migrating a local skill folder (e.g. `my-skill/SKILL.md` plus365`scripts/`, `references/`, `assets/`):3663671. Read the local `SKILL.md`. Its frontmatter maps to `name`, `description`,368 `license`, `compatibility`, `allowed_tools`, `metadata`. The body after the369 frontmatter becomes `body`.3702. Walk the bundled subdirs and gather every file as371 `{ path, content, content_type }`.3723. Call `posthog:llma-skill-create` once with everything — the skill lands at373 `version: 1` complete. Do not split this into a create + N file-create374 calls.375376After the create, the skill is live for everyone via `llma-skill-get`.377378## When a skill is the wrong answer379380Not every persistent prompt belongs in the skills store:381382- One-off task instructions belong in the conversation, not in a skill.383- Personal scratchpads belong in agent memory or local files.384- Code is not a skill — if it's something a service runs, it belongs in the385 repo.386387A good skill is reusable, discoverable by description, and worth the cost of388keeping it correct over time.389
Full transparency — inspect the skill content before installing.