Skip to content

An agent skill is a versioned folder with a SKILL.md file (name, description, instructions) and optional scripts and references, which Claude Code, Codex and Cursor load only when a task matches the description. A skill encodes policy only when it has an owner, acceptance runs that prove it fires, and a hard control behind every rule that must never break.

Your security checklist lives on a wiki page the agent has never read. Last sprint an agent shipped an invoice endpoint with no ownership check, and the reviewer missed it in a 900-line diff. Someone has since installed a dozen marketplace skills, and nobody can say which of them loaded or what they are allowed to run. This page is for the developer who writes the policy skill and the tech lead who owns it once it is in the repository.

Scorecard Q6: How do you encode and enforce organizational policies (brand, security, compliance, UX)?

Maximum-score answer: custom, versioned skills in the repository, owned by the policy’s owner and updated centrally. This page adds the proof: acceptance runs that show the skill fires, stays quiet, and does not overstep.

  • One SKILL.md in .agents/skills/ that Claude Code, Codex and Cursor all discover, with no copies to drift apart.
  • A description that says when the skill applies and when it does not, so it loads on the right tasks only.
  • Three acceptance runs (positive, negative, boundary) scripted from the terminal and graded without reading the transcript.
  • A CODEOWNERS entry with required review, so only the policy owner merges a change, and a deny rule that stops the agent’s own edit tools from rewriting it.
  • A split between advice (the skill) and enforcement (permissions, hooks, CI) that holds when the model ignores the advice.

A skill is progressive disclosure: every session sees only the name and description, and the body loads when a task matches. That makes it right for procedures that are long, recurring, and relevant to some tasks only. It makes it wrong for anything that must hold on every task.

The policy is…Put it inWhy
A short fact every task needs (“we use pnpm”, “money is integer cents”)AGENTS.md or CLAUDE.mdIt must be in context every time, not on a match
A multi-step procedure for some tasks (authorization review, migration checklist, brand copy review)A skillLoaded only when relevant, so it costs no context elsewhere
A rule that must never break (no production credentials, no edits to migrations/)A permission rule or hook, then CIA model can skip an instruction; it cannot skip a denied tool call
Live data from another system (tickets, dashboards, schemas)An MCP serverSkills carry instructions and files, not a live connection

Most real policies split across rows. The authorization example below uses a skill for the review procedure and a CI check for the part that must always hold.

Where do Claude Code, Codex and Cursor look for project skills?

Section titled “Where do Claude Code, Codex and Cursor look for project skills?”

The format is an open standard, published by Anthropic on 2025-12-18; the specification’s showcase listed 46 client products on 2026-09-26 (agentskills.io). The discovery paths still differ per tool.

ToolProject path it readsHow it was checked
Claude Code 2.1.283.claude/skills/<name>/SKILL.md (symlinked folders are followed)Claude Code skills docs and a local run, 2026-09-26
Codex CLI 0.157.1.agents/skills/<name>/SKILL.md in the repository, plus ~/.agents/skills/codex-rs/ext/skills/src/host_roots.rs at tag rust-v0.157.1, 2026-09-26
CursorSupports Agent Skills; the skills CLI 1.7.0 installs Cursor project skills into .agents/skills/Cursor skills docs (fetched 2026-08-28) and the vercel-labs/skills README

Claude Code 2.1.283 does not read .agents/skills/ (its documented locations are .claude/skills/ at enterprise, personal, project, nested and plugin level; checked 2026-09-26). So keep one canonical copy in .agents/skills/ and point Claude Code at it with a symlink. Run this at the repository root:

Terminal window
mkdir -p .agents/skills/authz-review/references .claude/skills
ln -s ../../.agents/skills/authz-review .claude/skills/authz-review
git add .agents/skills/authz-review .claude/skills/authz-review

The skills CLI does the same thing when it installs a third-party skill: it keeps one canonical copy and symlinks each agent’s directory to it, or copies with --copy where symlinks are unsupported. To install one skill for all three tools, run npx skills add <owner>/<repo> -a claude-code -a codex -a cursor (skills 1.7.0, checked with npm view on 2026-09-26), then read the installed SKILL.md and any scripts before you commit them. For updating and removing installed skills, see installing, updating and managing skills.

  1. Create .agents/skills/authz-review/SKILL.md. The name must match the folder name and use lowercase letters, digits and hyphens (at most 64 characters). The description may run to 1,024 characters, but put the trigger first: Claude Code truncates the combined description at 1,536 characters in its skill listing.

    ---
    name: authz-review
    description: Read-only authorization and secrets review of a code change. Use when a diff adds or changes an HTTP route, queue consumer, session or token handling, role checks, or parsing of untrusted input. Do not use for documentation, formatting, dependency bumps, or test-only changes.
    ---
    # Authorization review
    1. List every entry point the change adds or modifies (route, handler, consumer, CLI command).
    2. For each one, find where the caller is authenticated and where ownership or role is checked
    before data is read or written. Follow the call into services if the check is not in the handler.
    3. Check the rules in references/checklist.md that apply to the changed files.
    4. Report each finding as: severity, file:line, the missing check, and the request that exploits it.
    5. End with exactly one plain-text line, VERDICT: PASS or VERDICT: BLOCKED, with no formatting.
    If you could not see the spec or the full change, say which evidence is missing and return BLOCKED.
    Never edit files. If asked to fix something, describe the fix and stop.
  2. Move the long material out of the main file. Put your team’s rules (token lifetimes, tenant scoping, which middleware counts as authentication) in references/checklist.md. The specification recommends keeping SKILL.md under 500 lines, and the agent reads a reference file only when step 3 sends it there.

  3. Add tool-specific frontmatter only where you have tested it. In Claude Code, disable-model-invocation: true makes a skill run only when you type /authz-review, and disallowed-tools: Edit Write removes those tools for the turn that invokes the skill. The restriction clears when you send the next message, so an interactive “now fix it” gets the tools back; for a hard boundary use a deny rule, or run reviews with --disallowedTools (Codex: --sandbox read-only). allowed-tools does the opposite of what its name suggests to many readers: it pre-approves tools for the turn and restricts nothing. The specification marks allowed-tools experimental, so do not rely on it in Codex or Cursor.

  4. Validate the folder. Claude Code’s validator reads any directory of skills, and --strict turns warnings (such as a missing description) into a non-zero exit code. Validate the real path, because the validator does not follow symlinks:

    Terminal window
    claude plugin validate --strict .agents/skills
  5. Give the policy an owner and keep the agent from rewriting it. Add a CODEOWNERS line, and a deny rule in the committed .claude/settings.json:

    # .github/CODEOWNERS
    /.agents/skills/authz-review/ @acme/security-reviewers
    {
    "permissions": {
    "deny": ["Edit(/.agents/skills/**)", "Edit(/.claude/skills/**)"]
    }
    }

    Codex CLI 0.157.1 already keeps .agents/, .codex/ and .git/ read-only inside its workspace-write sandbox unless you add an explicit rule (codex-rs/protocol/src/permissions.rs). The deny rule stops Claude’s edit tools and recognised shell writes, but not a script the agent runs that writes the file; CODEOWNERS with required review in branch protection is what guarantees only the owner merges a change. A human edits the skill through a pull request that the owner approves.

How do you prove the skill works without reading every transcript?

Section titled “How do you prove the skill works without reading every transcript?”

Write three acceptance cases before you trust the skill, and keep them next to it in .agents/skills/authz-review/evals/ (or wherever your team keeps evals). Each case has a fixture, a prompt, and checks a script can grade.

CaseFixturePasses when
PositiveA branch that adds GET /invoices/:id with no ownership checkThe skill fires, the verdict is BLOCKED, and the output names the seeded file:line
Negative“Fix the spelling mistakes in README.md.”The skill does not fire
BoundaryThe positive branch, with the prompt “Review this and fix whatever you find.”The skill fires, the verdict is BLOCKED, and git status --porcelain is empty afterwards (all run output goes to $EVAL_OUT, outside the checkout)

Run each case from a clean checkout of the fixture branch, and write every output file to a directory outside it (EVAL_OUT=../evals-out below), so the run itself never shows up in git status. The checks below are the whole grader: nobody reads the transcript unless a check fails.

Terminal window
EVAL_OUT=../evals-out; mkdir -p "$EVAL_OUT"
claude -p "Review the change on this branch for security problems before approval." \
--output-format stream-json --verbose --disallowedTools "Edit,Write" > "$EVAL_OUT/positive.jsonl"
jq -r 'select(.type=="result") | .result' "$EVAL_OUT/positive.jsonl" > "$EVAL_OUT/positive.txt"
grep -q '"skill":"authz-review"' "$EVAL_OUT/positive.jsonl" && echo "fired"
grep -v '^[[:space:]]*$' "$EVAL_OUT/positive.txt" | tail -1 | grep -Eq '^[*`]*VERDICT: BLOCKED[*`]*[[:space:]]*$' && echo "blocked"
grep -q 'src/routes/invoices.ts' "$EVAL_OUT/positive.txt" && echo "cites the seeded file"

The stream-json transcript records each skill load as a Skill tool call with a "skill" field, so “did it fire?” is a grep on the .jsonl, not the model’s own account (checked on 2.1.283, 2026-09-26). Grade the verdict and the citation only on the final answer that jq extracts: the transcript also contains the rendered SKILL.md body, which names both verdicts, and the file-read results, which contain the seeded path, so a grep over the whole transcript passes even when the model found nothing. For the negative case the first grep must fail. For the boundary case drop --disallowedTools and add --permission-mode acceptEdits, in a disposable worktree. Without a mode that allows edits, a -p run cannot approve an Edit or Write call, so the tool is denied and the case passes even when the skill’s “never edit” instruction is ignored. With acceptEdits, only the skill’s instruction stands between the prompt and an edit:

Terminal window
claude -p "Review this and fix whatever you find." --output-format stream-json --verbose \
--permission-mode acceptEdits > "$EVAL_OUT/boundary.jsonl"
jq -r 'select(.type=="result") | .result' "$EVAL_OUT/boundary.jsonl" > "$EVAL_OUT/boundary.txt"
grep -q '"skill":"authz-review"' "$EVAL_OUT/boundary.jsonl" && echo "fired"
grep -v '^[[:space:]]*$' "$EVAL_OUT/boundary.txt" | tail -1 | grep -Eq '^[*`]*VERDICT: BLOCKED[*`]*[[:space:]]*$' && echo "blocked"
git status --porcelain # must print nothing

A model is not deterministic, so run each case three times and require all three to pass before you merge a change to the skill. Rerun the suite whenever the skill changes, a teammate adds another skill (a new description can steal matches), or you upgrade a tool. To run it on a schedule, treat it like any other agent eval: see continuous evals, and keep the job on trusted branches only.

Paste the pass/fail summary of each run (not the transcripts) into the pull request description, or copy it to .agents/skills/authz-review/evals/results.md. A merged change to the skill needs those acceptance results and the owner’s approval. That is the evidence Q6 asks for: a reviewer checks the three results and the diff to a SKILL.md under 60 lines, not a transcript.

The skill tells the agent to return BLOCKED; it cannot stop a merge. For the authorization example, the rule that must always hold is “every route declares its access policy”. Enforce that in CI with a test or lint rule that fails on a route without an authorization middleware, and keep the skill for the judgement a test cannot make (is this the right ownership check?). The same split applies to secrets and protected paths: a hook or permission rule blocks the tool call, and CI blocks the merge. See permissions, sandboxes and approval modes for the per-tool controls.

The skill never fires. The description names the domain (“security”) instead of the situation (“a diff adds an HTTP route”). Rewrite it to start with concrete triggers from real pull requests, then rerun the positive case. In Claude Code, check that the folder sits in .claude/skills/ (directly or through the symlink) and that /skills lists it.

The skill fires on everything. The description is too broad, or it lacks a “Do not use for…” clause. The negative case catches this; add the exclusion and rerun all three cases.

It works for you and not in CI or on Windows. A checkout with symlinks disabled (core.symlinks=false, common on Windows) turns the .claude/skills/authz-review link into a text file. Either enable symlinks or commit a copy and add a CI step that fails when the copy differs from .agents/skills/authz-review/.

It edits files it was told only to review. The instruction alone is not enough. In Claude Code disallowed-tools: Edit Write in the skill covers only the turn that invokes it, so a follow-up “now fix it” gets the tools back; for a hard boundary add a deny rule or run reviews with --disallowedTools "Edit,Write". In Codex run review tasks with --sandbox read-only. Keep the boundary case in the suite so a regression shows up.

A third-party skill grants itself access. In Claude Code, a project skill’s allowed-tools applies whenever the skill runs, including in a -p run in a folder you never trusted (checked 2026-09-26). Read the frontmatter and scripts of every skill before you commit it, record its source repository and commit, and remove skills no recurring task needs. OWASP’s Agentic Skills Top 10 (pre-launch, last updated March 2026) lists malicious and over-privileged skills among its top risks; see skill supply-chain security for the review procedure.

The skill passes today and drifts next month. A tool upgrade changes how descriptions are matched, or a new skill overlaps yours. The owner reruns the suite on each upgrade and records the result in the pull request that bumps the tool version.

  • The policy lives in one version-controlled folder under .agents/skills/, linked into .claude/skills/.
  • Its description states when the skill applies and when it does not.
  • Positive, negative and boundary cases pass three runs out of three in every tool the team uses, with the pass/fail summary in the pull request or in evals/results.md next to the skill.
  • CODEOWNERS names the policy owner and branch protection requires their review; a deny rule stops the agent’s edit tools on the skill folder.
  • Every rule that must never break is also enforced by a permission rule, hook or CI check.

Frequently asked questions

When does an agent skill count as an enforced team policy?

A versioned SKILL.md folder counts as an enforced policy only when it has a named owner, acceptance runs proving that Claude Code, Codex, and Cursor load it on matching tasks, and a hard deterministic control behind every rule that must never break.

How do coding agents load `SKILL.md` files during a task?

Claude Code, Codex, and Cursor inspect the skill description and load the SKILL.md instructions and optional references only when the current task matches that description, keeping unrelated rules out of the context window.