Skip to main content

Modular Claude Code Skills: Building a Dependency Graph That Doesn't Exist

· 10 min read
Pere Pages
Software Engineer
A small graph of connected nodes with one shared hub node highlighted, representing skills depending on a common skill

Claude Code skills have no requires: field, no imports, no dependency resolver — and yet you can build a real graph of skills that depend on each other. This post lays out the mental model, the three composition mechanisms that fall out of it, and a refactor of this blog's own skill folder as a case study in what happens when you skip them.

note

This reflects Claude Code as of mid-2026. The composition patterns here are conventions layered on top of the tool rather than tool features, so they should age well — but check the current skills documentation for what exists today.

Once a project accumulates more than a handful of skills, they start wanting to share things: the same conventions, the same procedures, the same helper scripts. The natural instinct is to look for a dependency mechanism — and there isn't one. Nothing in a skill's frontmatter declares "this skill needs that one", and using one skill never automatically loads another. The instinct is still right, though. It just needs a different mental model.

The mental model: a skill is context, not code

A Claude Code skill[1] is a folder holding a SKILL.md file — instructions that get loaded into the model's context when the skill is used, either because the model decides it's relevant or because it's invoked directly as /skill-name. A skill is not a function you call; it is a package of instructions that gets loaded into context at the right moment — so a "dependency" is anything that reliably brings shared knowledge or behavior into the task when it's needed.

Reframed that way, there are exactly three ways to share something between skills, depending on what kind of thing is being shared:

Each of the three deserves a closer look, because each has one non-obvious detail that makes it work.

Composition by invocation

The closest thing to a real dependency edge: one skill's SKILL.md simply instructs the model to invoke another skill at a specific point in its procedure.

## Step 5 — generate the cover

For an illustrated cover, invoke the `og-card` skill now and
follow its "derive card + hero" flow, writing the results into
`<post-dir>/images/`.

When that line executes, the other skill's full SKILL.md loads into context mid-task — current instructions, straight from the single source of truth, with nothing copied. Two details make it work:

  • The calling skill must be allowed to invoke skills. A skill's allowed-tools frontmatter pre-approves the tools it may use, and skill invocation is itself a tool, so it has to be listed:

    allowed-tools: Read, Edit, Skill, Bash(node:*)
  • Name the skill exactly. The edge is resolved by name at run time, so a rename on one side silently breaks the other. Keep the graph small enough to grep.

Invocation is the right edge for procedures — flows with several steps and some judgment in them. For material that isn't a procedure at all, there's a cheaper mechanism.

Composition by shared reference docs

Rules and conventions — formatting standards, style guides, checklists that several skills must all apply — don't need invocation. They need one canonical document that every skill points into. On this site that's a _shared/ folder next to the skills:

.claude/skills/
├── _shared/
│ └── content-conventions.md # no SKILL.md — deliberately
├── new-post/
│ └── SKILL.md
├── review-post/
│ └── SKILL.md
└── write-post/
└── SKILL.md

The trick is what's missing: _shared/ contains no SKILL.md, so the skill scanner — which registers any */SKILL.md it finds — ignores the folder entirely. It's referenced material, not an invocable skill. Skills then deep-link into it by anchor:

Apply the table conventions —
[content-conventions.md § Comparison tables](../_shared/content-conventions.md#comparison-tables-and-rating-pills).

The shared doc turns N skills × M rules into one file with N pointers — the writing skill and the reviewing skill cite the same nine anchors instead of carrying two slowly-diverging copies of the same rulebook. Each skill adds only its own verb: one produces posts that follow the rules, the other fixes posts to match them.

Composition by shared scripts

The third mechanism handles what prose instructions are worst at: exact, deterministic behavior. Cropping an image to precise pixel dimensions, validating frontmatter, generating a file — describing these in SKILL.md invites the model to improvise. A script makes the behavior a guarantee, and the skill's instructions shrink to one line:

Generate the card: `node scripts/generate-post-og.mjs <post-dir>`

The script is callable from any skill (and from git hooks and continuous integration (CI) pipelines, which no SKILL.md can offer), and allowed-tools can scope permission down to exactly that command — Bash(node scripts/generate-post-og.mjs:*) — instead of blanket shell access.

Choosing between the three

The three mechanisms map cleanly onto what's being shared:

MechanismIt sharesBest forDrift resistanceSetup cost
Skill invocationa whole proceduremulti-step flows involving judgmentStrongLow
Shared reference docrules and conventionsstandards many skills must applyGoodLow
Shared scriptexact behaviordeterministic transforms and checksStrongMedium

Strong Good Medium Weak

A shared doc's drift resistance is only "good" because consumers can still paraphrase it instead of linking; a script's setup cost is "medium" because it drags in dependencies and a runtime. Knowing the mechanisms is the easy half, though. The interesting question is what happens when a skill folder grows without them.

A case study in rot

This site's skill folder had four skills touching Open Graph (OG) social cards — the 1200×630 preview images social networks show when a post is shared (the system itself is described in an earlier post). An audit of how the card knowledge was distributed found:

  • The post-writing skill said "follow the scaffolding skill's step 5, don't reinvent it" — and then reinvented all of it: roughly 50 lines of procedure duplicated nearly verbatim, covering both cover-generation paths and the design rationale.
  • The regenerate-the-card-when-the-title-changes rule existed in three copies across three skills.
  • The one skill actually named og-card handled only a narrow slice (cropping hand-made cards) — and was referenced by nobody.

The failure has a name — pointer-plus-copy: a link that says "don't reinvent this" sitting directly above a reinvented copy is worse than either alternative, because the copy drifts and the pointer keeps vouching for it. A pointer must replace the content, not caption it. Each of the three duplications above was a missing edge: the 50-line procedure wanted an invocation, the thrice-copied rule wanted a shared-doc anchor, and the crop logic wanted to be a script behind a skill that other skills actually call.

Drawing the graph: split on context-dependence

Fixing it isn't just deduplication — it's deciding where each piece of card knowledge lives. Skills exist at two levels: project level (.claude/skills/, versioned with the repo, visible to anyone who clones it) and user level (~/.claude/skills/, following you across every project). That choice is the module boundary: what encodes this project's decisions stays project-level; what would work unchanged in any repository can be exported to a portable user-level skill.

Applied to the card system, the knife falls cleanly. Cropping an image to 1200×630, or deriving a social card plus a body hero from one source illustration, is generic — a few lines of sharp with no opinions about this blog. Rendering the typographic title card is the opposite: it bakes in the site's fonts, colors, logo, and frontmatter conventions. So the generic image operations export to a portable og-card skill at user level, and a project-level post-card skill keeps the site-specific knowledge and depends on it — as do the three post skills, giving the graph that "doesn't exist":

Every arrow is one of the three mechanisms — mostly invocations, backed by shared scripts underneath. The exported skill has to be self-contained, because at user level there's no host project to lean on:

~/.claude/skills/og-card/
├── SKILL.md # generic card operations, paths relative to this folder
├── package.json # its own dependencies (sharp)
└── scripts/
├── og-fit.mjs # crop any image to 1200×630 WebP
└── cover-derive.mjs

Two practical consequences of that self-containment:

  1. It carries its own dependencies. The SKILL.md opens with a bootstrap instruction — if node_modules is missing next to it, run npm install --prefix <skill-dir> first — so the skill works in a repository that has never heard of sharp.
  2. It resolves its own paths. The working directory is always the host project, so every script reference in the SKILL.md is written relative to the skill's own folder, never to the current directory.

Rules of thumb

The refactor distilled into a few portable rules:

  • A pointer must delegate, not caption. If a skill links to another skill's procedure, it must not also contain that procedure. One or the other.
  • Match the mechanism to the content. Procedures → invocation; rules → shared doc anchors; exact transforms → scripts. (The Agent Skills standard[2] explicitly allows skills to bundle supporting files and scripts — the structure is designed for this.)
  • Watch for name collisions across levels. A project skill and a user skill with the same name will fight over it. When exporting a skill, either retire the project-level name or rename one side.
  • Portability has a price. A repository whose skills invoke a user-level skill breaks on any machine that doesn't have it. For a solo project that trade is fine; on a team, keep the whole graph inside the repo where everyone gets it from a clone.
  • Don't over-modularize. Two duplicated lines don't earn a dependency edge. The 50-line procedure did; the mechanism exists for duplication that hurts, not for symmetry.

The dependency graph Claude Code doesn't give you is one you can build anyway — out of invocations, anchors, and scripts — and the discipline that keeps it standing is refusing to let a pointer and a copy coexist. For the wider customization picture these skills sit inside — CLAUDE.md, hooks, and settings — see the earlier overview post.

References

  1. Extend Claude with skills — Claude Code documentation
  2. Agent Skills — the open standard for agent skills