From claude-commands
Validates Claude Code settings.json against official JSON schema using IDE support (VS Code, IntelliJ), Python jsonschema, and Node.js ajv scripts. Ensures configuration correctness.
npx claudepluginhub joshuarweaver/cascade-code-general-misc-2 --plugin jleechanorg-claude-commandsThis skill uses the workspace's default tool permissions.
**Purpose**: Comprehensive guide for validating Claude Code settings.json and agent files using official and community tools.
Provides best practices for Claude Code settings.json and hooks configuration to prevent validation errors. Useful for troubleshooting configs, agent files, and consulting official docs.
Audits Claude subagent configurations in .claude/agents/ for frontmatter completeness, tool assignment security, privilege risks, and naming consistency.
Validates Claude Code plugins for compliance with official guidelines including plugin.json manifest, directory structure, and formats for agents/skills/commands to prevent installation failures.
Share bugs, ideas, or general feedback.
Purpose: Comprehensive guide for validating Claude Code settings.json and agent files using official and community tools.
As of September 29, 2025, Anthropic provides official JSON Schema validation for Claude Code settings.
https://json.schemastore.org/claude-code-settings.json
Source: GitHub Issue #2783 - Closed as completed
Add the $schema field at the top of your settings.json:
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"env": {
"BASH_MAX_OUTPUT_LENGTH": "5000"
},
"permissions": {
"allow": ["Bash(git:*)"]
},
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [...]
}
]
}
}
Once the $schema field is added, your IDE will automatically:
Supported IDEs:
Use the official schema with standard JSON validation tools:
Using Python (jsonschema):
#!/usr/bin/env python3
"""Test Claude Code settings.json validation"""
import json
import requests
from jsonschema import validate, ValidationError
def test_settings_validation():
"""Validate settings.json against official schema"""
# Fetch official schema
schema_url = "https://json.schemastore.org/claude-code-settings.json"
schema = requests.get(schema_url).json()
# Load settings file
with open('.claude/settings.json', 'r') as f:
settings = json.load(f)
# Validate
try:
validate(instance=settings, schema=schema)
print("โ
settings.json is valid")
return True
except ValidationError as e:
print(f"โ Validation error: {e.message}")
print(f" Path: {' -> '.join(str(p) for p in e.path)}")
return False
if __name__ == "__main__":
success = test_settings_validation()
exit(0 if success else 1)
Using Node.js (ajv):
#!/usr/bin/env node
const Ajv = require('ajv');
const fs = require('fs');
const https = require('https');
async function testSettingsValidation() {
// Fetch official schema
const schemaUrl = 'https://json.schemastore.org/claude-code-settings.json';
const schema = await new Promise((resolve, reject) => {
https.get(schemaUrl, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => resolve(JSON.parse(data)));
}).on('error', reject);
});
// Load settings
const settings = JSON.parse(fs.readFileSync('.claude/settings.json', 'utf8'));
// Validate
const ajv = new Ajv();
const valid = ajv.validate(schema, settings);
if (valid) {
console.log('โ
settings.json is valid');
return true;
} else {
console.error('โ Validation errors:');
ajv.errors.forEach(err => {
console.error(` - ${err.instancePath}: ${err.message}`);
});
return false;
}
}
testSettingsValidation().then(success => process.exit(success ? 0 : 1));
A. claude-code-settings-schema (npm)
Generates local schema file for offline validation:
# Generate schema file
npx claude-code-settings-schema
# Your settings.json will reference local schema
{
"$schema": "./claude-code-settings.schema.json",
...
}
Benefits:
B. claude-json-validator (Python CLI)
Standalone validator for settings files:
# Clone repository
git clone https://github.com/trial123Zel/claude-json-validator
cd claude-json-validator
# Setup
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -r requirements.txt
# Validate
python claude-json-validator.py ~/.claude/settings.json
python claude-json-validator.py .claude/settings.json --verbose
python claude-json-validator.py settings.json --strict
Exit Codes:
0: Valid (warnings allowed unless --strict)1: Errors foundFile: .claude/hooks/validate_settings.py
#!/usr/bin/env python3
"""Pre-commit hook to validate settings.json"""
import json
import sys
from pathlib import Path
try:
import requests
from jsonschema import validate, ValidationError
except ImportError:
print("โ ๏ธ jsonschema not installed, skipping validation")
print(" Install with: pip install jsonschema requests")
sys.exit(0)
def validate_settings():
"""Validate all settings.json files in the project"""
schema_url = "https://json.schemastore.org/claude-code-settings.json"
# Find settings files
settings_files = [
Path.home() / '.claude' / 'settings.json',
Path('.claude') / 'settings.json',
]
errors = []
for settings_file in settings_files:
if not settings_file.exists():
continue
print(f"๐ Validating {settings_file}...")
try:
# Load schema
schema = requests.get(schema_url, timeout=5).json()
# Load settings
with open(settings_file, 'r') as f:
settings = json.load(f)
# Validate
validate(instance=settings, schema=schema)
print(f" โ
Valid")
except ValidationError as e:
print(f" โ Validation error: {e.message}")
print(f" Path: {' -> '.join(str(p) for p in e.path)}")
errors.append((settings_file, e))
except json.JSONDecodeError as e:
print(f" โ Invalid JSON: {e}")
errors.append((settings_file, e))
except Exception as e:
print(f" โ ๏ธ Error validating: {e}")
if errors:
print(f"\nโ Found {len(errors)} validation error(s)")
return False
print(f"\nโ
All settings files valid")
return True
if __name__ == "__main__":
success = validate_settings()
sys.exit(0 if success else 1)
Register in .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "bash -c 'if [[ \"$CLAUDE_TOOL_INPUT\" == *\"settings.json\"* ]]; then python3 .claude/hooks/validate_settings.py; fi'",
"description": "Validate settings.json before writing"
}
]
}
]
}
}
The simplest validation method:
# Run validation
/doctor
Advantages:
Pre-commit hook:
#!/bin/bash
# .claude/hooks/pre_commit_validation.sh
echo "๐ Running Claude Code validation..."
# Run /doctor (requires claude CLI)
if command -v claude >/dev/null 2>&1; then
claude /doctor --quiet || {
echo "โ /doctor validation failed"
exit 1
}
echo "โ
/doctor validation passed"
else
echo "โ ๏ธ claude CLI not found, skipping /doctor validation"
fi
exit 0
File: .claude/hooks/validate_agents.py
#!/usr/bin/env python3
"""Validate Claude Code agent frontmatter"""
import re
import sys
from pathlib import Path
def validate_agent_frontmatter(agent_file):
"""Validate agent file frontmatter"""
with open(agent_file, 'r') as f:
content = f.read()
# Check for frontmatter
if not content.startswith('---\n'):
return False, "Missing frontmatter opening '---'"
# Extract frontmatter
match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
if not match:
return False, "Invalid frontmatter structure"
frontmatter = match.group(1)
# Check required fields
required_fields = ['name', 'description']
missing_fields = []
for field in required_fields:
pattern = f'^{field}:\\s*.+$'
if not re.search(pattern, frontmatter, re.MULTILINE):
missing_fields.append(field)
if missing_fields:
return False, f"Missing required fields: {', '.join(missing_fields)}"
# Check for quoted values (should be unquoted)
quoted_pattern = r'^(name|description):\s*["\']'
if re.search(quoted_pattern, frontmatter, re.MULTILINE):
return False, "Frontmatter values should be unquoted (remove quotes)"
return True, "Valid"
def validate_all_agents():
"""Validate all agent files"""
agent_dirs = [
Path('.claude') / 'agents',
Path.home() / '.claude' / 'agents',
]
errors = []
for agent_dir in agent_dirs:
if not agent_dir.exists():
continue
for agent_file in agent_dir.glob('*.md'):
print(f"๐ Validating {agent_file.name}...")
valid, message = validate_agent_frontmatter(agent_file)
if valid:
print(f" โ
{message}")
else:
print(f" โ {message}")
errors.append((agent_file, message))
if errors:
print(f"\nโ Found {len(errors)} agent validation error(s)")
return False
print(f"\nโ
All agents valid")
return True
if __name__ == "__main__":
success = validate_all_agents()
sys.exit(0 if success else 1)
File: .github/workflows/validate-claude-config.yml
name: Validate Claude Code Configuration
on:
pull_request:
paths:
- '.claude/**'
push:
branches: [main]
paths:
- '.claude/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install jsonschema requests pyyaml
- name: Validate settings.json
run: |
python3 .claude/hooks/validate_settings.py
- name: Validate agent frontmatter
run: |
python3 .claude/hooks/validate_agents.py
- name: Check JSON syntax
run: |
for file in $(find .claude -name "*.json"); do
echo "Checking $file..."
python3 -m json.tool "$file" > /dev/null
done
File: .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: validate-claude-settings
name: Validate Claude Code settings
entry: python3 .claude/hooks/validate_settings.py
language: system
pass_filenames: false
files: '\.claude/settings\.json$'
- id: validate-claude-agents
name: Validate Claude Code agents
entry: python3 .claude/hooks/validate_agents.py
language: system
pass_filenames: false
files: '\.claude/agents/.*\.md$'
- id: check-claude-json
name: Check Claude Code JSON syntax
entry: python3 -m json.tool
language: system
files: '\.claude/.*\.json$'
File: tests/test_claude_config.py
#!/usr/bin/env python3
"""Complete test suite for Claude Code configuration"""
import json
import unittest
from pathlib import Path
import requests
from jsonschema import validate, ValidationError
class TestClaudeCodeConfig(unittest.TestCase):
"""Test Claude Code configuration files"""
@classmethod
def setUpClass(cls):
"""Load schema once for all tests"""
schema_url = "https://json.schemastore.org/claude-code-settings.json"
cls.schema = requests.get(schema_url).json()
def test_project_settings_valid(self):
"""Test project settings.json is valid"""
settings_file = Path('.claude/settings.json')
self.assertTrue(settings_file.exists(), "settings.json not found")
with open(settings_file, 'r') as f:
settings = json.load(f)
# Should not raise ValidationError
validate(instance=settings, schema=self.schema)
def test_settings_has_schema_reference(self):
"""Test settings.json includes $schema field"""
with open('.claude/settings.json', 'r') as f:
settings = json.load(f)
self.assertIn('$schema', settings, "$schema field missing")
self.assertIn('json.schemastore.org', settings['$schema'])
def test_hooks_use_string_matchers(self):
"""Test all hooks use string matchers (not objects)"""
with open('.claude/settings.json', 'r') as f:
settings = json.load(f)
if 'hooks' not in settings:
return # No hooks to test
for hook_type, hooks_list in settings['hooks'].items():
for i, hook_entry in enumerate(hooks_list):
if 'matcher' in hook_entry:
self.assertIsInstance(
hook_entry['matcher'],
str,
f"{hook_type}[{i}] matcher should be string, not object"
)
def test_agent_frontmatter(self):
"""Test all agents have valid frontmatter"""
agents_dir = Path('.claude/agents')
if not agents_dir.exists():
self.skipTest("No agents directory")
for agent_file in agents_dir.glob('*.md'):
with self.subTest(agent=agent_file.name):
with open(agent_file, 'r') as f:
content = f.read()
# Check frontmatter exists
self.assertTrue(
content.startswith('---\n'),
f"{agent_file.name}: Missing frontmatter"
)
# Extract frontmatter
import re
match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
self.assertIsNotNone(match, f"{agent_file.name}: Invalid frontmatter")
frontmatter = match.group(1)
# Check required fields
self.assertRegex(
frontmatter,
r'^name:\s*.+$',
f"{agent_file.name}: Missing name field",
flags=re.MULTILINE
)
self.assertRegex(
frontmatter,
r'^description:\s*.+$',
f"{agent_file.name}: Missing description field",
flags=re.MULTILINE
)
# Check values are unquoted
self.assertNotRegex(
frontmatter,
r'^(name|description):\s*["\']',
f"{agent_file.name}: Values should be unquoted",
flags=re.MULTILINE
)
if __name__ == '__main__':
unittest.main()
Run tests:
# Run all tests
python3 tests/test_claude_config.py
# Run specific test
python3 tests/test_claude_config.py TestClaudeCodeConfig.test_hooks_use_string_matchers
# Verbose output
python3 tests/test_claude_config.py -v
Add $schema field to all settings.json files:
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
...
}
Configure your IDE to use JSON Schema validation:
$schema field# Before commits
/doctor
# In CI/CD
claude /doctor --quiet
Add validation to prevent invalid configs:
#!/bin/bash
python3 .claude/hooks/validate_settings.py || exit 1
python3 .claude/hooks/validate_agents.py || exit 1
Add validation to your CI pipeline:
- name: Validate Claude Config
run: |
python3 tests/test_claude_config.py
Problem: jsonschema validation errors
Solution:
curl https://json.schemastore.org/claude-code-settings.jsonpython3 -m json.tool settings.json/doctor for official validationProblem: No autocomplete or error highlighting
Solution:
$schema field is presentProblem: Agent parse errors in /doctor
Solution:
python3 .claude/hooks/validate_agents.pyname and description fields present---\nfields\n---Last Updated: 2025-11-17 Schema Version: As of September 29, 2025 Applies To: Claude Code 2.0+