P
PreToolUse (Skill)
HookFires on
PreToolUsematchingSkillImmediately before a tool runs — this is where an action can be denied.
Where this sits in a session
- Session start→
- Prompt submitted→
- Prompt expansion→
- Before tool use→
- After tool use→
- Notification→
- Before compaction→
- Subagent finished→
- Turn finished→
- Session end
Can block — This hook is able to deny the action or send Claude back to work.
What it runs
Executed automatically when the event fires — no prompt, no confirmation.
command· timeout 5s
node "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/track-active-skill.js"Install it yourself
Paste into .claude/settings.json to run this hook without installing the whole plugin. Adjust the paths to point at your own copy of the scripts.
settings.json
{
"hooks": {
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/track-active-skill.js\"",
"timeout": 5
}
],
"matcher": "Skill"
}
]
}
}Source
Read this before installing — it runs on your machine with your permissions.
#!/usr/bin/env node
/**
* PreToolUse hook on the Skill tool: track which carta-cap-table skills are loaded.
*
* Appends each skill to an ordered list in a session-scoped state file.
* The inject-instrumentation hook reads this list and includes it in
* every MCP tool call for server-side telemetry.
*
* State file: /tmp/claude-carta-cap-table/<session_id>.json
*
* Part of the official Carta AI Agent Plugin.
*/
const fs = require('fs');
const os = require('os');
const path = require('path');
const STATE_DIR = process.env.CLAUDE_PLUGIN_DATA
? path.join(process.env.CLAUDE_PLUGIN_DATA, 'sessions')
: '/tmp/claude-carta-cap-table';
let inputData = '';
process.stdin.on('data', chunk => (inputData += chunk));
process.stdin.on('end', () => {
try {
const input = JSON.parse(inputData);
const { tool_input, session_id } = input;
const skillFull = tool_input?.skill || '';
// Only track carta-cap-table skills
if (!skillFull.startsWith('carta-cap-table:')) {
allow();
return;
}
const skillName = skillFull.replace('carta-cap-table:', '');
if (session_id) {
fs.mkdirSync(STATE_DIR, { recursive: true });
const statePath = path.join(STATE_DIR, `${session_id}.json`);
// Read existing state
let existing = {};
try { existing = JSON.parse(fs.readFileSync(statePath, 'utf8')); } catch {}
// Append skill if not already in the list
const skills = existing.skills || [];
if (!skills.includes(skillName)) {
skills.push(skillName);
}
existing.skills = skills;
fs.writeFileSync(statePath, JSON.stringify(existing));
// Record the most-recently-invoked skill (namespaced) into the shared
// instrumentation registry so inject-instrumentation orders the emitted
// skills union with the genuine latest last (KAF-2912). Best effort.
try {
const base = process.env.CARTA_INSTRUMENTATION_REGISTRY_DIR
|| path.join(os.tmpdir(), 'carta-instrumentation');
const regDir = path.join(base, String(session_id).replace(/[^A-Za-z0-9._-]/g, '_'));
fs.mkdirSync(regDir, { recursive: true });
fs.writeFileSync(path.join(regDir, '.last-skill'), skillFull);
} catch {}
}
allow();
} catch (err) {
process.stderr.write(`track-active-skill error: ${err.message}\n`);
allow();
}
});
function allow() {
process.stdout.write(JSON.stringify({
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'allow',
},
}));
process.exit(0);
}
Shipped by 1 plugin
Installing any of these installs this hook.
Reviews
Log in to leave a review.
No reviews yet — be the first.
Explore related
Other things in this space — across every part of the ecosystem, not just hooks.