Quality rules
Every finding the suite can emit, all 106 of them, rendered from scripts/rules.py.
sqs.py explain <CODE> prints the same reasoning at the terminal, and sqs.py rules --module security lists one module.
Each rule carries two gradings that are about the check, not about the skill:
- confidence - how reliably the check finds the thing it names. A filesystem fact or a parse is
high; a regex over prose ismedium; a similarity heuristic islow. - false positives - when the thing is there, how often it is nonetheless intended.
README.mdinside a skill folder is a finding and also exactly what a repository-shaped skill does.
--min-confidence high keeps the facts and drops the heuristics, which is the gate you can leave switched on in CI.
capabilities (CBxxx)
| Code | Severity | Confidence | False positives | Autofix | What it is |
|---|---|---|---|---|---|
CB001 |
info | medium | medium | no | Bundled script can reach the network |
CB002 |
info | high | low | no | Bundled script can spawn a process |
CB003 |
info | high | low | no | Bundled script can read the environment |
CB004 |
info | high | low | no | Commands run when the skill loads |
CB005 |
warning | high | low | no | Bundled script’s capabilities cannot be read |
CB006 |
warning | high | low | no | Bundled script needs a package the skill never names |
CB001 - Bundled script can reach the network
Why it matters. An import or a command a bundled script carries - requests, socket, curl - gives it the ability to reach the network, independent of whether the specific call looks dangerous. security flags a call that is dangerous on its own; this names the capability so an installer can decide before reading every line.
Fix. Not a defect - confirm the destination matches what the skill claims to do.
CB002 - Bundled script can spawn a process
Why it matters. An import of subprocess/multiprocessing, or a call to os.system/os.popen/os.exec*, gives the script the ability to run another program with the permissions the agent has.
Fix. Not a defect - confirm the process it spawns matches what the skill claims to do.
CB003 - Bundled script can read the environment
Why it matters. os.environ/os.getenv gives a script access to whatever the process’s environment carries, which commonly includes API keys and tokens set for other tools.
Fix. Not a defect - confirm the script only reads the variables it names needing.
CB004 - Commands run when the skill loads
Why it matters. !commandin the body, and every line of a block opened with!, runs on the machine before the model is sent the skill - the output replaces the placeholder. It never prompts: a permission rule or the skill's own allowed-tools` lets it through, or the invocation aborts. A skill that pre-approves its own injected commands runs them silently on every load, before anything in it has been read. A plain code block does not stop it: watched, an injection inside one ran like any other.
Fix. Read each command as you would a hook. If the skill pre-approves them in allowed-tools, that approval is the author’s, not yours.
CB005 - Bundled script’s capabilities cannot be read
Why it matters. The script imports a module by a name it computes, reaches into os, subprocess or builtins by a computed attribute name, or passes code built at run time to exec/eval. Whatever that line does is decided by data, not by the file, so the other capability rules stay silent about it - and a silent manifest reads as “can do nothing”. The same indirection written with constants is followed and reported as the capability it spells.
Fix. Write the import or the call out plainly. If the name really has to come from data, check it against a fixed list first, and say in the skill what the list is.
CB006 - Bundled script needs a package the skill never names
Why it matters. A script imports a package outside the standard library, outside try: ... except ImportError, and neither the skill’s text, a requirements file nor the script’s inline PEP 723 block names it. The agent finds out halfway through the task, as a traceback, on every machine that does not happen to have it - and the author’s machine always does.
Fix. Name the package where the agent reads before running the script - a line in SKILL.md (“needs pymupdf: pip install pymupdf”), a requirements file, or a PEP 723 block the script runs under with uv run. If the script can do without it, import it inside try/except ImportError.
cases (CSxxx)
| Code | Severity | Confidence | False positives | Autofix | What it is |
|---|---|---|---|---|---|
CS001 |
warning | medium | medium | no | Promise with no instruction behind it |
CS002 |
info | low | medium | no | Expectation the description never claimed |
CS003 |
info | low | high | no | Capability the skill never announces |
CS004 |
info | medium | medium | no | Numbered steps no case checks |
CS001 - Promise with no instruction behind it
Why it matters. The description commits to leaving something behind - a file, a note, a report - and no step in the body writes, saves or files anything. This skill passes --trigger, passes a hand-written --runtime set, and still does not deliver what it advertised, which is the failure nothing else here catches because nothing about it looks broken.
Fix. Either the body is missing the step that produces it, or the description is promising work the skill does not do. QL004 reports the same gap as a statistic; this names the clause.
CS002 - Expectation the description never claimed
Why it matters. A sentence in evals/expectations.md shares no wording with the description. The top row of the disagreement table: not a broken skill, the wrong skill for what you wrote beside it.
Fix. Not a defect. Either you are adopting the wrong skill, or the expectation belongs beside a different one - the finding names the closer skill when the tree holds one.
CS003 - Capability the skill never announces
Why it matters. A bundled script reaches the network (CB001) or spawns a process (CB002) and no wording anywhere in the skill says so. The bottom row of the disagreement table: it does Z and never mentioned it. capabilities says what a skill CAN do to the machine; this says you were not told. Reading the environment (CB003) is out of scope on purpose - the words an author would announce it with are ordinary prose in the same breath, so the test cannot tell an announcement from the subject matter.
Fix. Not a defect - say so in the body, in the sentence that sends the agent to the script.
CS004 - Numbered steps no case checks
Why it matters. The body prescribes a procedure - three or more numbered steps - and evals/evals.json exists, but no case in it carries process. Every case grades what the run left behind; none grades how it got there, so a run that skips a step or does them out of order passes as long as the answer comes out right. SkillForgeBench (arXiv 2610.14918) separates the two and finds strict process scores far below loose ones: following most steps and following every step are different measurements.
Fix. Add a process item to the case that exercises the procedure: order for the steps that must come in sequence, after/then for a check that must follow the last change. sqs.py cases <skill> --generate --format json drafts one with a TODO matcher per step. A numbered list of options rather than steps reads the same to this check - suppress it there.
compat (CPxxx)
Whether the skill will work on somebody else’s harness. UNKNOWN is never NOT_SUPPORTED: where a project’s documentation is silent, the answer is unknown rather than an invented incompatibility.
| Code | Severity | Confidence | False positives | Autofix | What it is |
|---|---|---|---|---|---|
CP001 |
warning | high | low | no | Field value the runtime cannot read |
CP002 |
warning | high | low | no | Field rejected by the strict reference validator |
CP006 |
warning | medium | medium | no | Feature that does not travel |
CP007 |
info | high | low | no | Undocumented on a target harness |
CP008 |
error | high | low | no | Target harness refuses the skill |
CP009 |
warning | high | low | no | No adapter for the named harness |
CP001 - Field value the runtime cannot read
Why it matters. The field is read; the value is not one of the ones it accepts, so the runtime falls back to its default and says nothing.
Fix. Use one of the accepted values.
CP002 - Field rejected by the strict reference validator
Why it matters. skills-ref refuses unknown top-level fields. Real clients warn and load anyway, so the breakage only shows on publication.
Fix. Nest it under metadata:, which is the spec’s escape hatch.
CP006 - Feature that does not travel
Why it matters. The feature is real on one harness and inert or absent on another target. Nothing fails loudly: the instruction simply has no effect there.
Fix. Keep it if that harness is the only target; otherwise state the behaviour in the instructions, where every harness reads it.
CP007 - Undocumented on a target harness
Why it matters. The harness’s official documentation does not say what it does with this. That is a gap in the documentation, not a defect in the skill, and it is reported as unknown rather than as an incompatibility.
Fix. Try it once on that harness and record what happened, or avoid relying on it.
CP008 - Target harness refuses the skill
Why it matters. A rule the harness publishes is broken - a required field missing, a documented ceiling exceeded, a name that has to match its folder and does not.
Fix. Fix the field, or drop the harness from the targets.
CP009 - No adapter for the named harness
Why it matters. The target is not in the registry, so nothing about it was checked. Silence here would read as a pass.
Fix. Use a name from sqs.py harnesses, or add an adapter under scripts/harnesses/.
evals (EVxxx)
Whether anything would notice the skill breaking: the eval files, the routing invariants, and the agent runs behind sqs.py eval.
| Code | Severity | Confidence | False positives | Autofix | What it is |
|---|---|---|---|---|---|
EV001 |
error | high | low | no | Routing invariant broken |
EV002 |
info | high | low | no | Skill has no routing cases |
EV003 |
error | high | low | no | Live routing run below threshold |
EV004 |
warning | high | low | no | Malformed evals |
EV005 |
info | high | low | no | Thin trigger set |
EV006 |
info | high | low | no | Routing runner in the tree was not executed |
EV007 |
warning | low | high | no | Semantic overlap between two skills |
EV008 |
warning | high | low | no | Eval case cannot pass or fail |
EV009 |
error | high | low | no | Eval case cannot run as written |
EV010 |
info | high | medium | no | Trigger cases repeat the description |
EV011 |
warning | high | low | no | Eval case uses files for an output |
EV001 - Routing invariant broken
Why it matters. Two skills claim the same wording and only one can win, or a skill claims wording its own instructions do not serve.
Fix. Run run_evals.py for the detail; the fix is in the descriptions, not here.
EV002 - Skill has no routing cases
Why it matters. Nothing verifies that this skill fires on the wording a human would actually use, so every description edit moves it blind.
Fix. Add cases to evals/cases/.
EV003 - Live routing run below threshold
Why it matters. The judge sent wording to the wrong skill.
Fix. Read the failing cases: usually one description is claiming a neighbour’s branch.
EV004 - Malformed evals
Why it matters. An evals/ directory that does not parse is worse than none: it looks like the skill is tested.
Fix. evals/evals.json is {skill_name, evals: [{id, prompt, expected_output, assertions}]}; evals/eval_queries.json is [{query, should_trigger}].
EV005 - Thin trigger set
Why it matters. A trigger set with few cases on one side measures almost nothing. The negatives matter most, and the useful ones are near-misses: queries sharing keywords with the skill that need something else.
Fix. Aim for about twenty queries, eight to ten on each side.
EV006 - Routing runner in the tree was not executed
Why it matters. The routing report comes from a script that lives in the tree being checked. Running it would mean executing code out of the directory the suite was handed to read, which is the thing reading it was meant to avoid.
Fix. If the tree is yours, pass --trust-target. If it is not, a missing routing report is the correct outcome.
EV007 - Semantic overlap between two skills
Why it matters. Two skills’ trigger branches cover the same wording, so only one of them can win a request that names it - this is EV001’s claim made without executing the tree’s own run_evals.py, so it works on any skill tree, not only one that ships its own routing runner.
Fix. Read the two descriptions named in the finding; usually one needs to name the other and defer, the way QL008 and QL013 already ask for.
EV008 - Eval case cannot pass or fail
Why it matters. A case in evals/evals.json carries no assertions, outputs or judge, so nothing decides whether a run of it passed. eval --runtime would run it on both arms, spend the money, and report it as ungraded. skill-creator’s expectations do not count: a model grades them there, and nothing reads them here.
Fix. Add one checkable assertion - a literal the answer must contain, a re: pattern, an output the run has to create, with contains for what has to be in it, or a judge program whose exit code decides.
EV009 - Eval case cannot run as written
Why it matters. A case is still a draft (a field opens with TODO), names a fixture that is not in the skill, carries a re: assertion that does not compile, or has an output that is malformed, outside the run’s directory, or a binary format checked with contains, or a judge that is not an argv list or names a script the skill does not have. The first measures nothing, the second hands the agent a task about a file it never receives, and the rest crash the grader or fail every run on both arms. eval --runtime refuses the whole set until it is fixed.
Fix. Fill the draft, add the fixture under the skill or drop it, fix the pattern; check a binary output by existence, or have the task also write a text summary and check that.
EV010 - Trigger cases repeat the description
Why it matters. A should-trigger case that contains, word for word, a wording the description quotes as a trigger can pass by string match alone. It shows the listed words are there; it says nothing about the phrasings a person uses that the author did not think to list, which is what a trigger set is for. Measured on a real routing set of 105 positives: 37 were this.
Fix. Keep one such case per branch as a sanity check if you like, and write the rest the way the requests actually arrive - with context, in other words, without the listed phrase.
EV011 - Eval case uses files for an output
Why it matters. In skill-creator’s evals.json, which this set is read as, files are input paths inside the skill. This suite used to read them as outputs, so an entry that is not a file in the skill is still graded that way - the case runs - but the same file means something else to every other tool that reads it, and a missing input looks exactly like an old-style output.
Fix. Move outputs to outputs. If the entry was an input, put the file under the skill at that path.
publish (PBxxx)
What has to be true before the skill leaves the machine it was written on. Half of these are correct-and-intended for a skill that stays home.
| Code | Severity | Confidence | False positives | Autofix | What it is |
|---|---|---|---|---|---|
PB001 |
warning | high | low | no | No license |
PB002 |
info | high | low | no | No README |
PB003 |
error | high | low | no | Personal path in a skill about to be published |
PB004 |
warning | high | medium | no | Text not in the declared publication language |
PB005 |
warning | high | low | no | Version drift |
PB006 |
info | medium | medium | no | Private material in a skill about to be published |
PB007 |
info | high | low | no | Package wires hooks beside this skill |
PB008 |
warning | medium | medium | no | Skill points at a package sibling |
PB009 |
info | high | low | no | Package installed from an unreviewed checkout |
PB010 |
warning | high | medium | no | Instructions changed, the version did not |
PB011 |
warning | medium | medium | no | Call surface changed, the version moved by a patch |
PB012 |
warning | medium | medium | no | Update reaches further than the version before it |
PB013 |
warning | high | low | no | Version went backwards |
PB014 |
warning | medium | medium | no | README installs from somewhere this does not ship |
PB015 |
warning | high | low | no | Repository material ships inside the skill |
PB001 - No license
Why it matters. Without one, nobody may legally reuse the skill.
Fix. Add a license: field, or a LICENSE file beside the skill.
PB002 - No README
Why it matters. The SKILL.md talks to the agent. A human deciding whether to install needs a page that talks to them.
Fix. Add README.md next to the skill folder.
PB003 - Personal path in a skill about to be published
Why it matters. It leaks your directory layout and breaks on every other machine.
Fix. Replace with ~, or with a path the reader supplies.
PB004 - Text not in the declared publication language
Why it matters. A published skill documented in another language than its repository is read by nobody it was published for.
Fix. Translate, or drop --lang.
PB005 - Version drift
Why it matters. The version in the plugin or marketplace manifest disagrees with the skill’s own.
Fix. Make them agree; the manifest is usually the one that went stale.
PB006 - Private material in a skill about to be published
Why it matters. A path into a personal vault, a private repository name, an email address.
Fix. Strip it, or keep the skill unpublished.
PB007 - Package wires hooks beside this skill
Why it matters. hooks/hooks.json runs a command on its event whether or not the model ever routes to this skill. A clean verdict on SKILL.md next to an unread hooks/ directory is the most convincing wrong answer the suite can give.
Fix. Not a defect - read hooks/hooks.json before trusting the package, the way you would read a script this skill calls.
PB008 - Skill points at a package sibling
Why it matters. A command, agent or script one directory above skills/<name>/, or a path through ${CLAUDE_PLUGIN_ROOT}. The structure rules only resolve pointers inside the skill, so this one is invisible to them and breaks in silence the moment the skill is copied out of the plugin.
Fix. Declare the dependency in prose, or bring what it needs inside the skill.
PB009 - Package installed from an unreviewed checkout
Why it matters. The marketplace entry’s source names a remote repository, archive or command - the files under review may be a checkout nobody has looked at.
Fix. Not a defect - a fact about where the package came from.
PB010 - Instructions changed, the version did not
Why it matters. A skill that was improved and still carries its old version is a skill nobody can tell apart from the one they installed. PB005 catches two files contradicting each other; this catches a number that contradicts nothing and describes nothing, which is why it goes unnoticed for months.
Fix. Bump the patch, 0.0.1 at a time. Nothing rewrites it for you - the version is your claim about your own work.
PB011 - Call surface changed, the version moved by a patch
Why it matters. name, the invocation mode and allowed-tools decide how a skill is called. Moving one of them and bumping only the patch tells every reader the upgrade is safe to take without reading it.
Fix. Bump the minor or the major, whichever the break deserves.
PB012 - Update reaches further than the version before it
Why it matters. A skill keeps the trust it earned when somebody read it and installed it, while its content moves underneath. This update pre-approves a tool or a scope the earlier copy did not, or a bundled script gained network access or started spawning processes. Either one is a decision the person who installed the earlier copy never made.
Fix. Read the new reach before taking the update. If it is yours and intended, say so in the changelog and bump the minor - the reader deciding whether to update is the one who needs to know.
PB013 - Version went backwards
Why it matters. The declared version is lower than the one at --since. Replacing a patched release with an older one that still looks correctly published is how a fixed hole comes back without anybody editing anything.
Fix. If the rollback is deliberate, ship it as a new, higher version that carries the old content. A number that only ever rises is what lets a reader trust it.
PB014 - README installs from somewhere this does not ship
Why it matters. An install command in the README names this project under an owner its own git remotes and marketplace entry do not know, or installs this plugin from a marketplace other than the one listing it. A project that moved while its instructions did not sends every new user to an account that is no longer the author’s, and whoever takes that name next receives the installs.
Fix. Point the command at where the project lives now. If this is a fork telling people to install upstream, add upstream as a git remote and the finding goes away on its own.
PB015 - Repository material ships inside the skill
Why it matters. SKILL.md sits at the root of its git repository, beside tests, docs, examples or CI. An installer copies the skill’s folder, which here is the whole repository: the test corpus and the documentation land in every user’s skills directory, and every marketplace scanner reads them as the skill. A fixture of an attack that tests a scanner reads, to the next scanner, as an attack.
Fix. Move the skill into skills/<name>/ and keep the repository’s own material beside it. .sqsignore does not help: it hides a directory from this suite, not from the installer or anybody else’s scanner.
quality (QLxxx)
Whether the instructions read like instructions. The module with the most room to lie, so every rule here names a file and a line.
| Code | Severity | Confidence | False positives | Autofix | What it is |
|---|---|---|---|---|---|
QL001 |
warning | high | low | no | Description too short to carry triggers |
QL002 |
warning | medium | medium | no | Description states what, never when |
QL003 |
warning | low | high | no | One branch written twice |
QL004 |
info | low | high | no | Description and body barely overlap |
QL005 |
warning | medium | high | no | Steering by prohibition |
QL006 |
info | medium | medium | no | Unverifiable instruction |
QL007 |
warning | high | low | no | TODO or placeholder left in the text |
QL008 |
info | medium | medium | no | Disclaimer naming a neighbouring skill’s topic |
QL009 |
info | high | medium | no | Description repeats the skill’s own name |
QL010 |
warning | medium | low | no | Description talks about itself |
QL011 |
error | medium | medium | no | Bundled script waits for input |
QL012 |
info | high | low | no | Unpinned one-off command |
QL013 |
info | medium | medium | no | Description rules out a topic |
QL014 |
warning | medium | medium | no | Fuzzy boundary between fire and do-not-fire |
QL015 |
warning | medium | low | no | User-invoked skill’s description is written for the router |
QL016 |
info | medium | medium | no | Procedure written as one block of prose |
QL017 |
info | medium | medium | no | No example anywhere in the skill |
QL018 |
info | low | medium | no | Output produced, never checked |
QL019 |
info | medium | low | no | Output format required, no template shown |
QL001 - Description too short to carry triggers
Why it matters. Trigger conditions are the only thing the agent uses to decide whether to open the skill. Two words hold none.
Fix. State what the skill is and list the distinct branches that should reach it.
QL002 - Description states what, never when
Why it matters. A pointer does two jobs: name the material and list the branches that trigger reaching it. This one does the first only, so firing is left to chance.
Fix. Add the trigger conditions: Use when ..., Trigger on ....
QL003 - One branch written twice
Why it matters. Two trigger phrases that name the same branch cost context on every turn and sharpen nothing. The rule prints the pair, so you can judge it.
Fix. Collapse the pair into one trigger. Deliberately high-precision and therefore low-recall: it catches word-level repetition only, and the judgement-level pass over a description lives in references/writing-rubric.md.
QL004 - Description and body barely overlap
Why it matters. The skill fires on wording that its own instructions never mention, which usually means the description drifted from what the skill grew into.
Fix. Re-read the body and rewrite the description from it.
QL005 - Steering by prohibition
Why it matters. A ban drags the forbidden behaviour into context and makes it more available, not less. The negation is a weak modifier that the strongly-activated concept overruns, so the ban half-reads as an instruction to do the thing.
Fix. State the target behaviour positively, so the banned one is never spoken. Keep a prohibition only as a hard guardrail you cannot phrase positively - and pair it with the positive target.
QL006 - Unverifiable instruction
Why it matters. be thorough, write clearly, as needed: the agent cannot tell done from not-done, and neither can you.
Fix. Replace with a checkable bound, or with a bad -> good pair the reader can compare.
QL007 - TODO or placeholder left in the text
Why it matters. The agent reads it as instruction, not as a note to self.
Fix. Finish the sentence or cut it.
QL008 - Disclaimer naming a neighbouring skill’s topic
Why it matters. A description is a shop window, not a fence. Naming a neighbour’s topic makes this skill a candidate for it; negation does not reliably reverse that.
Fix. Move the boundary into the body, which is read after activation and does not affect the choice. Keep it in the description only when the neighbour can fire on its own.
QL009 - Description repeats the skill’s own name
Why it matters. Identity the body already carries, paid for on every turn.
Fix. Cut the restatement and spend the room on a trigger.
QL010 - Description talks about itself
Why it matters. The description is an instruction to the agent about when to act, not a paragraph about the skill. Use when the user ... outperforms This skill does ..., and first or second person (I can help you ...) is injected into a system prompt where the point of view does not fit.
Fix. Rewrite in the imperative, third person: what it does, then the situations that should reach it.
QL011 - Bundled script waits for input
Why it matters. Agents run in non-interactive shells. A script that blocks on a prompt does not fail - it hangs until something kills it, and the skill looks broken for reasons nothing explains.
Fix. Take every input from flags, environment variables or stdin, and fail with a message naming the missing one.
QL012 - Unpinned one-off command
Why it matters. npx eslint resolves to whatever is newest today. The skill’s behaviour then changes without the skill changing, which is the hardest kind of drift to trace.
Fix. Pin the version: npx eslint@9.0.0.
QL013 - Description rules out a topic
Why it matters. A description is matched on topic, and a negation does not reverse a match. Spelling out the work the skill will not take puts that work’s vocabulary in the one place the router reads, so the clause meant as a fence reads as one more reason to fire. QL008 is this same trap when a neighbouring skill is named; this is the version with only a topic in it, which is the common one.
Fix. Move the boundary into the body, which is read after the skill has already been chosen. Keep it in the description only when a model-invoked neighbour would otherwise take the work, and then name that neighbour rather than its topic.
QL014 - Fuzzy boundary between fire and do-not-fire
Why it matters. An exclusion clause in the description shares its topic words with a clause that claims the work. A description can separate its branches well on average and still be misrouted by one pair like this, because the router matches wording and a negation does not reverse a match. QL003 compares two clauses that agree and calls the second redundant; this compares two that disagree, where the second reads as a reason to fire rather than a reason to stay quiet.
Fix. Make the two sides differ in topic words, not only in the negation - or move the exclusion into the body, which is read after the skill has been chosen.
QL015 - User-invoked skill’s description is written for the router
Why it matters. With disable-model-invocation: true the description is not in the model’s context at all. Trigger wordings, orders to fire or not to fire, and the user spoken of in the third person are addressed to a reader who never sees them - and they take the place of the one thing the remaining reader needs: what the command does when you run it.
Fix. Rewrite the description as a one-line menu entry for a person. Keep the routing boundary, if it matters, in the body.
QL016 - Procedure written as one block of prose
Why it matters. The body has no steps - no numbered item, no step heading - and one paragraph of 200 words or more carries an order (first, then, finally). An agent reading a procedure that way has to find the step boundaries itself, and a step buried mid-sentence is the one it skips. The Stepless Workflow smell of arXiv 2607.01456, which found it in 32% of 238 marketplace skills with a model; this check sees only the extreme shape.
Fix. Split the paragraph at its steps, each one saying what it is for. Numbered or bulleted both work. Keep each step a goal, not a fixed command line: the same paper counts a rigid series of commands as a smell of its own (62%), so the fix is structure, not a script.
QL017 - No example anywhere in the skill
Why it matters. Neither SKILL.md nor any reference gives an example: no for example, no fenced block, no bad / good pair, no quoted wording. The agent works from the description of a thing alone, and fills what it has not seen with a guess. Missing Example in arXiv 2607.01456 (34% of 238 skills by a model).
Fix. Add one worked example of the input and what the skill makes of it - in SKILL.md if it is short, in a reference the body links if it is long.
QL018 - Output produced, never checked
Why it matters. The body tells the agent to write, create, render or build something, and no text in the skill mentions checking the result in any form - verify, test, compare, review, a checklist. The output is treated as done the moment it exists. No Validation Step in arXiv 2607.01456 (69% of 238 skills by a model); this check fires only where checking is never mentioned at all.
Fix. Add the check that would catch this output being wrong - run it, render it and look, compare it with the source - and what to do when it fails.
QL019 - Output format required, no template shown
Why it matters. A section is headed as the output or report format, or a sentence promises “the following format:”, and what follows is prose: no fenced block, table, list of parts, sample or linked file. The agent invents a layout each run, and two runs disagree. Missing Template in arXiv 2607.01456 (25% of 238 skills by a model).
Fix. Put the template under the heading - a fenced skeleton with the parts filled by placeholders - or link the file that holds it.
security (SExxx)
What an installed skill can do to the machine that loads it. A skill is executable text, and an installed skill is a supply chain.
| Code | Severity | Confidence | False positives | Autofix | What it is |
|---|---|---|---|---|---|
SE001 |
error | high | medium | no | Secret in the skill text |
SE002 |
warning | medium | medium | no | Destructive command in an instruction |
SE003 |
error | medium | medium | no | Instruction-override text |
SE004 |
error | high | low | yes | Hidden or bidirectional Unicode |
SE005 |
warning | medium | medium | no | Outbound network call carrying local data |
SE006 |
info | high | medium | no | Absolute path naming a user account |
SE007 |
info | medium | low | no | Skill vouches for itself |
SE008 |
error | medium | low | no | Code decoded before it runs |
SE001 - Secret in the skill text
Why it matters. An API key, token or private key committed into a skill travels with every copy of it.
Fix. Revoke the credential, then remove it and read it from the environment.
SE002 - Destructive command in an instruction
Why it matters. A recursive delete, a download piped into a shell, world-writable permissions, a force push: the agent runs what the skill tells it to run.
Fix. Narrow the command, or make the step ask the human first.
SE003 - Instruction-override text
Why it matters. An instruction to disregard what came before, a claimed change of role, an order to keep something from the user. In a skill you wrote it is a mistake; in a skill you installed it is the payload.
Fix. Remove it. If the skill came from elsewhere, read the whole file before using it.
SE004 - Hidden or bidirectional Unicode
Why it matters. Zero-width and bidi control characters make the rendered text differ from the text the model reads - the Trojan Source trick.
Fix. Strip the characters. sqs.py fix removes them.
SE005 - Outbound network call carrying local data
Why it matters. A step that posts file contents to a host is exfiltration whether or not it was meant as one.
Fix. Confirm the host is yours and that the human agreed to the send.
SE006 - Absolute path naming a user account
Why it matters. C:\Users\<name>, /home/<name>: the skill only works on one machine, and it publishes whose machine that is.
Fix. Use ~, or an environment variable.
SE007 - Skill vouches for itself
Why it matters. A guarantee of safety, an endorsement by a named vendor, a count of users who trust it, or an invitation to skip review. Nothing in a skill can certify the skill: whoever wrote the files wrote the badge too. It is aimed at the router choosing between skills and at the person deciding whether to install, and it asks both to take on faith what they should check. The router’s half is a description that ranks the skill above its neighbours (“the best tool”, “better than any other”): rewriting a tool’s description that way moved its selection rate from about 20% to 81% in one published attack.
Fix. Remove the claim, or replace it with something a reader can verify - a link to an audit, a test suite, a repository with history. In a description, say when the skill applies, not how it compares.
SE008 - Code decoded before it runs
Why it matters. A script or an instruction decodes text - base64, hex, a compressed or marshalled blob - and executes the result: a decoder piped into a shell, PowerShell’s encoded-command switch, eval over a decoded string, or Python’s exec/eval over a decoder’s output. Nothing on the page says what runs, which is the purpose of writing it that way; no instruction a person has to trust needs to be unreadable to them.
Fix. Put the code in the file in plain text. If it is a vendored binary or data, ship it as a file with its source named, not as a string that is executed.
spec (SPxxx)
Agent Skills specification conformance. Everything here is something a skill survives locally and fails on publication, which is why it is a separate pass: the author never sees it until it is too late.
| Code | Severity | Confidence | False positives | Autofix | What it is |
|---|---|---|---|---|---|
SP001 |
error | high | low | no | No frontmatter |
SP002 |
error | high | low | yes | Required field name missing |
SP003 |
error | high | low | no | Required field description missing |
SP004 |
error | high | low | yes | name does not match the folder |
SP005 |
warning | high | low | no | name longer than 64 characters |
SP006 |
warning | high | low | yes | name off-spec |
SP007 |
error | high | low | no | description empty |
SP008 |
warning | high | low | no | description over 1024 characters |
SP009 |
warning | high | low | no | compatibility over 500 characters |
SP010 |
warning | high | medium | no | Unknown frontmatter key |
SP011 |
error | high | low | no | Unclosed code fence |
SP012 |
info | high | medium | no | Directory outside the conventions |
SP013 |
info | high | high | no | Extraneous file at the skill root |
SP014 |
error | high | low | no | No body |
SP015 |
info | high | low | no | Multiline description |
SP016 |
warning | high | medium | no | Oversized or binary asset |
SP017 |
warning | high | medium | no | Reference nested too deep |
SP018 |
error | high | low | no | XML tag in name or description |
SP019 |
error | high | low | no | Reserved word in name |
SP020 |
warning | high | low | no | Bare angle bracket in description |
SP001 - No frontmatter
Why it matters. Without a --- block the file is plain markdown and never loads as a skill.
Fix. Open the file with ---, put name and description inside, close with ---.
SP002 - Required field name missing
Why it matters. The loader keys the skill by name; without it there is nothing to invoke.
Fix. Add name: <folder-name>.
SP003 - Required field description missing
Why it matters. The description is the skill’s only context pointer. No description, no triggering.
Fix. Add a description stating what the skill does and when to reach for it.
SP004 - name does not match the folder
Why it matters. The specification requires them equal. What actually happens is split, and the client-implementation guide states both halves: a lenient client warns and loads the skill anyway, while skills-ref validate and publication reject it. So it works on your machine and fails the moment the skill leaves it.
Fix. Rename the folder or the field so the two agree.
SP005 - name longer than 64 characters
Why it matters. Over the spec limit. Claude Code tolerates it, skills-ref validate and publication do not.
Fix. Shorten the name.
SP006 - name off-spec
Why it matters. The spec allows lowercase latin letters, digits and single inner hyphens only.
Fix. Rewrite as lower-case-with-hyphens.
SP007 - description empty
Why it matters. An empty description is the same as none: the skill never triggers.
Fix. Write the description.
SP008 - description over 1024 characters
Why it matters. Over the spec limit. It works locally and is rejected on publication.
Fix. Cut restated identity and synonym triggers; keep one trigger per branch.
SP009 - compatibility over 500 characters
Why it matters. Over the spec limit.
Fix. Shorten it.
SP010 - Unknown frontmatter key
Why it matters. A typo in a key is not a syntax error: the field silently vanishes along with its meaning, and the skill keeps loading as if nothing happened.
Fix. Fix the spelling, or drop the key.
SP011 - Unclosed code fence
Why it matters. Everything after the unclosed fence reads as code, so the instructions that follow it stop being instructions.
Fix. Close the fence.
SP012 - Directory outside the conventions
Why it matters. The specification permits any directory beside SKILL.md and calls scripts/, references/ and assets/ recommendations, so this is not a violation. It is a portability note: tools that walk the conventional layout will not see the directory, and harnesses document different sets - Cline names docs/ and templates/, Antigravity names examples/ and resources/.
Fix. Leave it if the skill links it from SKILL.md; compat says what each harness makes of it. Add the name to allow_dirs to stop being told.
SP013 - Extraneous file at the skill root
Why it matters. README.md, CHANGELOG.md, Makefile and friends are repository furniture. Inside a published skill they ship as payload nobody reads.
Fix. Keep them outside the skill folder, or accept the cost knowingly.
SP014 - No body
Why it matters. Frontmatter present, instructions absent: the skill activates and says nothing.
Fix. Write the instructions.
SP015 - Multiline description
Why it matters. A folded or literal block scalar (>-, |) is valid YAML and Claude Code reads it. It is flagged as non-portable by the vendor-neutral validators, which is as far as the evidence goes: whether a given other runtime reads it has to be tried, not assumed.
Fix. Put the description on one line if the skill has to be portable; leave it if Claude Code is the only target.
SP016 - Oversized or binary asset
Why it matters. Every byte in the skill folder travels with it. A large binary is paid for by everyone who installs the skill and read by nobody.
Fix. Host it elsewhere and link, or shrink it.
SP017 - Reference nested too deep
Why it matters. Progressive disclosure is one hop: SKILL.md names the file. Three levels down, nothing reaches the file but a reader who already knew it was there.
Fix. Flatten to references/<file>.md, or link the intermediate level explicitly.
SP018 - XML tag in name or description
Why it matters. The Agent Skills validation rules forbid XML tags in either field. The description is injected into a system prompt, where a stray tag changes how the surrounding text is read.
Fix. Remove the angle brackets; name the tag in prose instead.
SP019 - Reserved word in name
Why it matters. anthropic and claude are reserved in a skill name by the validation rules, and the refusal comes at upload time, after the skill is finished.
Fix. Rename the skill after what it does.
SP020 - Bare angle bracket in description
Why it matters. Not a tag, so SP018 stays quiet - but the validator the reference skill-creation tooling ships refuses any < or > in a description, and its packager runs that validator before packaging. An arrow or a <10MB is enough for a skill that works locally to be refused there.
Fix. Write it out: “to” for an arrow, “under 10 MB” for a comparison.
structure (STxxx)
Links, orphans, budgets, section pointers, outbound paths. This is the class of defect that breaks today, in silence: a broken pointer into references/ crashes nothing, the agent just skips the step.
| Code | Severity | Confidence | False positives | Autofix | What it is |
|---|---|---|---|---|---|
ST001 |
error | high | low | no | Link to a file that does not exist |
ST002 |
error | high | low | no | Broken sibling link |
ST003 |
error | high | low | no | Link to a skill that does not exist |
ST004 |
error | high | low | no | Linked skill has no such file |
ST005 |
warning | high | medium | no | Orphan file |
ST006 |
warning | high | low | no | SKILL.md over budget |
ST007 |
warning | high | low | no | Reference over budget |
ST008 |
warning | medium | medium | no | Pointer to a section that is gone |
ST009 |
warning | medium | medium | no | Path naming no folder in this skill |
ST010 |
warning | medium | medium | no | Path with no skill name |
ST011 |
error | high | low | no | Outbound path does not exist |
ST012 |
warning | medium | high | no | Code as prose |
ST013 |
warning | high | low | no | Body nearly empty |
ST014 |
error | high | low | no | Duplicate name across skills |
ST015 |
error | high | low | no | No SKILL.md |
ST016 |
warning | unrated | unrated | no | External link unreachable |
ST017 |
warning | high | medium | no | Path into a tool’s folder not on this machine |
ST001 - Link to a file that does not exist
Why it matters. The agent does not crash on a broken pointer: it skips the step in silence, and the only symptom is that the work came out worse than usual.
Fix. Restore the file or correct the link.
ST002 - Broken sibling link
Why it matters. Same silent skip, in the short [text](neighbour.md) form that a path-shaped check cannot see.
Fix. Restore the file or correct the link.
ST003 - Link to a skill that does not exist
Why it matters. A rename elsewhere left this pointer aimed at nothing.
Fix. Point at the new name, or drop the link.
ST004 - Linked skill has no such file
Why it matters. The skill is there, the file inside it is not.
Fix. Correct the path, or restore the file in the other skill.
ST005 - Orphan file
Why it matters. Nothing links to it. Either it was never wired up, or it is no longer needed. An orphan is a pruning candidate, not a defect.
Fix. Link it from SKILL.md, or delete it.
ST006 - SKILL.md over budget
Why it matters. SKILL.md is loaded whole on every activation, so its length is a permanent tax on the context window.
Fix. Move what only some branches need into references/, and keep the routing - which reference to open when - in SKILL.md.
ST007 - Reference over budget
Why it matters. A reference is opened whole too, just later. A 30 KB file cancels the point of two-stage loading.
Fix. Split it by branch.
ST008 - Pointer to a section that is gone
Why it matters. The file is in place, so an ordinary link check stays quiet while the agent opens the reference and does not find what it came for. Renaming a heading is enough.
Fix. Restore the heading, or correct the pointer - copying the section name from the file verbatim, never from memory.
ST009 - Path naming no folder in this skill
Why it matters. Almost always an example path from somebody else’s repository rather than a route of your own.
Fix. Leave it if it is an example; fix it if it was meant to be a route.
ST010 - Path with no skill name
Why it matters. The file lives in a different skill, and the bare relative path resolves there only by accident.
Fix. Spell out ~/.claude/skills/<skill>/<path>.
ST011 - Outbound path does not exist
Why it matters. A skill that reads a note before working degrades silently when the note is renamed: the theory is gone and the work goes on anyway.
Fix. Update the path. Cyrillic is compared NFC/NFD-insensitively, so a hit here is a real miss, not a normalisation artefact.
ST012 - Code as prose
Why it matters. A long executable block sitting in the text is retyped by the model every run - probabilistically, and for tokens. It cannot be run, and it cannot be fixed once.
Fix. Move it into scripts/; leave the call and how to read its output in the text.
ST013 - Body nearly empty
Why it matters. Enough text to load, not enough to act on.
Fix. Write the instructions.
ST014 - Duplicate name across skills
Why it matters. One shadows the other and which one wins is not knowable in advance.
Fix. Rename one of them.
ST015 - No SKILL.md
Why it matters. The folder is in the skills tree and holds no skill.
Fix. Add SKILL.md, or move the folder out.
ST016 - External link unreachable
Why it matters. A dead URL in a reference sends the agent to fetch nothing.
Fix. Update or remove the link. 403 is reported separately: it usually means the site blocks bots, not that the page is gone.
ST017 - Path into a tool’s folder not on this machine
Why it matters. A path under ~/.claude, ~/.config/<tool>, a dot-folder or a platform config root exists only where that tool is installed. On a CI runner or a fresh machine it is missing, and a missing install cannot be told from a broken path.
Fix. Check it on a machine with the tool installed. If the file only describes install locations, waive it for the file with sqs-allow-file: ST017.