blender-agents-code-validator
Systematic validation checklist for reviewing Blender Python code. Run this checklist against any Blender Python code to identify errors, deprecations, and anti-patterns.
Works with
Agent Skills format with YAML frontmatter. Claude Code reads it as-is.
---
name: "blender-agents-code-validator"
description: "Systematic validation checklist for reviewing Blender Python code. Run this checklist against any Blender Python code to identify errors, deprecations, and anti-patterns."
license: "MIT"
---
# Blender Code Validator Agent
Systematic validation checklist for reviewing Blender Python code. Run this checklist against any Blender Python code to identify errors, deprecations, and anti-patterns.
## Quick Reference: When to Activate
Activate this validator when:
- Reviewing, auditing, or validating Blender Python scripts
- Checking addon/extension code before distribution
- Migrating code between Blender versions (3.x → 4.x → 5.x)
- Investigating runtime crashes, context errors, or stale references
- Generating new Blender Python code (run validator on output)
## Validation Checklist
Run each check in order. Each check has a severity level:
- **BLOCKER**: Code will crash, produce data corruption, or cause undefined behavior
- **WARNING**: Code has a bug, performance issue, or deprecated usage that will break in a future version
- **INFO**: Code works but does not follow best practices
---
### CHECK 1: Determine Target Version
**Severity if missing: WARNING**
Every Blender Python file MUST declare or imply a target Blender version. Without this, validation cannot determine which APIs are valid.
**Detection patterns:**
1. Look for `bpy.app.version` checks
2. Look for `bl_info["blender"]` tuple in addon code
3. Look for `blender_manifest.toml` in extension code (implies 4.2+)
4. If no version indicator exists → flag WARNING: "No target Blender version declared"
**Decision tree:**
```
Has bl_info with "blender" key? → Target = bl_info["blender"] tuple
Has blender_manifest.toml? → Target = 4.2+
Has bpy.app.version checks? → Target = checked range
None of the above? → WARNING: Undeclared target version
```
---
### CHECK 2: Context Safety
**Severity: BLOCKER**
Scan for context-related errors — the #1 source of Blender Python crashes.
#### 2.1 Restricted Context Access
Flag BLOCKER if code calls `bpy.ops.*` or modifies `bpy.data.*` inside:
- `draw()` method of any Panel, Header, Menu, UIList, or Gizmo
- Application handler callbacks (frame_change_pre/post, depsgraph_update_post, load_post, render_*)
- Timer callbacks registered via `bpy.app.timers.register()`
- Draw callbacks registered via `SpaceView3D.draw_handler_add()`
- Any background thread (threading.Thread, concurrent.futures)
**Detection**: Identify all function definitions. Check parent class. If parent inherits from `bpy.types.Panel/Header/Menu/UIList/Gizmo`, flag any `bpy.ops` call or data mutation inside `draw()`.
#### 2.2 Operator poll() Not Checked
Flag WARNING if code calls `bpy.ops.*` without verifying prerequisites:
- `bpy.ops.object.mode_set(mode='EDIT')` without checking `context.active_object is not None`
- `bpy.ops.mesh.*` without verifying `context.mode == 'EDIT_MESH'`
- `bpy.ops.object.modifier_apply()` without checking object mode and modifier existence
- Any `bpy.ops.view3d.*` without verifying area type is `'VIEW_3D'`
#### 2.3 Context Override Errors (Version-Critical)
Flag BLOCKER if code uses dict-style context overrides on Blender 4.0+:
```python
# BLOCKER on 4.0+: Dict overrides removed
override = bpy.context.copy()
bpy.ops.something(override, ...)
```
Flag WARNING if code uses dict overrides without version gating (no `bpy.app.version` check).
**Version matrix:**
| Blender Version | Dict Override | temp_override |
|----------------|---------------|---------------|
| 3.0 – 3.1 | YES | NO |
| 3.2 – 3.6 | YES (deprecated) | YES |
| 4.0+ | REMOVED | YES |
---
### CHECK 3: API Correctness
**Severity: BLOCKER for removed APIs, WARNING for deprecated APIs**
#### 3.1 Removed API Usage
Scan for APIs removed in each version. Flag BLOCKER if used without version guard:
**Removed in 4.0:**
- `mesh.calc_normals()` → normals are auto-calculated
- `bone.layers` → use `bone.collections`
- `armature.bone_groups` → removed
- `obj.face_maps` → use integer face attributes
- `MeshEdge.bevel_weight` → use `mesh.attributes` with float layer
- `MeshEdge.crease` → use `mesh.attributes` with float layer
- `NodeTree.inputs.new()` / `NodeTree.outputs.new()` → use `NodeTree.interface.new_socket()`
- Dict-style context overrides → use `context.temp_override()`
**Removed in 5.0:**
- `bgl` module → use `gpu` module
- `del obj["custom_prop"]` for RNA properties → use `obj.property_unset("prop")`
- `scene["cycles"]` dict access → use `scene.cycles` attribute access
- `image.bindcode` → use `gpu.texture.from_image()`
**Removed in 5.1:**
- `Sequence.frame_final_start` → use `Sequence.frame_start`
- `Sequence.frame_final_end` → use `Sequence.frame_end`
#### 3.2 Deprecated API Usage
Flag WARNING for deprecated APIs with known removal schedule:
- `bpy.utils.register_module(__name__)` → removed in 2.80, register classes individually
- `obj.select = True` → use `obj.select_set(True)` (removed in 2.80)
- `bpy.data.lamps` → use `bpy.data.lights` (removed in 2.80)
- `mesh.uv_textures` → use `mesh.uv_layers` (removed in 2.80)
- `bl_info` dict in addon → use `blender_manifest.toml` for Blender 4.2+ extensions
#### 3.3 Incorrect EEVEE Identifier
Flag BLOCKER if EEVEE identifier does not match target version:
| Version | Correct Identifier |
|---------|-------------------|
| 3.x | `'BLENDER_EEVEE'` |
| 4.2–4.x | `'BLENDER_EEVEE_NEXT'` |
| 5.0+ | `'BLENDER_EEVEE'` |
---
### CHECK 4: Data Safety
**Severity: BLOCKER**
#### 4.1 Stale bpy.data References
Flag BLOCKER if code stores `bpy.data.*` references and uses them after:
- Any `bpy.ops.*` call (operators can trigger undo)
- Any `bpy.data.*.remove()` call
- Any `bpy.ops.wm.open_mainfile()` / `bpy.ops.wm.read_homefile()`
- Mode switches via `bpy.ops.object.mode_set()`
- Collection property `.add()` / `.remove()` (re-allocates C arrays)
**Detection**: Track variable assignments from `bpy.data.*`. If the variable is used after any of the above operations without re-fetching → BLOCKER.
#### 4.2 Name Collision on Data Creation
Flag WARNING if code does:
```python
bpy.data.meshes.new("Name")
mesh = bpy.data.meshes["Name"] # May be "Name.001"
```
Correct pattern: `mesh = bpy.data.meshes.new("Name")` — capture the return value.
#### 4.3 Missing Object-Collection Link
Flag BLOCKER if code creates a data block and object but never calls `collection.objects.link(obj)`:
```python
mesh = bpy.data.meshes.new("Mesh")
obj = bpy.data.objects.new("Object", mesh)
# MISSING: bpy.context.collection.objects.link(obj)
```
#### 4.4 BMesh Lifecycle Errors
Flag BLOCKER if:
- `bm.verts[i]` accessed without prior `bm.verts.ensure_lookup_table()` call
- `bmesh.new()` used without corresponding `bm.free()` (except edit-mode BMesh from `bmesh.from_edit_mesh()`)
- `bm.to_mesh(mesh)` called without `mesh.update()` afterward
#### 4.5 Missing mesh.update() After from_pydata()
Flag BLOCKER if `mesh.from_pydata()` is called without `mesh.update()` afterward.
---
### CHECK 5: Threading Safety
**Severity: BLOCKER**
#### 5.1 bpy Access from Background Threads
Flag BLOCKER if any `bpy.*` call occurs inside:
- `threading.Thread` target functions
- `concurrent.futures.ThreadPoolExecutor` / `ProcessPoolExecutor` submitted callables
- Any function passed to thread creation
**Correct pattern**: Use `queue.Queue` + `bpy.app.timers.register()` to relay results to main thread.
#### 5.2 Unsafe Timer Callbacks
Flag WARNING if a timer callback performs heavy computation (loops over large data sets, file I/O). Timers run on the main thread and block the UI.
---
### CHECK 6: Addon/Extension Structure
**Severity: WARNING for structural issues, BLOCKER for registration errors**
#### 6.1 Registration Order
Flag BLOCKER if `register()` function:
- Registers operators/panels before their dependent PropertyGroup classes
- Creates PointerProperty referencing a type not yet registered
- Does not delete PointerProperty in `unregister()` before unregistering the type
Correct order: Register dependencies first (PropertyGroup → Operator → Panel). Unregister in reverse order.
#### 6.2 Handler Cleanup
Flag WARNING if addon registers handlers in `register()` but does not remove them in `unregister()`.
#### 6.3 Missing @persistent on Handlers
Flag BLOCKER if a handler callback is appended to `bpy.app.handlers.*` without the `@bpy.app.handlers.persistent` decorator. Without it, the handler is removed on file load.
#### 6.4 Dynamic EnumProperty Items GC
Flag BLOCKER if `EnumProperty(items=callback_function)` where the callback returns a freshly created list without caching it in a persistent variable. The list gets garbage-collected → crash or empty dropdown.
#### 6.5 Extension vs Legacy Addon Format
Flag INFO if target is Blender 4.2+ and code uses `bl_info` dict instead of `blender_manifest.toml`.
---
### CHECK 7: Operator Best Practices
**Severity: WARNING**
#### 7.1 Unnecessary bpy.ops Usage
Flag WARNING if `bpy.ops.*` is used where direct data access achieves the same result:
- `bpy.ops.object.location_clear()` → `obj.location = (0, 0, 0)`
- `bpy.ops.object.select_all(action='DESELECT')` then individual select → `obj.select_set()`
- `bpy.ops.transform.translate(value=...)` → `obj.location += Vector(...)`
#### 7.2 Missing Undo Support in Custom Operators
Flag WARNING if a custom Operator modifies data but does not set `bl_options = {'REGISTER', 'UNDO'}`.
---
### CHECK 8: GPU / Drawing Code (Blender 5.0+)
**Severity: BLOCKER**
Flag BLOCKER if code imports `bgl` on Blender 5.0+. The `bgl` module is removed.
Flag WARNING if code uses `bgl` on Blender 4.x without version guard — `bgl` is deprecated since 3.x.
All drawing code MUST use the `gpu` module (`gpu.shader`, `gpu.types.GPUBatch`, `gpu.state`).
---
## Severity Summary Decision Tree
```
Code will crash or corrupt data?
├── YES → BLOCKER
│ ├── Context access in restricted callback
│ ├── bpy access from thread
│ ├── Stale reference after undo/remove/mode-switch
│ ├── Removed API without version guard
│ ├── Missing ensure_lookup_table() on BMesh
│ ├── Missing bm.free() on standalone BMesh
│ ├── Missing collection.objects.link()
│ ├── Wrong registration order (PointerProperty before type)
│ ├── Missing @persistent on handler
│ ├── EnumProperty items GC (no cache)
│ ├── bgl import on Blender 5.0+
│ └── Missing mesh.update() after from_pydata()
├── NO → Code uses deprecated API?
│ ├── YES → WARNING
│ │ ├── Deprecated API with known removal version
│ │ ├── Dict context override on 3.2–3.6
│ │ ├── Operator without poll() pre-check
│ │ ├── Unnecessary bpy.ops usage
│ │ ├── Missing handler cleanup in unregister()
│ │ ├── Heavy computation in timer callback
│ │ └── Missing undo support in operator
│ └── NO → Best practice issue?
│ ├── YES → INFO
│ │ ├── No target version declared
│ │ ├── bl_info on 4.2+ (use blender_manifest.toml)
│ │ └── Style/naming conventions
│ └── NO → PASS
```
---
## Auto-Fix Patterns
When a check fails, apply these fixes automatically where safe:
| Issue | Auto-Fix |
|-------|----------|
| Dict context override | Replace with `context.temp_override()` |
| `obj.select = True` | Replace with `obj.select_set(True)` |
| `bpy.data.lamps` | Replace with `bpy.data.lights` |
| `mesh.uv_textures` | Replace with `mesh.uv_layers` |
| Missing `mesh.update()` after `from_pydata()` | Insert `mesh.update()` after `from_pydata()` call |
| Missing `bm.free()` | Insert `bm.free()` after last `bm.to_mesh()` call |
| Missing `ensure_lookup_table()` | Insert `bm.verts.ensure_lookup_table()` before first index access |
| Missing `collection.objects.link()` | Insert `bpy.context.collection.objects.link(obj)` after `objects.new()` |
| `bgl` import on 5.0+ | Replace with `gpu` module equivalents |
| `scene["cycles"]` on 5.0+ | Replace with `scene.cycles` attribute access |
| `del obj["prop"]` on 5.0+ | Replace with `obj.property_unset("prop")` |
| `BLENDER_EEVEE` on 4.2–4.x | Replace with `BLENDER_EEVEE_NEXT` |
| `bone.layers` on 4.0+ | Replace with `bone.collections` usage |
| `NodeTree.inputs.new()` on 4.0+ | Replace with `NodeTree.interface.new_socket()` |
| Missing `@persistent` on handler | Add `@bpy.app.handlers.persistent` decorator |
---
## Validation Report Format
After running all checks, produce a report:
```
## Validation Report
Target Version: Blender X.Y
Total Issues: N
### BLOCKERS (N)
- [CHECK X.Y] Description — file:line
### WARNINGS (N)
- [CHECK X.Y] Description — file:line
### INFO (N)
- [CHECK X.Y] Description — file:line
### Auto-Fixes Applied (N)
- Description — file:line
```
---
## Reference Links
- **Validation Rules**: [references/methods.md](references/methods.md)
- **Before/After Examples**: [references/examples.md](references/examples.md)
- **Anti-Patterns Catalog**: [references/anti-patterns.md](references/anti-patterns.md)
### Dependency Skills
- `blender-core-api` — bpy module structure and data access patterns
- `blender-core-versions` — Version matrix and breaking changes
- `blender-core-runtime` — Threading, handlers, timers, mathutils
- `blender-errors-context` — Context error diagnosis and resolution
- `blender-errors-data` — Data reference errors and safe access patterns
- `blender-errors-version` — Version compatibility error diagnosis
### Official Documentation
- Blender Python API: https://docs.blender.org/api/current/
- Blender Gotchas: https://docs.blender.org/api/current/info_gotcha.html
- Blender 4.0 Release Notes: https://wiki.blender.org/wiki/Reference/Release_Notes/4.0
- Blender 5.0 Release Notes: https://wiki.blender.org/wiki/Reference/Release_Notes/5.0More General & Other skills
find-skills
vercel-labs/skills
Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
grill-me
mattpocock/skills
A relentless interview to sharpen a plan or design.
grill-with-docs
mattpocock/skills
A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.

