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