Agent skill
writing-elisp
Comprehensive guidance for writing high-quality, idiomatic, modern Emacs Lisp code with incremental validation. Use when writing or modifying elisp functions, especially complex nested forms with cl-loop, multiple let* bindings, or lambdas. Covers modern features, naming conventions, documentation standards, error handling, code quality, and performance.
Install this agent skill to your Project
npx add-skill https://github.com/moogah/claude_skills/tree/main/writing-elisp
SKILL.md
Writing Elisp
Overview
This skill provides comprehensive guidance for writing high-quality Emacs Lisp code, covering:
- Incremental validation - Catch syntax errors early (especially paren mismatches)
- Modern Elisp features - Lexical binding, cl-lib, pcase, threading macros
- Idiomatic patterns - Naming conventions, control flow, looping constructs
- Documentation standards - Docstrings, package structure, autoloading
- Code quality - Linting tools, error handling, anti-patterns
- Performance guidelines - When and how to optimize
LLMs frequently produce parenthesis errors in complex nested elisp - this skill helps prevent those issues through automated validation while also ensuring the code follows modern best practices.
When to Use This Skill
- Before writing any elisp function >20 lines
- When writing complex nested forms (cl-loop, multiple let*, lambdas)
- When modifying existing elisp in literate org files
- After generating elisp code with an LLM
- When working with deeply nested expressions (3+ levels)
Incremental Validation Workflow
The key principle: validate after each function, before moving to the next.
- Write in small chunks - One function at a time, not entire files
- Validate immediately - Run validation after each function
- Use automated tools - Never rely on visual inspection or manual counting
- Test in isolation - Extract to temp file if needed for testing
- Fix before continuing - Don't accumulate errors
Validation Commands
Quick Paren Check (Fastest)
/Applications/Emacs.app/Contents/MacOS/Emacs --batch --eval \
"(progn (find-file \"file.el\") (check-parens))"
Returns immediately with error or success. Use this after every function.
Full Syntax Check with Byte Compiler
/Applications/Emacs.app/Contents/MacOS/Emacs --batch --eval \
"(progn (find-file \"file.el\") (byte-compile-file \"file.el\"))"
Catches more issues but slower. Use before committing.
Scan for Premature Expression Endings
/Applications/Emacs.app/Contents/MacOS/Emacs --batch --eval \
"(with-temp-buffer
(insert-file-contents \"file.el\")
(goto-char (point-min))
(condition-case scan-err
(while (not (eobp)) (forward-sexp 1))
(scan-error
(message \"Scan error at position %d: %s\" (point) scan-err))))"
Shows exact position of paren mismatches. Use when check-parens reports errors.
Complexity Thresholds
Low Risk (2 nesting levels)
(let ((x 1))
(+ x 2))
Action: Validate after 5-10 functions
Medium Risk (3 nesting levels)
(let ((x (foo)))
(when x
(bar x)))
Action: Validate after each 2-3 functions
High Risk (4+ nesting levels)
(cl-loop for i from 1 below (length path)
for target-id = (aref path i)
do
(let ((children (plist-get node :children)))
(when node
(let* ((file (plist-get node :file))
(content (when (file-exists-p full-path)
(with-temp-buffer
(insert-file-contents full-path)
(buffer-string)))))
(push (list :type type :content content) context)))))
Action: Validate after EACH high-risk function Consider: Breaking into smaller helper functions
Integration with Literate Programming
When working with org-mode tangled files:
After Writing Elisp Block
#+begin_src emacs-lisp
(defun my-complex-function (arg1 arg2)
"Documentation..."
(let ((result (calculate arg1)))
(when result
(process result arg2))))
#+end_src
Immediate Validation Step
./bin/tangle-org.sh file.org && \
/Applications/Emacs.app/Contents/MacOS/Emacs --batch --eval \
"(progn (find-file \"file.el\") (check-parens))"
Add this as a habit after each significant elisp block.
Example Workflow
User Request: "Add a function to load context from a tree path"
Agent Response:
- Write function in org-mode code block
- Tangle to .el file:
./bin/tangle-org.sh file.org - Validate: Run check-parens on tangled .el file
- If errors: Fix in .org file and repeat steps 2-3
- If clean: Continue to next function
- Before committing: Run byte-compile check on full file
Common LLM Failure Patterns
LLMs frequently produce paren errors in:
- cl-loop with nested let* and lambdas (like example above)
- Functions >50 lines
- Complex backquote/unquote expressions
- Multiple nested when/if/cond forms
Prevention Strategy:
- Write these in 10-20 line chunks
- Validate after each chunk
- Use helper functions to reduce nesting
Real-World Example
From actual debugging session where LLM produced:
(push (list :type type :content content) context))))))
^^^^^^
6 parens - should be 5!
The function had 4 nesting levels (cl-loop, let, when, let*). The extra paren was caught by check-parens immediately after tangling, not after committing or restarting Emacs.
Integration with Claude Code Hooks
If using Claude Code with file-write hooks, the validation can be automated:
# In .claude/hooks/validate_elisp.py
import subprocess
import sys
def validate_elisp(file_path):
if not file_path.endswith('.el'):
return True
result = subprocess.run([
'/Applications/Emacs.app/Contents/MacOS/Emacs',
'--batch',
'--eval',
f'(progn (find-file "{file_path}") (check-parens))'
], capture_output=True, text=True)
if result.returncode != 0:
print(f"Elisp validation failed: {result.stderr}", file=sys.stderr)
return False
return True
Tips
- Never skip validation - "It looks right" is not reliable for elisp
- Start simple - Write trivial version first, then add complexity
- Test incrementally - Don't write 100+ lines before first validation
- Use git - Commit working code frequently so you have rollback points
- Break down complexity - Helper functions are cheaper than debugging
Extended Guidance
This skill includes comprehensive reference guides for different aspects of Elisp development:
references/modern-features.md
Covers modern Elisp features and requirements:
- Lexical binding (mandatory header)
- cl-lib vs deprecated cl package
- pcase for pattern matching
- Named let for recursive helpers
- Threading macros (dash.el)
- Modern hook and advice patterns
Consult when: Starting new code, updating old code, or whenever writing functions with keyword arguments, pattern matching, or recursive logic.
references/idioms.md
Covers naming conventions and idiomatic patterns:
- Naming conventions (predicates, internal functions, package prefixes)
- Control flow idioms (when/if/unless/cond/pcase)
- Looping constructs (dolist/dotimes/cl-loop/mapcar)
- Common pitfalls (push/nreverse, string building)
Consult when: Writing any Elisp code to ensure it follows community conventions and best practices.
references/documentation.md
Covers documentation and package structure:
- High-quality docstrings with examples
- Package headers (Author, Version, Package-Requires)
- Autoloading for lazy loading
- Customization groups (defgroup/defcustom)
- Forward declarations
Consult when: Creating packages, writing public APIs, or documenting complex functions.
references/quality.md
Covers code quality and error handling:
- Linting tools (checkdoc, package-lint, elisp-lint)
- Error handling (condition-case, ignore-errors, unwind-protect)
- Custom error types (define-error)
- Common anti-patterns and code smells
Consult when: Before committing code, when handling errors, or when reviewing/refactoring existing code.
references/performance.md
Covers optimization guidelines:
- When and how to profile (built-in profiler, benchmarking)
- Common performance issues (O(n²) operations, repeated calls)
- Data structure choices (lists vs vectors vs hash tables)
- Caching patterns (memoization, buffer-local caches)
- Lazy loading strategies
Consult when: Code feels slow, operations take >100ms, or when working with large datasets.
Quick Reference Decision Tree
Writing Elisp code?
├─ New file? → Check references/modern-features.md for lexical-binding header
├─ Naming function? → Check references/idioms.md for conventions
├─ Complex conditional? → Check references/idioms.md for pcase examples
├─ Need documentation? → Check references/documentation.md for docstring format
├─ Handling errors? → Check references/quality.md for error handling patterns
├─ Code feels slow? → Check references/performance.md for profiling guide
└─ Ready to commit? → Run validation commands + linting (quality.md)
References
For debugging when validation fails, see the emacs-elisp-debugging skill which covers:
- Git-based debugging strategies
- Automated validation tools (Flycheck, flyparens)
- Error pattern recognition
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
characterization-testing
Guide for characterization testing - capturing current system behavior to enable safe refactoring. Use when working with legacy code, undocumented systems, or code that needs modification but lacks tests. Helps create a safety net before making changes by testing what the code actually does (not what it should do).
bead-implementation
Execute on a Beads issue with proper discovery workflow and mandatory session close protocol. Use when starting work on a bead, during implementation when discovering new work that should be externalized, or when completing a session to ensure all work is properly committed.
architecture-analyzer
Perform in-depth architectural analysis of codebases through iterative dialog and visual diagrams. Use when users want to understand their software architecture, explore design patterns and anti-patterns, evaluate potential architectural changes, or gain deep insights into code structure and relationships. Particularly valuable for onboarding to unfamiliar codebases, assessing technical debt, planning refactoring efforts, or understanding how components interact across system boundaries.
openspec-to-beads
Convert OpenSpec artifacts (proposal.md, design.md, tasks.md, specs/) into self-contained Beads issues for implementation tracking. Use when you have completed OpenSpec planning artifacts and need to create trackable implementation tasks, or when converting any specification or design document into actionable Beads issues.
github-actions-troubleshooting
Troubleshoot and fix failing GitHub Actions pipeline steps. Use when user reports a failing pipeline with error output.
skill-learner
Reviews conversation context to identify learnings and patterns that should be captured in skills. Use when the user asks to review the session for skill creation or updates.
Didn't find tool you were looking for?