The point of Claude Skills in Claude Code is autonomous skill activation. You define a skill and Claude automatically triggers it when relevant.
But if you've actually tried building with Skills, you know that's not how it behaves by default. They don't trigger consistently. Even when the trigger conditions look correct, Claude often ignores them.
And while you can find workarounds like slash commands to fetch skills manually or stacking rules inside the CLAUDE.md file, these methods lose the core benefit: autonomous triggering.
There's a system that gets skill activation to 95% with the right setup. After implementing it and testing other approaches, I found two methods plus a bonus tip that works.
It's not perfect. But it's reliable enough to build on.
In this article, I'll walk through the methods that make the difference. We'll use the valibot-usage skill as our running example so you can see exactly how these fixes work in practice.
Table of Contents
TL;DR
This guide shows two proven fixes for reliable skill activation in Claude Code:
With proper skill activation, Claude reliably uses Valibot for composable TypeScript schema validation instead of writing manual checks.
Let's get right into it.
Fix 1: Detection Hook + Trigger Rules
The issue is, Claude doesn't detect when to use skills. You submit a prompt that requires a skill you've added, and Claude just ignores it and does its own thing.
For this example, we're going to trigger the Open Circle's Valibot-usage skill automatically, anytime we need validation . Valibot built an official skill that handles schema generation, type inference, and all the validation patterns you'd normally write by hand.
Prerequisites
Before setting up automatic triggering, install two things:
1. The Valibot library:
npm install valibot
2. The Valibot skill:
Copy the valibot-usage folder from Open Circle's skills repo into your .skills/ directory.
We're copying just the Valibot-usage skill instead of installing the full package because running a command would install Formisch too (which is for form management), and we don't need that here.
Valibot-usage skill is perfect for this example because Valibot itself is modular. It only pulls in the validators you actually use, not the entire library. So when Claude's working with your validation code, it's dealing with just the specific validators you need, not a massive validation library sitting in context.
The skill teaches Claude to use Valibot's composable validators instead of falling back to manual if-statement checks. You get type-safe schemas that compose together, and Claude writes them automatically when you ask for validation.
To be clear, If you explicitly tell it to use just the Valibot library or your codebase already strongly signals schema-based validation, it may generate a proper Valibot schema. The issue isn’t capability but it’s consistency. Without reliable skill activation, Claude may default to generic validation patterns more often than you’d expect.
Now, let's set up our magic hook.
What You're Installing
Three components work together to make skills trigger automatically:
This approach turns unreliable Claude Code skill activation into a predictable auto-trigger system.
Step 1: Create the Hook Files
You need two files for the hook to work. Create both in your .claude/hooks/ directory.
File 1: .claude/hooks/skill-activation-prompt.sh
#!/bin/bash
set -e
cd "$CLAUDE_PROJECT_DIR/.claude/hooks"
cat | npx tsx skill-activation-prompt.ts
Make it executable:
chmod +x .claude/hooks/skill-activation-prompt.sh
File 2: .claude/hooks/skill-activation-prompt.ts
#!/usr/bin/env node
import { readFileSync } from 'fs';
import { join } from 'path';
interface HookInput {
session_id: string;
transcript_path: string;
cwd: string;
permission_mode: string;
prompt: string;
}
interface PromptTriggers {
keywords?: string[];
intentPatterns?: string[];
}
interface SkillRule {
type: 'guardrail' | 'domain';
enforcement: 'block' | 'suggest' | 'warn';
priority: 'critical' | 'high' | 'medium' | 'low';
promptTriggers?: PromptTriggers;
}
interface SkillRules {
version: string;
skills: Record;
}
interface MatchedSkill {
name: string;
matchType: 'keyword' | 'intent';
config: SkillRule;
}
async function main() {
try {
const input = readFileSync(0, 'utf-8');
const data: HookInput = JSON.parse(input);
const prompt = data.prompt.toLowerCase();
const projectDir = process.env.CLAUDE_PROJECT_DIR || '$HOME/project';
const rulesPath = join(projectDir, '.claude', 'skills', 'skill-rules.json');
const rules: SkillRules = JSON.parse(readFileSync(rulesPath, 'utf-8'));
const matchedSkills: MatchedSkill[] = [];
for (const [skillName, config] of Object.entries(rules.skills)) {
const triggers = config.promptTriggers;
if (!triggers) {
continue;
}
if (triggers.keywords) {
const keywordMatch = triggers.keywords.some(kw =>
prompt.includes(kw.toLowerCase())
);
if (keywordMatch) {
matchedSkills.push({ name: skillName, matchType: 'keyword', config });
continue;
}
}
if (triggers.intentPatterns) {
const intentMatch = triggers.intentPatterns.some(pattern => {
const regex = new RegExp(pattern, 'i');
return regex.test(prompt);
});
if (intentMatch) {
matchedSkills.push({ name: skillName, matchType: 'intent', config });
}
}
}
if (matchedSkills.length > 0) {
let output = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
output += '🎯 SKILL ACTIVATION CHECK\n';
output += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n';
const critical = matchedSkills.filter(s => s.config.priority === 'critical');
const high = matchedSkills.filter(s => s.config.priority === 'high');
const medium = matchedSkills.filter(s => s.config.priority === 'medium');
const low = matchedSkills.filter(s => s.config.priority === 'low');
if (critical.length > 0) {
output += '⚠️ CRITICAL SKILLS (REQUIRED):\n';
critical.forEach(s => output += ` → ${s.name}\n`);
output += '\n';
}
if (high.length > 0) {
output += '📚 RECOMMENDED SKILLS:\n';
high.forEach(s => output += ` → ${s.name}\n`);
output += '\n';
}
if (medium.length > 0) {
output += '💡 SUGGESTED SKILLS:\n';
medium.forEach(s => output += ` → ${s.name}\n`);
output += '\n';
}
if (low.length > 0) {
output += '📌 OPTIONAL SKILLS:\n';
low.forEach(s => output += ` → ${s.name}\n`);
output += '\n';
}
output += 'ACTION: Use Skill tool BEFORE responding\n';
output += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
console.log(output);
}
process.exit(0);
} catch (err) {
console.error('Error in skill-activation-prompt hook:', err);
process.exit(1);
}
}
main().catch(err => {
console.error('Uncaught error:', err);
process.exit(1);
});
Install dependencies:
cd .claude/hooks
npm install
Step 2: Configure Skill Triggers
Create .claude/skills/skill-rules.json:
{
"version": "1.0",
"skills": {
"valibot-usage": {
"type": "domain",
"enforcement": "suggest",
"priority": "high",
"description": "Valibot schema validation",
"promptTriggers": {
"keywords": [
"validate",
"validation",
"schema",
"type check",
"verify input",
"validate data",
"parse request"
]
},
"fileTriggers": {
"pathPatterns": [
"**/*.ts",
"**/*.js"
]
}
}
}
}
This file tells the hook when to suggest the Valibot-usage skill. When you type words like "validate" or "schema" while editing TypeScript or JavaScript files, the hook detects it and suggests the Valibot skill.
Each skill you want to auto-trigger needs an entry in this file. Right now we only have Valibot, but you can add more skills later by adding more entries under "skills". For example, if you had a React patterns skill, you'd add another entry for "react-patterns" with its own keywords and file patterns.
Step 3: Activate the Hook
Add this to your .claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/skill-activation-prompt.sh"
}
]
}
]
}
}
If you already have a settings.json with other hooks, just add the UserPromptSubmit section.
How It Works
Before:
You: "validate this user input"
Claude: *writes manual if-statement validation*
After:
You: "validate this user input"
Hook: detects "validate" + .ts file
Claude: RECOMMENDED SKILLS:
Valibot-usage
ACTION: Use Skill tool BEFORE responding"
You: "yes"
Claude: uses valibot-usage skill to generate schema
One important thing to know: The hook doesn't automatically load skills for you. It detects when a skill is relevant and tells Claude about it, but Claude will still ask you "Should I load the Valibot-usage skill?" before actually using it. You have to say yes.
This is by design. The hook handles the detection part automatically so you don't have to remember which skill to use, but you still get final say on whether to load it.
The Result: Reliable Valibot Skill Activation in Claude Code
Before (manual validation):
function validateUser(data: any) {
if (!data.email || typeof data.email !== 'string') {
throw new Error('Invalid email');
}
if (!data.email.includes('@')) {
throw new Error('Email must contain @');
}
if (!data.age || typeof data.age !== 'number') {
throw new Error('Invalid age');
}
if (data.age 18) {
throw new Error('Must be 18 or older');
}
// ... continues for every field
}
After (Valibot-usage skill):
import * as v from 'valibot';
const UserSchema = v.object({
email: v.pipe(v.string(), v.email()),
age: v.pipe(v.number(), v.minValue(18)),
name: v.pipe(v.string(), v.minLength(2))
});
const result = v.safeParse(UserSchema, data);
Notice how Valibot's composable validators work. The v.pipe() lets you chain validations in a readable way. v.string() then v.email() for the email field. Each validation is its own function that you can compose together. This is cleaner than nested if-statements, and you get proper TypeScript types automatically from the schema.
Now when you're building an API endpoint and ask Claude to validate the request body, it uses Valibot's composable validators instead of writing manual checks. The validation logic stays readable even as your schemas get more complex.
Fix 2: Forced Skill Evaluation
There's another way to get skills working: force Claude to think out loud before coding.
This fix makes Claude stop and evaluate each skill before touching any code. It can't skip straight to implementation.
Setup
Create .claude/hooks/skill-forced-eval.sh:
#!/bin/bash
# UserPromptSubmit hook that forces explicit skill evaluation
#
# This hook requires Claude to explicitly evaluate each available skill
# before proceeding with implementation.
#
# Installation: Copy to .claude/hooks/UserPromptSubmit
cat INSTRUCTION: MANDATORY SKILL ACTIVATION SEQUENCE
Step 1 - EVALUATE (do this in your response):
For each skill in , state: [skill-name] - YES/NO - [reason]
Step 2 - ACTIVATE (do this immediately after Step 1):
IF any skills are YES → Use Skill(skill-name) tool for EACH relevant skill NOW
IF no skills are YES → State "No skills needed" and proceed
Step 3 - IMPLEMENT:
Only after Step 2 is complete, proceed with implementation.
CRITICAL: You MUST call Skill() tool in Step 2. Do NOT skip to implementation.
The evaluation (Step 1) is WORTHLESS unless you ACTIVATE (Step 2) the skills.
Example of correct sequence:
- research: NO - not a research task
- Valibot-usage: YES - need reactive state
[Then IMMEDIATELY use Skill() tool:]
> Skill(Valibot-usage)
[THEN and ONLY THEN start implementation]
EOF
#!/bin/bash
cat ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━
🎯 SKILL ACTIVATION PROTOCOL - MANDATORY
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━
Step 1 - EVALUATE: For each available skill, state YES/NO if it matches this prompt
Step 2 - ACTIVATE: Use Skill(skill-name) tool NOW for each YES
Step 3 - IMPLEMENT: Only after activation, proceed with implementation
CRITICAL: The evaluation is WORTHLESS unless you ACTIVATE the skills using Skill() tool.
Format:
- skill-name: YES/NO - [reason]
- ...
Then IMMEDIATELY use Skill() for each YES before any implementation.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━
EOF
Make it executable:
chmod +x .claude/hooks/skill-forced-eval.sh
Add to .claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": ".claude/hooks/skill-forced-eval.sh"
}
]
}
]
}
}
How It Works
When you type "validate this API request", Claude has to list out every skill and decide YES or NO:
Step 1 - EVALUATE:
Step 2 - ACTIVATE:
Skill(valibot-usage)
Step 3 - IMPLEMENT:
Claude writes down the decision, then has to follow through. Once it says "YES - need schema validation", it's locked in. It has to activate the skill before coding.
The aggressive words like "MANDATORY" and "CRITICAL" help. They make it harder for Claude to just skip the activation step.
It's more verbose though. You'll see Claude list every skill before it starts. But it works because Claude has to commit to the decision in writing before implementing anything.
Bonus: Settings Tweak Worth Testing
There's one more approach that's simpler than hooks but less consistent. If you don't want to deal with hook files, you can add instructions directly to Claude Code's settings.
Go to Settings at Custom Instructions and add this:
MANDATORY: Before responding to ANY prompt, you MUST:
1. Check ALL available skills in
2. Identify which skills apply to this prompt
3. Use Skill(skill-name) for EACH applicable skill
4. ONLY THEN start your response
Do NOT skip skill activation. Do NOT proceed without checking skills.
This is NON-NEGOTIABLE for every single prompt.
The aggressive language ("MANDATORY", "NON-NEGOTIABLE") makes it harder for Claude to ignore. It becomes part of Claude's system context on every interaction.
This works, but it's not as reliable as the hook approaches, as custom instructions sit in the background. Claude can still override them when it thinks it knows better, but Hooks force the behavior by injecting it into every prompt.
If you want something quick to test without installing files, try this. But if you need consistent activation, use one of the hook methods instead.
Shoutouts & References
The hook system used in section 2 was built by diet103 (check his full showcase for more).
The skill activation logic in section 3 is based on research by Scott Spence.
More...
But if you've actually tried building with Skills, you know that's not how it behaves by default. They don't trigger consistently. Even when the trigger conditions look correct, Claude often ignores them.
And while you can find workarounds like slash commands to fetch skills manually or stacking rules inside the CLAUDE.md file, these methods lose the core benefit: autonomous triggering.
There's a system that gets skill activation to 95% with the right setup. After implementing it and testing other approaches, I found two methods plus a bonus tip that works.
It's not perfect. But it's reliable enough to build on.
In this article, I'll walk through the methods that make the difference. We'll use the valibot-usage skill as our running example so you can see exactly how these fixes work in practice.
Table of Contents
- TL;DR
- Fix 1: Detection Hook + Trigger Rules
- Prerequisites
- What You're Installing
- Step 1: Create the Hook Files
- Step 2: Configure Skill Triggers
- Step 3: Activate the Hook
- How It Works
- Fix 2: Forced Skill Evaluation
- Setup
- How It Works
- Bonus: Settings Tweak Worth Testing
- Shoutouts & References
TL;DR
This guide shows two proven fixes for reliable skill activation in Claude Code:
- A detection hook that auto-suggests relevant skills.
- A forced evaluation hook that makes Claude activate skills before writing code.
With proper skill activation, Claude reliably uses Valibot for composable TypeScript schema validation instead of writing manual checks.
Let's get right into it.
Fix 1: Detection Hook + Trigger Rules
The issue is, Claude doesn't detect when to use skills. You submit a prompt that requires a skill you've added, and Claude just ignores it and does its own thing.
For this example, we're going to trigger the Open Circle's Valibot-usage skill automatically, anytime we need validation . Valibot built an official skill that handles schema generation, type inference, and all the validation patterns you'd normally write by hand.
Prerequisites
Before setting up automatic triggering, install two things:
1. The Valibot library:
npm install valibot
2. The Valibot skill:
Copy the valibot-usage folder from Open Circle's skills repo into your .skills/ directory.
We're copying just the Valibot-usage skill instead of installing the full package because running a command would install Formisch too (which is for form management), and we don't need that here.
Valibot-usage skill is perfect for this example because Valibot itself is modular. It only pulls in the validators you actually use, not the entire library. So when Claude's working with your validation code, it's dealing with just the specific validators you need, not a massive validation library sitting in context.
The skill teaches Claude to use Valibot's composable validators instead of falling back to manual if-statement checks. You get type-safe schemas that compose together, and Claude writes them automatically when you ask for validation.
To be clear, If you explicitly tell it to use just the Valibot library or your codebase already strongly signals schema-based validation, it may generate a proper Valibot schema. The issue isn’t capability but it’s consistency. Without reliable skill activation, Claude may default to generic validation patterns more often than you’d expect.
Now, let's set up our magic hook.
What You're Installing
Three components work together to make skills trigger automatically:
- A hook that runs every time you send a message to Claude
- A configuration file that tells the hook when to suggest skills
- A settings update to activate the hook
This approach turns unreliable Claude Code skill activation into a predictable auto-trigger system.
Step 1: Create the Hook Files
You need two files for the hook to work. Create both in your .claude/hooks/ directory.
File 1: .claude/hooks/skill-activation-prompt.sh
#!/bin/bash
set -e
cd "$CLAUDE_PROJECT_DIR/.claude/hooks"
cat | npx tsx skill-activation-prompt.ts
Make it executable:
chmod +x .claude/hooks/skill-activation-prompt.sh
File 2: .claude/hooks/skill-activation-prompt.ts
#!/usr/bin/env node
import { readFileSync } from 'fs';
import { join } from 'path';
interface HookInput {
session_id: string;
transcript_path: string;
cwd: string;
permission_mode: string;
prompt: string;
}
interface PromptTriggers {
keywords?: string[];
intentPatterns?: string[];
}
interface SkillRule {
type: 'guardrail' | 'domain';
enforcement: 'block' | 'suggest' | 'warn';
priority: 'critical' | 'high' | 'medium' | 'low';
promptTriggers?: PromptTriggers;
}
interface SkillRules {
version: string;
skills: Record;
}
interface MatchedSkill {
name: string;
matchType: 'keyword' | 'intent';
config: SkillRule;
}
async function main() {
try {
const input = readFileSync(0, 'utf-8');
const data: HookInput = JSON.parse(input);
const prompt = data.prompt.toLowerCase();
const projectDir = process.env.CLAUDE_PROJECT_DIR || '$HOME/project';
const rulesPath = join(projectDir, '.claude', 'skills', 'skill-rules.json');
const rules: SkillRules = JSON.parse(readFileSync(rulesPath, 'utf-8'));
const matchedSkills: MatchedSkill[] = [];
for (const [skillName, config] of Object.entries(rules.skills)) {
const triggers = config.promptTriggers;
if (!triggers) {
continue;
}
if (triggers.keywords) {
const keywordMatch = triggers.keywords.some(kw =>
prompt.includes(kw.toLowerCase())
);
if (keywordMatch) {
matchedSkills.push({ name: skillName, matchType: 'keyword', config });
continue;
}
}
if (triggers.intentPatterns) {
const intentMatch = triggers.intentPatterns.some(pattern => {
const regex = new RegExp(pattern, 'i');
return regex.test(prompt);
});
if (intentMatch) {
matchedSkills.push({ name: skillName, matchType: 'intent', config });
}
}
}
if (matchedSkills.length > 0) {
let output = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
output += '🎯 SKILL ACTIVATION CHECK\n';
output += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n';
const critical = matchedSkills.filter(s => s.config.priority === 'critical');
const high = matchedSkills.filter(s => s.config.priority === 'high');
const medium = matchedSkills.filter(s => s.config.priority === 'medium');
const low = matchedSkills.filter(s => s.config.priority === 'low');
if (critical.length > 0) {
output += '⚠️ CRITICAL SKILLS (REQUIRED):\n';
critical.forEach(s => output += ` → ${s.name}\n`);
output += '\n';
}
if (high.length > 0) {
output += '📚 RECOMMENDED SKILLS:\n';
high.forEach(s => output += ` → ${s.name}\n`);
output += '\n';
}
if (medium.length > 0) {
output += '💡 SUGGESTED SKILLS:\n';
medium.forEach(s => output += ` → ${s.name}\n`);
output += '\n';
}
if (low.length > 0) {
output += '📌 OPTIONAL SKILLS:\n';
low.forEach(s => output += ` → ${s.name}\n`);
output += '\n';
}
output += 'ACTION: Use Skill tool BEFORE responding\n';
output += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
console.log(output);
}
process.exit(0);
} catch (err) {
console.error('Error in skill-activation-prompt hook:', err);
process.exit(1);
}
}
main().catch(err => {
console.error('Uncaught error:', err);
process.exit(1);
});
Install dependencies:
cd .claude/hooks
npm install
Step 2: Configure Skill Triggers
Create .claude/skills/skill-rules.json:
{
"version": "1.0",
"skills": {
"valibot-usage": {
"type": "domain",
"enforcement": "suggest",
"priority": "high",
"description": "Valibot schema validation",
"promptTriggers": {
"keywords": [
"validate",
"validation",
"schema",
"type check",
"verify input",
"validate data",
"parse request"
]
},
"fileTriggers": {
"pathPatterns": [
"**/*.ts",
"**/*.js"
]
}
}
}
}
This file tells the hook when to suggest the Valibot-usage skill. When you type words like "validate" or "schema" while editing TypeScript or JavaScript files, the hook detects it and suggests the Valibot skill.
Each skill you want to auto-trigger needs an entry in this file. Right now we only have Valibot, but you can add more skills later by adding more entries under "skills". For example, if you had a React patterns skill, you'd add another entry for "react-patterns" with its own keywords and file patterns.
Step 3: Activate the Hook
Add this to your .claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/skill-activation-prompt.sh"
}
]
}
]
}
}
If you already have a settings.json with other hooks, just add the UserPromptSubmit section.
How It Works
Before:
You: "validate this user input"
Claude: *writes manual if-statement validation*
After:
You: "validate this user input"
Hook: detects "validate" + .ts file
Claude: RECOMMENDED SKILLS:
Valibot-usage
ACTION: Use Skill tool BEFORE responding"
You: "yes"
Claude: uses valibot-usage skill to generate schema
One important thing to know: The hook doesn't automatically load skills for you. It detects when a skill is relevant and tells Claude about it, but Claude will still ask you "Should I load the Valibot-usage skill?" before actually using it. You have to say yes.
This is by design. The hook handles the detection part automatically so you don't have to remember which skill to use, but you still get final say on whether to load it.
The Result: Reliable Valibot Skill Activation in Claude Code
Before (manual validation):
function validateUser(data: any) {
if (!data.email || typeof data.email !== 'string') {
throw new Error('Invalid email');
}
if (!data.email.includes('@')) {
throw new Error('Email must contain @');
}
if (!data.age || typeof data.age !== 'number') {
throw new Error('Invalid age');
}
if (data.age 18) {
throw new Error('Must be 18 or older');
}
// ... continues for every field
}
After (Valibot-usage skill):
import * as v from 'valibot';
const UserSchema = v.object({
email: v.pipe(v.string(), v.email()),
age: v.pipe(v.number(), v.minValue(18)),
name: v.pipe(v.string(), v.minLength(2))
});
const result = v.safeParse(UserSchema, data);
Notice how Valibot's composable validators work. The v.pipe() lets you chain validations in a readable way. v.string() then v.email() for the email field. Each validation is its own function that you can compose together. This is cleaner than nested if-statements, and you get proper TypeScript types automatically from the schema.
Now when you're building an API endpoint and ask Claude to validate the request body, it uses Valibot's composable validators instead of writing manual checks. The validation logic stays readable even as your schemas get more complex.
Fix 2: Forced Skill Evaluation
There's another way to get skills working: force Claude to think out loud before coding.
This fix makes Claude stop and evaluate each skill before touching any code. It can't skip straight to implementation.
Setup
Create .claude/hooks/skill-forced-eval.sh:
#!/bin/bash
# UserPromptSubmit hook that forces explicit skill evaluation
#
# This hook requires Claude to explicitly evaluate each available skill
# before proceeding with implementation.
#
# Installation: Copy to .claude/hooks/UserPromptSubmit
cat INSTRUCTION: MANDATORY SKILL ACTIVATION SEQUENCE
Step 1 - EVALUATE (do this in your response):
For each skill in , state: [skill-name] - YES/NO - [reason]
Step 2 - ACTIVATE (do this immediately after Step 1):
IF any skills are YES → Use Skill(skill-name) tool for EACH relevant skill NOW
IF no skills are YES → State "No skills needed" and proceed
Step 3 - IMPLEMENT:
Only after Step 2 is complete, proceed with implementation.
CRITICAL: You MUST call Skill() tool in Step 2. Do NOT skip to implementation.
The evaluation (Step 1) is WORTHLESS unless you ACTIVATE (Step 2) the skills.
Example of correct sequence:
- research: NO - not a research task
- Valibot-usage: YES - need reactive state
[Then IMMEDIATELY use Skill() tool:]
> Skill(Valibot-usage)
[THEN and ONLY THEN start implementation]
EOF
#!/bin/bash
cat ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━
🎯 SKILL ACTIVATION PROTOCOL - MANDATORY
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━
Step 1 - EVALUATE: For each available skill, state YES/NO if it matches this prompt
Step 2 - ACTIVATE: Use Skill(skill-name) tool NOW for each YES
Step 3 - IMPLEMENT: Only after activation, proceed with implementation
CRITICAL: The evaluation is WORTHLESS unless you ACTIVATE the skills using Skill() tool.
Format:
- skill-name: YES/NO - [reason]
- ...
Then IMMEDIATELY use Skill() for each YES before any implementation.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━
EOF
Make it executable:
chmod +x .claude/hooks/skill-forced-eval.sh
Add to .claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": ".claude/hooks/skill-forced-eval.sh"
}
]
}
]
}
}
How It Works
When you type "validate this API request", Claude has to list out every skill and decide YES or NO:
Step 1 - EVALUATE:
- valibot-usage: YES - need schema validation
- react-patterns: NO - not building UI
Step 2 - ACTIVATE:
Skill(valibot-usage)
Step 3 - IMPLEMENT:
Claude writes down the decision, then has to follow through. Once it says "YES - need schema validation", it's locked in. It has to activate the skill before coding.
The aggressive words like "MANDATORY" and "CRITICAL" help. They make it harder for Claude to just skip the activation step.
It's more verbose though. You'll see Claude list every skill before it starts. But it works because Claude has to commit to the decision in writing before implementing anything.
Bonus: Settings Tweak Worth Testing
There's one more approach that's simpler than hooks but less consistent. If you don't want to deal with hook files, you can add instructions directly to Claude Code's settings.
Go to Settings at Custom Instructions and add this:
MANDATORY: Before responding to ANY prompt, you MUST:
1. Check ALL available skills in
2. Identify which skills apply to this prompt
3. Use Skill(skill-name) for EACH applicable skill
4. ONLY THEN start your response
Do NOT skip skill activation. Do NOT proceed without checking skills.
This is NON-NEGOTIABLE for every single prompt.
The aggressive language ("MANDATORY", "NON-NEGOTIABLE") makes it harder for Claude to ignore. It becomes part of Claude's system context on every interaction.
This works, but it's not as reliable as the hook approaches, as custom instructions sit in the background. Claude can still override them when it thinks it knows better, but Hooks force the behavior by injecting it into every prompt.
If you want something quick to test without installing files, try this. But if you need consistent activation, use one of the hook methods instead.
Shoutouts & References
The hook system used in section 2 was built by diet103 (check his full showcase for more).
The skill activation logic in section 3 is based on research by Scott Spence.
More...