Generated banner for Plugin Settings
Generated logo for Plugin Settings

Plugin Settings

AnthropicOfficialPublished by Anthropic

  • Skill
  • Guidelines
  • Agent tooling
  • Apache-2.0
  • Low risk
  • Has scripts

From the author

This skill should be used when the user asks about "plugin settings", "store plugin configuration", "user-configurable plugin", ".local.md files", "plugin state files", "read YAML frontmatter", "per-project plugin settings", or wants to make plugin behavior configurable. Documents the .claude/plugin-name.local.md pattern for storing plugin-specific configuration with YAML frontmatter and markdown content.
— the description by Anthropic

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill defines a standardized pattern for managing plugin-specific settings and state within a project. By utilizing .claude/plugin-name.local.md files, developers can store structured configuration in YAML frontmatter and descriptive content in the markdown body. This approach allows plugins to be highly configurable on a per-project basis while keeping settings out of version control. The skill provides best practices for parsing these files in bash hooks, managing agent state, and ensuring secure, reliable configuration handling.

What it does

  • Defines the .claude/plugin-name.local.md file structure for plugin configuration.
  • Provides patterns for parsing YAML frontmatter and markdown content in bash scripts.
  • Establishes a quick-exit pattern for hooks to skip inactive plugins.
  • Offers guidance on creating, updating, and validating settings files.
  • Includes security best practices for handling user-provided configuration.
  • Suggests standard gitignore entries to prevent accidental commits of local settings.

When to use it

  • When you need to make a plugin's behavior configurable per project.
  • When you need to store persistent state for an agent or plugin session.
  • When you want to provide users with a simple way to toggle plugin features.
  • When you need to pass complex instructions or prompts to an agent via a file.
  • When you are developing custom hooks or commands that require external configuration.

Advantages

  • Keeps configuration local to the project and excluded from version control.
  • Uses human-readable markdown and YAML for easy manual editing.
  • Enables dynamic behavior changes without modifying core plugin code.
  • Provides a consistent structure across different plugins.
  • Supports both structured data (YAML) and unstructured content (markdown).

Usage examples

“How do I make my plugin configurable for users?”
The agent explains the .claude/plugin-name.local.md pattern, providing a template and instructions on how to parse the YAML frontmatter within bash hooks.
“How can I read settings from a .local.md file in my bash hook?”
The agent provides a code snippet using sed to extract the frontmatter block and grep/sed to parse specific fields like enabled status or numeric thresholds.
“What is the best way to store agent state between tasks?”
The agent demonstrates using a .local.md file to store task numbers, agent names, and dependencies, showing how to update these values programmatically.
“How do I ensure my plugin settings are not committed to git?”
The agent advises adding .claude/*.local.md to the project's .gitignore file and explains why this is a best practice for local configuration.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/anthropics/plugin-settings/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip plugin-settings.zip -d .agents/skills

Documentation by Anthropic

Plugin Settings Pattern for Claude Code Plugins

Overview

Plugins can store user-configurable settings and state in .claude/plugin-name.local.md files within the project directory. This pattern uses YAML frontmatter for structured configuration and markdown content for prompts or additional context.

Key characteristics:

  • File location: .claude/plugin-name.local.md in project root

  • Structure: YAML frontmatter + markdown body

  • Purpose: Per-project plugin configuration and state

  • Usage: Read from hooks, commands, and agents

  • Lifecycle: User-managed (not in git, should be in .gitignore)

File Structure

Basic Template
---
enabled: true
setting1: value1
setting2: value2
numeric_setting: 42
list_setting: ["item1", "item2"]
---

# Additional Context

This markdown body can contain:
- Task descriptions
- Additional instructions
- Prompts to feed back to Claude
- Documentation or notes
Example: Plugin State File

.claude/my-plugin.local.md:

---
enabled: true
strict_mode: false
max_retries: 3
notification_level: info
coordinator_session: team-leader
---

# Plugin Configuration

This plugin is configured for standard validation mode.
Contact @team-lead with questions.

Reading Settings Files

From Hooks (Bash Scripts)

Pattern: Check existence and parse frontmatter

#!/bin/bash
set -euo pipefail

# Define state file path
STATE_FILE=".claude/my-plugin.local.md"

# Quick exit if file doesn't exist
if [[ ! -f "$STATE_FILE" ]]; then
  exit 0  # Plugin not configured, skip
fi

# Parse YAML frontmatter (between --- markers)
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE")

# Extract individual fields
ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//' | sed 's/^"\(.*\)"$/\1/')
STRICT_MODE=$(echo "$FRONTMATTER" | grep '^strict_mode:' | sed 's/strict_mode: *//' | sed 's/^"\(.*\)"$/\1/')

# Check if enabled
if [[ "$ENABLED" != "true" ]]; then
  exit 0  # Disabled
fi

# Use configuration in hook logic
if [[ "$STRICT_MODE" == "true" ]]; then
  # Apply strict validation
  # ...
fi

See examples/read-settings-hook.sh for complete working example.

From Commands

Commands can read settings files to customize behavior:

---
description: Process data with plugin
allowed-tools: ["Read", "Bash"]
---

# Process Command

Steps:
1. Check if settings exist at `.claude/my-plugin.local.md`
2. Read configuration using Read tool
3. Parse YAML frontmatter to extract settings
4. Apply settings to processing logic
5. Execute with configured behavior
From Agents

Agents can reference settings in their instructions:

---
name: configured-agent
description: Agent that adapts to project settings
---

Check for plugin settings at `.claude/my-plugin.local.md`.
If present, parse YAML frontmatter and adapt behavior according to:
- enabled: Whether plugin is active
- mode: Processing mode (strict, standard, lenient)
- Additional configuration fields

Parsing Techniques

Extract Frontmatter
# Extract everything between --- markers
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")
Read Individual Fields

String fields:

VALUE=$(echo "$FRONTMATTER" | grep '^field_name:' | sed 's/field_name: *//' | sed 's/^"\(.*\)"$/\1/')

Boolean fields:

ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
# Compare: if [[ "$ENABLED" == "true" ]]; then

Numeric fields:

MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//')
# Use: if [[ $MAX -gt 100 ]]; then
Read Markdown Body

Extract content after second ---:

# Get everything after closing ---
BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE")

Common Patterns

Pattern 1: Temporarily Active Hooks

Use settings file to control hook activation:

#!/bin/bash
STATE_FILE=".claude/security-scan.local.md"

# Quick exit if not configured
if [[ ! -f "$STATE_FILE" ]]; then
  exit 0
fi

# Read enabled flag
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE")
ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')

if [[ "$ENABLED" != "true" ]]; then
  exit 0  # Disabled
fi

# Run hook logic
# ...

Use case: Enable/disable hooks without editing hooks.json (requires restart).

Pattern 2: Agent State Management

Store agent-specific state and configuration:

.claude/multi-agent-swarm.local.md:

---
agent_name: auth-agent
task_number: 3.5
pr_number: 1234
coordinator_session: team-leader
enabled: true
dependencies: ["Task 3.4"]
---

# Task Assignment

Implement JWT authentication for the API.

**Success Criteria:**
- Authentication endpoints created
- Tests passing
- PR created and CI green

Read from hooks to coordinate agents:

AGENT_NAME=$(echo "$FRONTMATTER" | grep '^agent_name:' | sed 's/agent_name: *//')
COORDINATOR=$(echo "$FRONTMATTER" | grep '^coordinator_session:' | sed 's/coordinator_session: *//')

# Send notification to coordinator
tmux send-keys -t "$COORDINATOR" "Agent $AGENT_NAME completed task" Enter
Pattern 3: Configuration-Driven Behavior

.claude/my-plugin.local.md:

---
validation_level: strict
max_file_size: 1000000
allowed_extensions: [".js", ".ts", ".tsx"]
enable_logging: true
---

# Validation Configuration

Strict mode enabled for this project.
All writes validated against security policies.

Use in hooks or commands:

LEVEL=$(echo "$FRONTMATTER" | grep '^validation_level:' | sed 's/validation_level: *//')

case "$LEVEL" in
  strict)
    # Apply strict validation
    ;;
  standard)
    # Apply standard validation
    ;;
  lenient)
    # Apply lenient validation
    ;;
esac

Creating Settings Files

From Commands

Commands can create settings files:

# Setup Command

Steps:
1. Ask user for configuration preferences
2. Create `.claude/my-plugin.local.md` with YAML frontmatter
3. Set appropriate values based on user input
4. Inform user that settings are saved
5. Remind user to restart Claude Code for hooks to recognize changes
Template Generation

Provide template in plugin README:

## Configuration

Create `.claude/my-plugin.local.md` in your project:

\`\`\`markdown
---
enabled: true
mode: standard
max_retries: 3
---

# Plugin Configuration

Your settings are active.
\`\`\`

After creating or editing, restart Claude Code for changes to take effect.

Best Practices

File Naming

✅ DO:

  • Use .claude/plugin-name.local.md format

  • Match plugin name exactly

  • Use .local.md suffix for user-local files

❌ DON'T:

  • Use different directory (not .claude/)

  • Use inconsistent naming

  • Use .md without .local (might be committed)

Gitignore

Always add to .gitignore:

.claude/*.local.md
.claude/*.local.json

Document this in plugin README.

Defaults

Provide sensible defaults when settings file doesn't exist:

if [[ ! -f "$STATE_FILE" ]]; then
  # Use defaults
  ENABLED=true
  MODE=standard
else
  # Read from file
  # ...
fi
Validation

Validate settings values:

MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//')

# Validate numeric range
if ! [[ "$MAX" =~ ^[0-9]+$ ]] || [[ $MAX -lt 1 ]] || [[ $MAX -gt 100 ]]; then
  echo "⚠️  Invalid max_value in settings (must be 1-100)" >&2
  MAX=10  # Use default
fi
Restart Requirement

Important: Settings changes require Claude Code restart.

Document in your README:

## Changing Settings

After editing `.claude/my-plugin.local.md`:
1. Save the file
2. Exit Claude Code
3. Restart: `claude` or `cc`
4. New settings will be loaded

Hooks cannot be hot-swapped within a session.

Security Considerations

Sanitize User Input

When writing settings files from user input:

# Escape quotes in user input
SAFE_VALUE=$(echo "$USER_INPUT" | sed 's/"/\\"/g')

# Write to file
cat > "$STATE_FILE" <<EOF
---
user_setting: "$SAFE_VALUE"
---
EOF
Validate File Paths

If settings contain file paths:

FILE_PATH=$(echo "$FRONTMATTER" | grep '^data_file:' | sed 's/data_file: *//')

# Check for path traversal
if [[ "$FILE_PATH" == *".."* ]]; then
  echo "⚠️  Invalid path in settings (path traversal)" >&2
  exit 2
fi
Permissions

Settings files should be:

  • Readable by user only (chmod 600)

  • Not committed to git

  • Not shared between users

Real-World Examples

multi-agent-swarm Plugin

.claude/multi-agent-swarm.local.md:

---
agent_name: auth-implementation
task_number: 3.5
pr_number: 1234
coordinator_session: team-leader
enabled: true
dependencies: ["Task 3.4"]
additional_instructions: Use JWT tokens, not sessions
---

# Task: Implement Authentication

Build JWT-based authentication for the REST API.
Coordinate with auth-agent on shared types.

Hook usage (agent-stop-notification.sh):

  • Checks if file exists (line 15-18: quick exit if not)

  • Parses frontmatter to get coordinator_session, agent_name, enabled

  • Sends notifications to coordinator if enabled

  • Allows quick activation/deactivation via enabled: true/false

ralph-loop Plugin

.claude/ralph-loop.local.md:

---
iteration: 1
max_iterations: 10
completion_promise: "All tests passing and build successful"
---

Fix all the linting errors in the project.
Make sure tests pass after each fix.

Hook usage (stop-hook.sh):

  • Checks if file exists (line 15-18: quick exit if not active)

  • Reads iteration count and max_iterations

  • Extracts completion_promise for loop termination

  • Reads body as the prompt to feed back

  • Updates iteration count on each loop

Quick Reference

File Location
project-root/
└── .claude/
    └── plugin-name.local.md
Frontmatter Parsing
# Extract frontmatter
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")

# Read field
VALUE=$(echo "$FRONTMATTER" | grep '^field:' | sed 's/field: *//' | sed 's/^"\(.*\)"$/\1/')
Body Parsing
# Extract body (after second ---)
BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE")
Quick Exit Pattern
if [[ ! -f ".claude/my-plugin.local.md" ]]; then
  exit 0  # Not configured
fi

Additional Resources

Reference Files

For detailed implementation patterns:

  • references/parsing-techniques.md - Complete guide to parsing YAML frontmatter and markdown bodies

  • references/real-world-examples.md - Deep dive into multi-agent-swarm and ralph-loop implementations

Example Files

Working examples in examples/:

  • read-settings-hook.sh - Hook that reads and uses settings

  • create-settings-command.md - Command that creates settings file

  • example-settings.md - Template settings file

Utility Scripts

Development tools in scripts/:

  • validate-settings.sh - Validate settings file structure

  • parse-frontmatter.sh - Extract frontmatter fields

Implementation Workflow

To add settings to a plugin:

  1. Design settings schema (which fields, types, defaults)

  2. Create template file in plugin documentation

  3. Add gitignore entry for .claude/*.local.md

  4. Implement settings parsing in hooks/commands

  5. Use quick-exit pattern (check file exists, check enabled field)

  6. Document settings in plugin README with template

  7. Remind users that changes require Claude Code restart

Focus on keeping settings simple and providing good defaults when settings file doesn't exist.

Files

references/parsing-techniques.md

Settings File Parsing Techniques

Complete guide to parsing .claude/plugin-name.local.md files in bash scripts.

File Structure

Settings files use markdown with YAML frontmatter:

---
field1: value1
field2: "value with spaces"
numeric_field: 42
boolean_field: true
list_field: ["item1", "item2", "item3"]
---

# Markdown Content

This body content can be extracted separately.
It's useful for prompts, documentation, or additional context.

Parsing Frontmatter

Extract Frontmatter Block
#!/bin/bash
FILE=".claude/my-plugin.local.md"

# Extract everything between --- markers (excluding the markers themselves)
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")

How it works:

  • sed -n - Suppress automatic printing

  • /^---$/,/^---$/ - Range from first --- to second ---

  • { /^---$/d; p; } - Delete the --- lines, print everything else

Extract Individual Fields

String fields:

# Simple value
VALUE=$(echo "$FRONTMATTER" | grep '^field_name:' | sed 's/field_name: *//')

# Quoted value (removes surrounding quotes)
VALUE=$(echo "$FRONTMATTER" | grep '^field_name:' | sed 's/field_name: *//' | sed 's/^"\(.*\)"$/\1/')

Boolean fields:

ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')

# Use in condition
if [[ "$ENABLED" == "true" ]]; then
  # Enabled
fi

Numeric fields:

MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//')

# Validate it's a number
if [[ "$MAX" =~ ^[0-9]+$ ]]; then
  # Use in numeric comparison
  if [[ $MAX -gt 100 ]]; then
    # Too large
  fi
fi

List fields (simple):

# YAML: list: ["item1", "item2", "item3"]
LIST=$(echo "$FRONTMATTER" | grep '^list:' | sed 's/list: *//')
# Result: ["item1", "item2", "item3"]

# For simple checks:
if [[ "$LIST" == *"item1"* ]]; then
  # List contains item1
fi

List fields (proper parsing with jq):

# For proper list handling, use yq or convert to JSON
# This requires yq to be installed (brew install yq)

# Extract list as JSON array
LIST=$(echo "$FRONTMATTER" | yq -o json '.list' 2>/dev/null)

# Iterate over items
echo "$LIST" | jq -r '.[]' | while read -r item; do
  echo "Processing: $item"
done

Parsing Markdown Body

Extract Body Content
#!/bin/bash
FILE=".claude/my-plugin.local.md"

# Extract everything after the closing ---
# Counts --- markers: first is opening, second is closing, everything after is body
BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE")

How it works:

  • /^---$/ - Match --- lines

  • {i++; next} - Increment counter and skip the --- line

  • i>=2 - Print all lines after second ---

Handles edge case: If --- appears in the markdown body, it still works because we only count the first two --- at the start.

Use Body as Prompt
# Extract body
PROMPT=$(awk '/^---$/{i++; next} i>=2' "$RALPH_STATE_FILE")

# Feed back to Claude
echo '{"decision": "block", "reason": "'"$PROMPT"'"}' | jq .

Important: Use jq -n --arg for safer JSON construction with user content:

PROMPT=$(awk '/^---$/{i++; next} i>=2' "$FILE")

# Safe JSON construction
jq -n --arg prompt "$PROMPT" '{
  "decision": "block",
  "reason": $prompt
}'

Common Parsing Patterns

Pattern: Field with Default
VALUE=$(echo "$FRONTMATTER" | grep '^field:' | sed 's/field: *//' | sed 's/^"\(.*\)"$/\1/')

# Use default if empty
if [[ -z "$VALUE" ]]; then
  VALUE="default_value"
fi
Pattern: Optional Field
OPTIONAL=$(echo "$FRONTMATTER" | grep '^optional_field:' | sed 's/optional_field: *//' | sed 's/^"\(.*\)"$/\1/')

# Only use if present
if [[ -n "$OPTIONAL" ]] && [[ "$OPTIONAL" != "null" ]]; then
  # Field is set, use it
  echo "Optional field: $OPTIONAL"
fi
Pattern: Multiple Fields at Once
# Parse all fields in one pass
while IFS=': ' read -r key value; do
  # Remove quotes if present
  value=$(echo "$value" | sed 's/^"\(.*\)"$/\1/')

  case "$key" in
    enabled)
      ENABLED="$value"
      ;;
    mode)
      MODE="$value"
      ;;
    max_size)
      MAX_SIZE="$value"
      ;;
  esac
done <<< "$FRONTMATTER"

Updating Settings Files

Atomic Updates

Always use temp file + atomic move to prevent corruption:

#!/bin/bash
FILE=".claude/my-plugin.local.md"
NEW_VALUE="updated_value"

# Create temp file
TEMP_FILE="${FILE}.tmp.$$"

# Update field using sed
sed "s/^field_name: .*/field_name: $NEW_VALUE/" "$FILE" > "$TEMP_FILE"

# Atomic replace
mv "$TEMP_FILE" "$FILE"
Update Single Field
# Increment iteration counter
CURRENT=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//')
NEXT=$((CURRENT + 1))

# Update file
TEMP_FILE="${FILE}.tmp.$$"
sed "s/^iteration: .*/iteration: $NEXT/" "$FILE" > "$TEMP_FILE"
mv "$TEMP_FILE" "$FILE"
Update Multiple Fields
# Update several fields at once
TEMP_FILE="${FILE}.tmp.$$"

sed -e "s/^iteration: .*/iteration: $NEXT_ITERATION/" \
    -e "s/^pr_number: .*/pr_number: $PR_NUMBER/" \
    -e "s/^status: .*/status: $NEW_STATUS/" \
    "$FILE" > "$TEMP_FILE"

mv "$TEMP_FILE" "$FILE"

Validation Techniques

Validate File Exists and Is Readable
FILE=".claude/my-plugin.local.md"

if [[ ! -f "$FILE" ]]; then
  echo "Settings file not found" >&2
  exit 1
fi

if [[ ! -r "$FILE" ]]; then
  echo "Settings file not readable" >&2
  exit 1
fi
Validate Frontmatter Structure
# Count --- markers (should be exactly 2 at start)
MARKER_COUNT=$(grep -c '^---$' "$FILE" 2>/dev/null || echo "0")

if [[ $MARKER_COUNT -lt 2 ]]; then
  echo "Invalid settings file: missing frontmatter markers" >&2
  exit 1
fi
Validate Field Values
MODE=$(echo "$FRONTMATTER" | grep '^mode:' | sed 's/mode: *//')

case "$MODE" in
  strict|standard|lenient)
    # Valid mode
    ;;
  *)
    echo "Invalid mode: $MODE (must be strict, standard, or lenient)" >&2
    exit 1
    ;;
esac
Validate Numeric Ranges
MAX_SIZE=$(echo "$FRONTMATTER" | grep '^max_size:' | sed 's/max_size: *//')

if ! [[ "$MAX_SIZE" =~ ^[0-9]+$ ]]; then
  echo "max_size must be a number" >&2
  exit 1
fi

if [[ $MAX_SIZE -lt 1 ]] || [[ $MAX_SIZE -gt 10000000 ]]; then
  echo "max_size out of range (1-10000000)" >&2
  exit 1
fi

Edge Cases and Gotchas

Quotes in Values

YAML allows both quoted and unquoted strings:

# These are equivalent:
field1: value
field2: "value"
field3: 'value'

Handle both:

# Remove surrounding quotes if present
VALUE=$(echo "$FRONTMATTER" | grep '^field:' | sed 's/field: *//' | sed 's/^"\(.*\)"$/\1/' | sed "s/^'\\(.*\\)'$/\\1/")
--- in Markdown Body

If the markdown body contains ---, the parsing still works because we only match the first two:

---
field: value
---

# Body

Here's a separator:
---

More content after the separator.

The awk '/^---$/{i++; next} i>=2' pattern handles this correctly.

Empty Values

Handle missing or empty fields:

field1:
field2: ""
field3: null

Parsing:

VALUE=$(echo "$FRONTMATTER" | grep '^field1:' | sed 's/field1: *//')
# VALUE will be empty string

# Check for empty/null
if [[ -z "$VALUE" ]] || [[ "$VALUE" == "null" ]]; then
  VALUE="default"
fi
Special Characters

Values with special characters need careful handling:

message: "Error: Something went wrong!"
path: "/path/with spaces/file.txt"
regex: "^[a-zA-Z0-9_]+$"

Safe parsing:

# Always quote variables when using
MESSAGE=$(echo "$FRONTMATTER" | grep '^message:' | sed 's/message: *//' | sed 's/^"\(.*\)"$/\1/')

echo "Message: $MESSAGE"  # Quoted!

Performance Optimization

Cache Parsed Values

If reading settings multiple times:

# Parse once
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")

# Extract multiple fields from cached frontmatter
FIELD1=$(echo "$FRONTMATTER" | grep '^field1:' | sed 's/field1: *//')
FIELD2=$(echo "$FRONTMATTER" | grep '^field2:' | sed 's/field2: *//')
FIELD3=$(echo "$FRONTMATTER" | grep '^field3:' | sed 's/field3: *//')

Don't: Re-parse file for each field.

Lazy Loading

Only parse settings when needed:

#!/bin/bash
input=$(cat)

# Quick checks first (no file I/O)
tool_name=$(echo "$input" | jq -r '.tool_name')
if [[ "$tool_name" != "Write" ]]; then
  exit 0  # Not a write operation, skip
fi

# Only now check settings file
if [[ -f ".claude/my-plugin.local.md" ]]; then
  # Parse settings
  # ...
fi

Debugging

Print Parsed Values
#!/bin/bash
set -x  # Enable debug tracing

FILE=".claude/my-plugin.local.md"

if [[ -f "$FILE" ]]; then
  echo "Settings file found" >&2

  FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")
  echo "Frontmatter:" >&2
  echo "$FRONTMATTER" >&2

  ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
  echo "Enabled: $ENABLED" >&2
fi
Validate Parsing
# Show what was parsed
echo "Parsed values:" >&2
echo "  enabled: $ENABLED" >&2
echo "  mode: $MODE" >&2
echo "  max_size: $MAX_SIZE" >&2

# Verify expected values
if [[ "$ENABLED" != "true" ]] && [[ "$ENABLED" != "false" ]]; then
  echo "⚠️  Unexpected enabled value: $ENABLED" >&2
fi

Alternative: Using yq

For complex YAML, consider using yq:

# Install: brew install yq

# Parse YAML properly
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")

# Extract fields with yq
ENABLED=$(echo "$FRONTMATTER" | yq '.enabled')
MODE=$(echo "$FRONTMATTER" | yq '.mode')
LIST=$(echo "$FRONTMATTER" | yq -o json '.list_field')

# Iterate list properly
echo "$LIST" | jq -r '.[]' | while read -r item; do
  echo "Item: $item"
done

Pros:

  • Proper YAML parsing

  • Handles complex structures

  • Better list/object support

Cons:

  • Requires yq installation

  • Additional dependency

  • May not be available on all systems

Recommendation: Use sed/grep for simple fields, yq for complex structures.

Complete Example

#!/bin/bash
set -euo pipefail

# Configuration
SETTINGS_FILE=".claude/my-plugin.local.md"

# Quick exit if not configured
if [[ ! -f "$SETTINGS_FILE" ]]; then
  # Use defaults
  ENABLED=true
  MODE=standard
  MAX_SIZE=1000000
else
  # Parse frontmatter
  FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$SETTINGS_FILE")

  # Extract fields with defaults
  ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
  ENABLED=${ENABLED:-true}

  MODE=$(echo "$FRONTMATTER" | grep '^mode:' | sed 's/mode: *//' | sed 's/^"\(.*\)"$/\1/')
  MODE=${MODE:-standard}

  MAX_SIZE=$(echo "$FRONTMATTER" | grep '^max_size:' | sed 's/max_size: *//')
  MAX_SIZE=${MAX_SIZE:-1000000}

  # Validate values
  if [[ "$ENABLED" != "true" ]] && [[ "$ENABLED" != "false" ]]; then
    echo "⚠️  Invalid enabled value, using default" >&2
    ENABLED=true
  fi

  if ! [[ "$MAX_SIZE" =~ ^[0-9]+$ ]]; then
    echo "⚠️  Invalid max_size, using default" >&2
    MAX_SIZE=1000000
  fi
fi

# Quick exit if disabled
if [[ "$ENABLED" != "true" ]]; then
  exit 0
fi

# Use configuration
echo "Configuration loaded: mode=$MODE, max_size=$MAX_SIZE" >&2

# Apply logic based on settings
case "$MODE" in
  strict)
    # Strict validation
    ;;
  standard)
    # Standard validation
    ;;
  lenient)
    # Lenient validation
    ;;
esac

This provides robust settings handling with defaults, validation, and error recovery.

Requirements and compatibility

Written for: Claude Code, Claude apps

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

What our scanner noticed· Low risk

  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
Anthropic
Publisher
Anthropic (Official)
Source
anthropics/claude-plugins-official · plugins/plugin-dev/skills/plugin-settings · commit 0412be7
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (added by AutoGineer from plugins/plugin-dev/LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Anthropic or Anthropic.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download plugin-settings.zip