- ERPNext Code Validator Agent
- This agent validates ERPNext/Frappe code against established patterns, common pitfalls, and version compatibility requirements.
- Purpose
- Catch errors BEFORE deployment, not after When to Use This Agent ┌─────────────────────────────────────────────────────────────────────┐ │ CODE VALIDATION TRIGGERS │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ► Code has been generated and needs review │ │ "Check this Server Script before I save it" │ │ └── USE THIS AGENT │ │ │ │ ► Code is causing errors │ │ "Why isn't this working?" │ │ └── USE THIS AGENT │ │ │ │ ► Pre-deployment validation │ │ "Is this production-ready?" │ │ └── USE THIS AGENT │ │ │ │ ► Code review for best practices │ │ "Can this be improved?" │ │ └── USE THIS AGENT │ │ │ └─────────────────────────────────────────────────────────────────────┘ Validation Workflow ┌─────────────────────────────────────────────────────────────────────┐ │ CODE VALIDATOR WORKFLOW │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ STEP 1: IDENTIFY CODE TYPE │ │ ══════════════════════════ │ │ • Client Script (JavaScript) │ │ • Server Script (Python sandbox) │ │ • Controller (Python full) │ │ • hooks.py configuration │ │ • Jinja template │ │ • Whitelisted method │ │ │ │ STEP 2: RUN TYPE-SPECIFIC CHECKS │ │ ═════════════════════════════════ │ │ • Apply checklist for identified code type │ │ • Check syntax patterns │ │ • Verify API usage │ │ │ │ STEP 3: CHECK UNIVERSAL RULES │ │ ══════════════════════════════ │ │ • Error handling present │ │ • User feedback appropriate │ │ • Security considerations │ │ • Performance implications │ │ │ │ STEP 4: VERIFY VERSION COMPATIBILITY │ │ ════════════════════════════════════ │ │ • v14/v15/v16 specific features │ │ • Deprecated patterns │ │ • Version-specific behaviors │ │ │ │ STEP 5: GENERATE VALIDATION REPORT │ │ ══════════════════════════════════ │ │ • Critical errors (must fix) │ │ • Warnings (should fix) │ │ • Suggestions (nice to have) │ │ • Corrected code (if errors found) │ │ │ └─────────────────────────────────────────────────────────────────────┘ → See references/workflow.md for detailed validation steps. Critical Checks by Code Type Server Script Checks ┌─────────────────────────────────────────────────────────────────────┐ │ ⚠️ SERVER SCRIPT CRITICAL CHECKS │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ [FATAL] Import statements │ │ ═══════════════════════════ │ │ ❌ import json → Use frappe.parse_json() │ │ ❌ from frappe.utils import X → Use frappe.utils.X() │ │ ❌ import requests → IMPOSSIBLE in Server Script │ │ │ │ [FATAL] Undefined variables │ │ ════════════════════════════ │ │ ❌ self.field → Use doc.field │ │ ❌ document.field → Use doc.field │ │ │ │ [FATAL] Wrong event handling │ │ ═══════════════════════════════ │ │ ❌ try/except for validation → Just frappe.throw() │ │ │ │ [ERROR] Event name mismatch │ │ ═══════════════════════════ │ │ ❌ Event "Before Save" code in "After Save" script │ │ │ │ [WARNING] Missing validation │ │ ═══════════════════════════════ │ │ ⚠️ No null/empty checks before operations │ │ │ └─────────────────────────────────────────────────────────────────────┘ Client Script Checks ┌─────────────────────────────────────────────────────────────────────┐ │ CLIENT SCRIPT CRITICAL CHECKS │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ [FATAL] Wrong API usage │ │ ═════════════════════════ │ │ ❌ frappe.db.get_value() → Server-side only! │ │ ❌ frappe.get_doc() → Server-side only! │ │ ✓ frappe.call() for server data │ │ │ │ [FATAL] Missing async handling │ │ ══════════════════════════════ │ │ ❌ let result = frappe.call() → Returns undefined │ │ ✓ frappe.call({callback: fn}) → Use callback │ │ ✓ await frappe.call({async:false}) → Or async/await │ │ │ │ [ERROR] Field refresh issues │ │ ════════════════════════════ │ │ ❌ frm.set_value() without refresh │ │ ✓ frm.set_value() then frm.refresh_field() │ │ │ │ [WARNING] Form state checks │ │ ═══════════════════════════ │ │ ⚠️ Not checking frm.doc.__islocal for new docs │ │ ⚠️ Not checking frm.doc.docstatus for submitted docs │ │ │ └─────────────────────────────────────────────────────────────────────┘ Controller Checks ┌─────────────────────────────────────────────────────────────────────┐ │ CONTROLLER CRITICAL CHECKS │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ [FATAL] Wrong lifecycle usage │ │ ═════════════════════════════ │ │ ❌ Modifying self.field in on_update → Changes NOT saved! │ │ ✓ Use frappe.db.set_value() in on_update │ │ │ │ [FATAL] Missing super() call │ │ ════════════════════════════ │ │ ❌ def validate(self): pass → Breaks parent validation │ │ ✓ def validate(self): super().validate() │ │ │ │ [ERROR] Transaction assumptions │ │ ═══════════════════════════════ │ │ ❌ Assuming rollback on error in on_update │ │ (only validate and before_* roll back on error) │ │ │ │ [ERROR] Circular save │ │ ══════════════════════ │ │ ❌ self.save() inside lifecycle hooks │ │ ❌ doc.save() for same document in hooks │ │ │ └─────────────────────────────────────────────────────────────────────┘ → See references/checklists.md for complete checklists. Validation Report Format
Code Validation Report
Code Type: [Server Script / Client Script / Controller / etc.]
Target DocType: [DocType name]
Event/Trigger: [Event name]
🔴 CRITICAL ERRORS (Must Fix) | Line | Issue | Fix | |
|
|
| | 3 | Import statement in Server Script | Use frappe.utils.X() directly |
🟡 WARNINGS (Should Fix) | Line | Issue | Recommendation | |
|
|
| | 12 | No null check before .lower() | Add: if value: value.lower() |
🔵 SUGGESTIONS (Nice to Have) | Line | Suggestion | |
|
| | 8 | Consider using frappe.db.get_value for single field |
Corrected Code ```python
[Corrected version with all critical errors fixed] Version Compatibility Version Status v14 ✅ Compatible v15 ✅ Compatible v16 ✅ Compatible
Universal Validation Rules
These apply to ALL code types:
Security Checks
| Check | Severity | Description |
|---|---|---|
| SQL Injection | CRITICAL | Raw user input in SQL queries |
| Permission bypass | CRITICAL | Missing permission checks before operations |
| XSS vulnerability | HIGH | Unescaped user input in HTML |
| Sensitive data exposure | HIGH | Logging passwords/tokens |
| ### Error Handling Checks | ||
| Check | Severity | Description |
| ------- | ---------- | ------------- |
| Silent failures | HIGH | Catching exceptions without handling |
| Missing user feedback | MEDIUM | Errors not communicated to user |
| Generic error messages | LOW | "An error occurred" without details |
| ### Performance Checks | ||
| Check | Severity | Description |
| ------- | ---------- | ------------- |
| Query in loop | HIGH | frappe.db.* inside for loop |
| Unbounded query | MEDIUM | SELECT without LIMIT |
| Unnecessary get_doc | LOW | get_doc when get_value suffices |
| → See references/examples.md for validation examples. | ||
| ## Version-Specific Validations | ||
| ### v16 Features (Fail on v14/v15) | ||
| ```python | ||
| # These ONLY work on v16+ | ||
| extend_doctype_class = {} # hooks.py - v16 only | ||
| naming_rule = "UUID" # DocType - v16 only | ||
| pdf_renderer = "chrome" # Print Format - v16 only | ||
| Deprecated Patterns (Warn) | ||
| # DEPRECATED - still works but should update | ||
| frappe | ||
| . | ||
| bean | ||
| ( | ||
| ) | ||
| # Use frappe.get_doc() | ||
| frappe | ||
| . | ||
| msgprint | ||
| ( | ||
| raise_exception | ||
| = | ||
| True | ||
| ) | ||
| # Use frappe.throw() | ||
| job_name parameter | ||
| # Use job_id (v15+) | ||
| Version-Specific Behaviors | ||
| Behavior | ||
| v14 | ||
| v15/v16 | ||
| Scheduler tick | ||
| 240s | ||
| 60s | ||
| Background job dedup | ||
| job_name | ||
| job_id | ||
| Quick Validation Commands | ||
| Server Script Quick Check | ||
| ❌ Any | ||
| import | ||
| statements? → FATAL | ||
| ❌ Any | ||
| self. | ||
| references? → FATAL (use | ||
| doc. | ||
| ) | ||
| ❌ Any | ||
| try/except | ||
| ? → WARNING (usually wrong) | ||
| ✅ Uses | ||
| frappe.throw() | ||
| for validation errors? → GOOD | ||
| ✅ Uses | ||
| doc.field | ||
| for document access? → GOOD | ||
| Client Script Quick Check | ||
| ❌ Any | ||
| frappe.db.* | ||
| calls? → FATAL (server-side only) | ||
| ❌ Any | ||
| frappe.get_doc() | ||
| calls? → FATAL (server-side only) | ||
| ❌ | ||
| frappe.call() | ||
| without callback? → FATAL (async issue) | ||
| ✅ Uses | ||
| frm.doc.field | ||
| for field access? → GOOD | ||
| ✅ Uses | ||
| frm.refresh_field() | ||
| after changes? → GOOD | ||
| Controller Quick Check | ||
| ❌ Modifying | ||
| self.* | ||
| in | ||
| on_update | ||
| ? → ERROR (won't save) | ||
| ❌ Missing | ||
| super().method() | ||
| calls? → WARNING | ||
| ❌ | ||
| self.save() | ||
| in lifecycle hook? → FATAL (circular) | ||
| ✅ Imports at top of file? → GOOD (controllers allow imports) | ||
| ✅ Error handling with try/except? → GOOD (controllers allow this) | ||
| Integration with Other Skills | ||
| This validator uses knowledge from: | ||
| Skill | ||
| What It Provides | ||
| erpnext-syntax-* | ||
| Correct syntax patterns | ||
| erpnext-impl-* | ||
| Correct implementation patterns | ||
| erpnext-errors-* | ||
| Error handling patterns | ||
| erpnext-database | ||
| Query patterns and pitfalls | ||
| erpnext-permissions | ||
| Permission check patterns | ||
| erpnext-api-patterns | ||
| API response patterns | ||
| Validation Depth Levels | ||
| Level | ||
| Checks | ||
| Use When | ||
| Quick | ||
| Fatal errors only | ||
| Initial scan | ||
| Standard | ||
| + Warnings | ||
| Pre-deployment | ||
| Deep | ||
| + Suggestions + Optimization | ||
| Production review | ||
| Default: | ||
| Standard | ||
| level for most validations. |