
Frappe Agent Validator
- 1 installs
- 159 repo stars
- Updated July 8, 2026
- openaec-foundation/erpnext_anthropic_claude_development_skill_package
Reviews Frappe/ERPNext code against best practices and the full skill knowledge base, catching v16 and ops patterns and producing correction reports.
About
A code-validation skill that reviews Frappe/ERPNext code against best practices and generates correction reports before deployment. A developer uses it to catch bugs and anti-patterns in generated or hand-written Frappe code prior to shipping.
- Validates Frappe code against the full 61-skill knowledge base before deployment
- Catches v16 patterns (extend_doctype_class, type annotations) and ops/bench mistakes
Frappe Agent Validator by the numbers
- 1 all-time installs (skills.sh)
- Ranked #984 of 1,352 Code Review & Quality skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/erpnext_anthropic_claude_development_skill_package --skill frappe-agent-validatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 159 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/erpnext_anthropic_claude_development_skill_package ↗ |
What it does
Reviews Frappe/ERPNext code against best practices and the full skill knowledge base, catching v16 and ops patterns and producing correction reports.
Files
Frappe Code Validator Agent
Validates Frappe/ERPNext code against the complete 61-skill knowledge base, catching errors BEFORE deployment.
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
|
+-- Ops/deployment validation
| "Is my bench setup correct?"
| --> USE THIS AGENTValidation Workflow
STEP 1: IDENTIFY CODE TYPE
Client Script | Server Script | Controller | hooks.py |
Jinja | Whitelisted | Bench/Ops | DocType JSON
STEP 2: RUN TYPE-SPECIFIC CHECKS
Apply checklist for identified code type
STEP 3: CHECK UNIVERSAL RULES
Error handling | Security | Performance | User feedback
STEP 4: VERIFY VERSION COMPATIBILITY
v14/v15/v16 features | Deprecated patterns
STEP 5: VALIDATE AGAINST SKILL CATALOG
Cross-reference with relevant frappe-* skills
STEP 6: GENERATE VALIDATION REPORT
Critical errors | Warnings | Suggestions | Corrected codeSee references/workflow.md for detailed steps.
Critical Checks by Code Type
Server Script Checks
| Check | Severity | Pattern | Fix |
|---|---|---|---|
| Import statements | FATAL | import X or from X import Y | Use frappe.utils.X() directly |
| Wrong doc variable | FATAL | self.field or document.field | Use doc.field |
| Wrong event for purpose | ERROR | Validation code in on_update | Move to validate event |
| try/except blocks | WARNING | try: ... except: | Use frappe.throw() for validation |
| No null checks | WARNING | doc.field.lower() | Add if doc.field: guard |
Client Script Checks
| Check | Severity | Pattern | Fix |
|---|---|---|---|
| Server-side API calls | FATAL | frappe.db.get_value() | Use frappe.call() |
| Missing async handling | FATAL | let x = frappe.call() | Use callback or async/await |
| No refresh after set_value | ERROR | frm.set_value() alone | Add frm.refresh_field() |
| Using cur_frm | WARNING | cur_frm.doc.field | Use frm parameter |
| No form state check | WARNING | Missing __islocal/docstatus | Add state guards |
Controller Checks
| Check | Severity | Pattern | Fix |
|---|---|---|---|
| self.* in on_update | FATAL | self.field = X in on_update | Use self.db_set() |
| Circular save | FATAL | self.save() in lifecycle hook | Remove self.save() |
| Missing super() | ERROR | Override without super() | Add super().method() |
| v16 extend_doctype_class | ERROR | Missing super() in mixin | ALWAYS call super() first |
| No type annotations | SUGGESTION | Missing type hints (v16) | Add type annotations |
hooks.py Checks
| Check | Severity | Pattern | Fix |
|---|---|---|---|
| Invalid Python syntax | FATAL | Syntax errors | Fix dict/list structure |
| Wrong event names | FATAL | Typo in event name | Use correct event names |
| Invalid function paths | FATAL | Wrong dotted path | Verify path exists |
| v16-only hooks on v14/v15 | ERROR | extend_doctype_class | Use doc_events instead |
| Missing required_apps | WARNING | No dependency declaration | Add all dependencies |
Ops/Bench Checks
| Check | Severity | Pattern | Fix |
|---|---|---|---|
| No migrate after hooks | FATAL | hooks.py changed, no migrate | Run bench migrate |
| Wrong bench command syntax | ERROR | Incorrect CLI args | Check frappe-ops-bench |
| Missing backup before upgrade | ERROR | Upgrade without backup | ALWAYS backup first |
| Production without supervisor | WARNING | No process manager | Use supervisor/systemd |
| No SSL in production | WARNING | HTTP-only deployment | Configure SSL/TLS |
DocType JSON Checks
| Check | Severity | Pattern | Fix |
|---|---|---|---|
| Missing mandatory fields | ERROR | No primary identifier | Add name or autoname |
| Duplicate fieldnames | FATAL | Same fieldname twice | Use unique fieldnames |
| Wrong fieldtype for data | WARNING | Text for short values | Use Data/Small Text |
| No permissions defined | WARNING | Empty permission list | Add role permissions |
v16 Specific Validations
extend_doctype_class Pattern
# VALIDATE: Mixin class MUST call super()
class CustomSalesInvoice(SalesInvoice):
def validate(self):
super().validate() # REQUIRED - never skip
self.custom_validation()
def on_submit(self):
super().on_submit() # REQUIRED - never skip
self.custom_on_submit()Type Annotations (v16 best practice)
# v16 recommended pattern
def get_customer_balance(customer: str) -> float:
...
# Validate: type hints on public API methods
@frappe.whitelist()
def process_order(order_name: str, action: str = "approve") -> dict:
...Data Masking (v16)
# Validate: sensitive fields should use data masking
# Check if PII fields have mask_with configured in DocType JSONUniversal Validation Rules
Security Checks (ALL code types)
| Check | Severity | Description |
|---|---|---|
| SQL Injection | CRITICAL | Raw user input in SQL |
| Permission bypass | CRITICAL | Missing permission checks |
| XSS vulnerability | HIGH | Unescaped user input in HTML |
| Sensitive data exposure | HIGH | Logging passwords/tokens |
| Hardcoded credentials | CRITICAL | API keys in source code |
Performance Checks (ALL code types)
| 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 |
| Missing index | MEDIUM | Filter on non-indexed field |
| No batch commit | HIGH | Commit per record in bulk ops |
Error Handling Checks (ALL code types)
| Check | Severity | Description |
|---|---|---|
| Silent failures | HIGH | except: pass without logging |
| Missing user feedback | MEDIUM | Errors not shown to user |
| Generic error messages | LOW | "An error occurred" |
| No rollback on failure | HIGH | Partial data on error |
Validation Report Format
ALWAYS generate reports in this format:
## Code Validation Report
### Code Type: [type]
### Target: [DocType / App / File]
### Event/Trigger: [if applicable]
### CRITICAL ERRORS (Must Fix)
| # | Line | Issue | Fix |
|---|------|-------|-----|
### WARNINGS (Should Fix)
| # | Line | Issue | Recommendation |
|---|------|-------|----------------|
### SUGGESTIONS (Nice to Have)
| # | Line | Suggestion |
|---|------|------------|
### Corrected Code
[If critical errors found, provide corrected version]
### Version Compatibility
| Version | Status | Notes |
|---------|--------|-------|
| v14 | [status] | |
| v15 | [status] | |
| v16 | [status] | |
### Referenced Skills
- frappe-skill-name: [what was validated against]Validation Depth Levels
| Level | Checks | Use When |
|---|---|---|
| Quick | Fatal errors only | Initial scan |
| Standard | + Warnings + Security | Pre-deployment (DEFAULT) |
| Deep | + Suggestions + Performance + Ops | Production review |
Skill Catalog Cross-Reference
This validator validates against ALL 61 frappe-* skills:
Syntax Validation (11 skills)
frappe-syntax-clientscripts, frappe-syntax-serverscripts, frappe-syntax-controllers, frappe-syntax-hooks, frappe-syntax-hooks-events, frappe-syntax-whitelisted, frappe-syntax-jinja, frappe-syntax-scheduler, frappe-syntax-customapp, frappe-syntax-doctypes, frappe-syntax-reports
Implementation Validation (12 skills)
frappe-impl-clientscripts, frappe-impl-serverscripts, frappe-impl-controllers, frappe-impl-hooks, frappe-impl-whitelisted, frappe-impl-jinja, frappe-impl-scheduler, frappe-impl-customapp, frappe-impl-reports, frappe-impl-workflow, frappe-impl-website, frappe-impl-ui-components, frappe-impl-integrations
Error Pattern Validation (7 skills)
frappe-errors-clientscripts, frappe-errors-serverscripts, frappe-errors-controllers, frappe-errors-hooks, frappe-errors-api, frappe-errors-permissions, frappe-errors-database
Core Pattern Validation (7 skills)
frappe-core-database, frappe-core-permissions, frappe-core-api, frappe-core-workflow, frappe-core-notifications, frappe-core-files, frappe-core-cache
Ops Validation (8 skills)
frappe-ops-bench, frappe-ops-deployment, frappe-ops-backup, frappe-ops-performance, frappe-ops-upgrades, frappe-ops-cloud, frappe-ops-app-lifecycle, frappe-ops-frontend-build
Testing Validation (2 skills)
frappe-testing-unit, frappe-testing-cicd
Quick Validation Commands
Server Script: 5-point check
1. Any import statements? --> FATAL 2. Any self. references? --> FATAL (use doc.) 3. Any try/except? --> WARNING (usually wrong) 4. Uses frappe.throw() for validation? --> GOOD 5. Uses doc.field for access? --> GOOD
Client Script: 5-point check
1. Any frappe.db.* calls? --> FATAL 2. Any frappe.get_doc() calls? --> FATAL 3. frappe.call() without callback? --> FATAL 4. Uses frm.doc.field for access? --> GOOD 5. Uses frm.refresh_field() after changes? --> GOOD
Controller: 5-point check
1. Modifying self.* in on_update? --> FATAL 2. Missing super().method() calls? --> ERROR 3. self.save() in lifecycle hook? --> FATAL 4. Imports at top of file? --> GOOD 5. Error handling for external calls? --> GOOD
hooks.py: 5-point check
1. Valid Python syntax? --> Check 2. Function paths exist? --> Check 3. v16-only hooks marked? --> Check 4. required_apps complete? --> Check 5. Fixture filters present? --> Check
Bench/Ops: 5-point check
1. bench migrate after changes? --> REQUIRED 2. Backup before destructive ops? --> REQUIRED 3. Scheduler enabled? --> Check 4. Workers running? --> Check 5. SSL configured (production)? --> Check
See references/checklists.md for complete checklists. See references/examples.md for validation examples.
Code Validation Checklists
Server Script Checklist
Fatal Errors (Code Will Not Work)
- [ ] No import statements
import json--> Usefrappe.parse_json()from frappe.utils import nowdate--> Usefrappe.utils.nowdate()from datetime import datetime--> Usefrappe.utils.*import requests--> IMPOSSIBLE in Server Script, use Controller
- [ ] Correct document variable
self.field_name--> Usedoc.field_namedocument.field_name--> Usedoc.field_name
- [ ] No undefined variables
- Only available:
doc,frappe,None,True,False - Built-in types:
int,float,str,list,dict,set,tuple
- [ ] Correct event for purpose
- Validation logic --> must be in
validateevent - Post-save logic --> must be in
on_updateevent - Pre-submit logic --> must be in
before_submitevent
Errors (Code May Fail)
- [ ] API Script has method and returns response
- [ ] Permission Query returns condition string or None
- [ ] Scheduler has proper cron syntax
Warnings (Should Fix)
- [ ] No try/except blocks (just use frappe.throw())
- [ ] Null checks before operations
- [ ] Using frappe.throw() not frappe.msgprint() for blocking
---
Client Script Checklist
Fatal Errors
- [ ] No server-side APIs
frappe.db.get_value()--> Usefrappe.call()frappe.db.set_value()--> Usefrappe.call()frappe.get_doc()--> Usefrappe.call()
- [ ] Async handling for frappe.call()
- WRONG:
let result = frappe.call({method: 'x'}) - CORRECT: Use callback or async/await
- [ ] Correct form event structure
- Must wrap in
frappe.ui.form.on('DocType', {...})
Errors
- [ ] refresh_field after set_value
- [ ] Use frm parameter, not cur_frm
Warnings
- [ ] Check form state (__islocal, docstatus)
- [ ] Await or callback for async operations
---
Controller Checklist
Fatal Errors
- [ ] No self modification in on_update
- Use
self.db_set()orfrappe.db.set_value()
- [ ] No circular save
self.save()in lifecycle hooks causes infinite loop
- [ ] Correct class inheritance
- Must extend Document or specific DocType class
Errors
- [ ] Call super() in overrides
- v16 extend_doctype_class: MANDATORY
- v14/v15 overrides: strongly recommended
- [ ] Understand transaction behavior
- validate, before_*: rollback on exception
- on_update, on_*: NO automatic rollback
v16 Specific
- [ ] extend_doctype_class uses mixin pattern correctly
- [ ] Type annotations on public methods
- [ ] super() called in EVERY overridden method
Warnings
- [ ] Error handling for external calls
- [ ] Logging for important operations
---
hooks.py Checklist
Fatal Errors
- [ ] Valid Python syntax
- [ ] Correct hook names (doc_events, scheduler_events, etc.)
- [ ] Valid function paths (dotted Python module paths)
Errors
- [ ] Version-specific hooks marked
extend_doctype_class: v16+ onlyoverride_doctype_class: v14+ (deprecated in v16)
- [ ] required_apps includes all dependencies
- [ ] Fixture filters present for shared DocTypes
Warnings
- [ ] Permission hooks return values, not throw
- [ ] Scheduler event handler paths are valid
---
Ops/Bench Checklist
Required Actions
- [ ] bench migrate after hooks.py or DocType changes
- [ ] bench build after JS/CSS changes
- [ ] bench clear-cache after Python changes
- [ ] bench restart for production changes
Deployment Checks
- [ ] Backup before destructive operations
- [ ] Scheduler enabled (
bench --site X scheduler enable) - [ ] Workers running (check supervisor status)
- [ ] SSL configured for production
- [ ] Nginx configured for production
Upgrade Checks
- [ ] Backup taken before upgrade
- [ ] Custom app compatibility verified
- [ ] Patches tested on staging
- [ ] Fixtures re-exported if needed
---
DocType JSON Checklist
Required
- [ ] Naming configured (autoname or naming_rule)
- [ ] Module specified and exists in modules.txt
- [ ] Permissions defined for at least one role
- [ ] Fieldnames unique within DocType
Warnings
- [ ] No duplicate fieldnames across sections
- [ ] Appropriate fieldtypes for data
- [ ] Link fields have valid options (target DocType)
- [ ] Required fields marked as reqd
---
Universal Security Checklist
Critical
- [ ] No SQL injection - Use parameterized queries
- [ ] Permission checks present - frappe.has_permission()
- [ ] No hardcoded credentials - Use frappe.conf.get()
High
- [ ] XSS prevention - frappe.utils.escape_html()
- [ ] Sensitive data not logged - Mask passwords/tokens
---
Universal Performance Checklist
- [ ] No query in loop - Single query before loop
- [ ] Bounded queries - LIMIT on large tables
- [ ] get_value over get_doc - When only one field needed
- [ ] Batch commits - Every 100-500 records, not per record
- [ ] Cache used - For frequently accessed static data
---
Quick Reference: Regex Patterns for Detection
Server Script Issues
Import: ^import |^from .* import
Self usage: \bself\.\w+
Try/except: \btry\s*:|except\sClient Script Issues
Server API: frappe\.db\.(get_value|set_value|sql|get_all|get_list)
Async issue: (let|const|var)\s+\w+\s*=\s*frappe\.call\s*\((?!.*callback)
cur_frm: \bcur_frm\bController Issues
on_update mod: def on_update\(self\):[\s\S]*?self\.\w+\s*=
circular save: def (validate|on_update)\(self\):[\s\S]*?self\.save\(\)
missing super: def (validate|on_submit)\(self\):(?![\s\S]*?super\(\))Security Issues
SQL injection: f["'].*\{.*\}.*["']\s*\) (in SQL context)
Hardcoded key: (api_key|password|secret|token)\s*=\s*["'][^"']+["']Code Validation Examples
Example 1: Server Script with Import Error
Code Submitted for Validation
# Server Script: Document Event - Before Save on Sales Invoice
from frappe.utils import nowdate, getdate
import json
if doc.posting_date < getdate(nowdate()):
data = json.loads(doc.custom_data)
if data.get("allow_backdating"):
pass
else:
frappe.throw("Backdating not allowed")Validation Report
## Code Validation Report
### Code Type: Server Script (Document Event)
### Target DocType: Sales Invoice
### Event: validate (Before Save)
### CRITICAL ERRORS (Must Fix)
| # | Line | Issue | Fix |
|---|------|-------|-----|
| 1 | 2 | `from frappe.utils import` - blocked in sandbox | Use `frappe.utils.nowdate()` directly |
| 2 | 3 | `import json` - blocked in sandbox | Use `frappe.parse_json()` |
### WARNINGS (Should Fix)
| # | Line | Issue | Recommendation |
|---|------|-------|----------------|
| 1 | 5 | No null check on `doc.custom_data` | Add `if doc.custom_data:` |
| 2 | 9 | Generic error message | Include date values |
### Corrected Code
if doc.posting_date < frappe.utils.getdate(frappe.utils.nowdate()): if doc.custom_data: data = frappe.parse_json(doc.custom_data) if not data.get("allow_backdating"): frappe.throw(f"Backdating not allowed. Date {doc.posting_date} is before today.") else: frappe.throw(f"Backdating not allowed. Date {doc.posting_date} is before today.")
### Referenced Skills
- frappe-syntax-serverscripts: import rules, doc variable
- frappe-errors-serverscripts: error message patterns---
Example 2: Client Script with Async Issue
Code Submitted
frappe.ui.form.on('Sales Order', {
refresh(frm) {
let customer_data = frappe.call({
method: 'frappe.client.get_value',
args: { doctype: 'Customer', filters: {name: frm.doc.customer}, fieldname: 'credit_limit' }
});
if (customer_data.message.credit_limit > 0) {
frm.set_value('credit_limit', customer_data.message.credit_limit);
}
}
});Validation Report
### CRITICAL ERRORS
| # | Line | Issue | Fix |
|---|------|-------|-----|
| 1 | 3-6 | `frappe.call()` without callback returns undefined | Use callback pattern |
| 2 | 7-8 | Accessing `.message` on undefined value | Will throw TypeError |
### WARNINGS
| # | Line | Issue | Recommendation |
|---|------|-------|----------------|
| 1 | 3 | Missing check for `frm.doc.customer` | Add null check |
| 2 | 8 | No `refresh_field()` after `set_value()` | Add refresh |
### Corrected Code
frappe.ui.form.on('Sales Order', { refresh(frm) { if (!frm.doc.customer) return;
frappe.call({ method: 'frappe.client.get_value', args: { doctype: 'Customer', filters: {name: frm.doc.customer}, fieldname: 'credit_limit' }, callback: function(r) { if (r.message && r.message.credit_limit > 0) { frm.set_value('credit_limit', r.message.credit_limit); frm.refresh_field('credit_limit'); } } }); } });
### Referenced Skills
- frappe-syntax-clientscripts: async patterns
- frappe-errors-clientscripts: common JS errors---
Example 3: Controller with on_update Modification
Code Submitted
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice
class CustomSalesInvoice(SalesInvoice):
def on_update(self):
if self.sales_partner:
commission = self.grand_total * 0.1
self.commission_amount = commission
self.custom_commission_status = "Calculated"Validation Report
### CRITICAL ERRORS
| # | Line | Issue | Fix |
|---|------|-------|-----|
| 1 | 7-8 | `self.*` in on_update won't be saved | Use `self.db_set()` |
### WARNINGS
| # | Line | Issue | Recommendation |
|---|------|-------|----------------|
| 1 | 4 | Missing `super().on_update()` | Add to preserve parent behavior |
| 2 | 6 | Hardcoded commission rate | Make configurable |
### Corrected Code
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice
class CustomSalesInvoice(SalesInvoice): def on_update(self): super().on_update() if self.sales_partner: commission = self.grand_total * 0.1 self.db_set({ "commission_amount": commission, "custom_commission_status": "Calculated" })
### Referenced Skills
- frappe-syntax-controllers: lifecycle hooks
- frappe-errors-controllers: on_update pitfall---
Example 4: v16 extend_doctype_class Missing super()
Code Submitted
# hooks.py
extend_doctype_class = {
"Sales Invoice": "my_app.overrides.CustomSalesInvoice"
}# my_app/overrides.py
class CustomSalesInvoice:
def validate(self):
self.custom_profit = self.grand_total - self.total_costValidation Report
### CRITICAL ERRORS
| # | Line | Issue | Fix |
|---|------|-------|-----|
| 1 | 3 | Missing `super().validate()` in extend_doctype_class mixin | ALWAYS call super() first |
| 2 | - | `extend_doctype_class` is v16 only | Will fail on v14/v15 |
### Corrected Code
class CustomSalesInvoice: def validate(self): super().validate() # MANDATORY in extend_doctype_class self.custom_profit = self.grand_total - self.total_cost
### Version Compatibility
| Version | Status | Notes |
|---------|--------|-------|
| v14 | FAIL | extend_doctype_class not supported |
| v15 | FAIL | extend_doctype_class not supported |
| v16 | PASS (after fix) | super() required |
### Referenced Skills
- frappe-syntax-hooks: extend_doctype_class syntax
- frappe-syntax-controllers: v16 mixin pattern---
Example 5: Ops Validation - Missing Migrate
Scenario Submitted
"I added scheduler_events to hooks.py and restarted bench, but my scheduled task never runs"
Validation Report
### CRITICAL ERRORS
| # | Issue | Fix |
|---|-------|-----|
| 1 | Missing `bench migrate` after hooks.py change | ALWAYS run `bench --site mysite migrate` after editing hooks.py |
### Diagnostic Steps
1. Run `bench --site mysite migrate`
2. Verify: `bench --site mysite scheduler status` shows "enabled"
3. Check: Setup > Scheduled Job Type > find your task
4. Verify: Task shows correct frequency, Stopped = No
5. Check: Scheduled Job Log for any execution attempts
### Referenced Skills
- frappe-ops-bench: bench migrate requirement
- frappe-impl-scheduler: scheduler registration---
Example 6: Clean Code (No Issues)
Code Submitted
# Server Script: Document Event - Before Save on Purchase Order
if doc.supplier:
supplier = frappe.get_doc("Supplier", doc.supplier)
if supplier.lead_time_days and doc.transaction_date:
from_date = frappe.utils.getdate(doc.transaction_date)
delivery_date = frappe.utils.add_days(from_date, supplier.lead_time_days)
doc.schedule_date = delivery_dateValidation Report
### NO CRITICAL ERRORS
### NO WARNINGS
### SUGGESTIONS
| # | Line | Suggestion |
|---|------|------------|
| 1 | 3 | Use `frappe.db.get_value("Supplier", doc.supplier, "lead_time_days")` for better performance |
### Code Quality: EXCELLENT
- Uses frappe namespace correctly (no imports)
- Uses `doc.` for document access
- Has proper null checks
- Clear purpose
### Version Compatibility
| Version | Status |
|---------|--------|
| v14 | Compatible |
| v15 | Compatible |
| v16 | Compatible |---
Example 7: Security Validation
Code Submitted
# Whitelisted method
@frappe.whitelist()
def search_customers(query):
return frappe.db.sql(f"SELECT name FROM tabCustomer WHERE name LIKE '%{query}%'")Validation Report
### CRITICAL ERRORS
| # | Line | Issue | Fix |
|---|------|-------|-----|
| 1 | 4 | SQL INJECTION - raw user input in SQL string | Use parameterized query |
| 2 | 4 | No permission check before database access | Add frappe.has_permission() |
### Corrected Code
@frappe.whitelist() def search_customers(query: str) -> list: if not frappe.has_permission("Customer", "read"): frappe.throw("Not permitted", frappe.PermissionError)
return frappe.db.sql( "SELECT name FROM tabCustomer WHERE name LIKE %s", [f"%{query}%"], as_dict=True )
### Referenced Skills
- frappe-core-database: parameterized queries
- frappe-core-permissions: permission checking
- frappe-errors-api: security patternsCode Validator Workflow - Detailed Steps
Step 1: Identify Code Type
Detection Rules
| If Code Contains... | Code Type |
|---|---|
frappe.ui.form.on( | Client Script |
# Server Script or sandbox patterns | Server Script |
class X(Document): | Controller |
doc_events = {, scheduler_events = { | hooks.py |
{% ... %}, {{ ... }} | Jinja Template |
@frappe.whitelist() | Whitelisted Method |
bench commands | Ops/Bench |
.json with doctype key | DocType JSON |
extend_doctype_class | v16 hooks.py |
When Ambiguous
Ask: "Is this code running in:"
- Browser (JavaScript) --> Client Script
- Frappe UI Server Script editor --> Server Script
- Python file in custom app --> Controller or Whitelisted
- hooks.py configuration --> Hooks
- Terminal/CLI --> Bench/Ops command
Step 2: Type-Specific Validation
Server Script Validation
1. IMPORT SCAN [FATAL]
Regex: ^import |^from .* import
If found: FATAL - imports blocked in sandbox
2. VARIABLE REFERENCE CHECK [FATAL]
Check for: self.*, document.*, this.
If found: FATAL - should use doc.*
3. TRY/EXCEPT SCAN [WARNING]
Regex: try:|except
If found: WARNING - usually wrong in Server Scripts
4. EVENT NAME VERIFICATION
Before Save code --> should be validate hook
After Save code --> should be on_update hook
Mismatch: ERROR
5. AVAILABLE NAMESPACE CHECK
Only allowed: frappe.*, doc, None, True, False,
int, float, str, list, dict, set, tuple
6. FRAPPE API CHECK
Verify valid: frappe.throw(), frappe.msgprint(),
frappe.db.*, frappe.utils.*, frappe.get_doc(), etc.Client Script Validation
1. SERVER API MISUSE [FATAL]
Check for: frappe.db.*, frappe.get_doc( (without frappe.call)
2. ASYNC HANDLING [FATAL]
Check for: frappe.call() without callback/async
Pattern: let x = frappe.call({...}) without callback
3. FORM STRUCTURE CHECK
Must be inside: frappe.ui.form.on('DocType', {...})
4. FIELD OPERATIONS CHECK
After frm.set_value(): should have frm.refresh_field()
5. FORM STATE CHECKS
Operations on new doc: check frm.doc.__islocal
Operations on submitted: check frm.doc.docstatusController Validation
1. CLASS STRUCTURE [ERROR]
Must extend Document or specific DocType class
2. SUPER CALL CHECK [WARNING/ERROR]
Override methods should call super()
v16 extend_doctype_class: super() is MANDATORY
3. LIFECYCLE MODIFICATION CHECK [FATAL]
In on_update: modifications to self.* won't save
Should use: self.db_set() or frappe.db.set_value()
4. CIRCULAR SAVE CHECK [FATAL]
Pattern: self.save() in lifecycle hooks
5. IMPORT VERIFICATION
Imports ARE allowed (unlike Server Scripts)
6. TYPE ANNOTATIONS CHECK [SUGGESTION - v16]
Public methods should have type hintshooks.py Validation
1. STRUCTURE CHECK
Valid Python dict syntax
No syntax errors
2. HOOK NAME VERIFICATION
doc_events: valid event names
scheduler_events: valid frequency keys
3. PATH VERIFICATION
Dotted paths should be valid Python paths
4. VERSION-SPECIFIC HOOKS
extend_doctype_class: v16+ only
If found in v14/v15 code: ERROR
5. REQUIRED APPS CHECK
All imported apps must be in required_apps
6. FIXTURE FILTER CHECK
Shared DocTypes (Custom Field, etc.) must have filtersOps/Bench Validation
1. COMMAND SYNTAX CHECK
bench --site sitename [command] format
2. MIGRATE REQUIREMENT CHECK
After hooks.py changes: bench migrate required
After DocType changes: bench migrate required
3. BUILD REQUIREMENT CHECK
After JS/CSS changes: bench build required
4. BACKUP CHECK
Before destructive operations: backup required
5. DEPLOYMENT CHECKS
Production: supervisor/systemd configured
Production: nginx configured
Production: SSL/TLS configured
Production: scheduler enabledStep 3: Universal Checks
Security Validation
1. SQL INJECTION [CRITICAL]
Pattern: f"...{user_input}..." in SQL
Should use: parameterized queries or frappe.db.escape()
2. PERMISSION BYPASS [CRITICAL]
Pattern: ignore_permissions=True without justification
Should have: explicit permission checks
3. XSS VULNERABILITY [HIGH]
Pattern: user input directly in HTML
Should use: frappe.utils.escape_html()
4. SENSITIVE DATA [HIGH]
Pattern: password, token, secret in log/print
Should be: masked or omitted
5. HARDCODED CREDENTIALS [CRITICAL]
Pattern: API keys, passwords in source
Should use: frappe.conf.get() or settings DocTypeError Handling Validation
1. SILENT FAILURE [HIGH]
Pattern: except: pass
Should have: logging or re-raise
2. USER FEEDBACK [MEDIUM]
Error occurs but no frappe.throw/msgprint
Should have: user notification
3. ROLLBACK HANDLING [HIGH]
After failed operations: frappe.db.rollback()
In batch processing: commit per batch, not per recordPerformance Validation
1. QUERY IN LOOP [HIGH]
Pattern: for item in items: frappe.db.get_value()
Should be: single query before loop
2. UNBOUNDED QUERY [MEDIUM]
Pattern: frappe.get_all() without limit
Should have: limit_page_length or filters
3. UNNECESSARY GET_DOC [LOW]
Pattern: frappe.get_doc() when only one field needed
Should be: frappe.db.get_value()
4. NO BATCH COMMIT [HIGH]
Pattern: frappe.db.commit() per record
Should be: commit every 100-500 recordsStep 4: Version Compatibility Check
V16-ONLY FEATURES (will fail on v14/v15):
- extend_doctype_class hook
- naming_rule = "UUID" in DocType
- pdf_renderer = "chrome" in Print Format
- data masking configuration
- Type annotations (best practice, not required)
DEPRECATED PATTERNS (warn):
- frappe.bean() --> use frappe.get_doc()
- frappe.msgprint(raise_exception=True) --> use frappe.throw()
- job_name parameter --> use job_id (v15+)
- setup.py --> use pyproject.toml (v15+)
BEHAVIORAL DIFFERENCES:
- Scheduler tick: 240s (v14) vs 60s (v15+)
- Job dedup: job_name (v14) vs job_id (v15+)Step 5: Validate Against Skill Catalog
Cross-reference code against relevant frappe-* skills:
| Code Type | Validate Against |
|---|---|
| Server Script | frappe-syntax-serverscripts, frappe-errors-serverscripts |
| Client Script | frappe-syntax-clientscripts, frappe-errors-clientscripts |
| Controller | frappe-syntax-controllers, frappe-errors-controllers |
| hooks.py | frappe-syntax-hooks, frappe-errors-hooks |
| Jinja | frappe-syntax-jinja |
| Whitelisted | frappe-syntax-whitelisted |
| Scheduler | frappe-syntax-scheduler, frappe-impl-scheduler |
| Custom App | frappe-syntax-customapp, frappe-impl-customapp |
| Database ops | frappe-core-database, frappe-errors-database |
| Permissions | frappe-core-permissions, frappe-errors-permissions |
| API calls | frappe-core-api, frappe-errors-api |
| Workflow | frappe-core-workflow, frappe-impl-workflow |
| Reports | frappe-syntax-reports, frappe-impl-reports |
| Bench/Ops | frappe-ops-bench, frappe-ops-deployment |
| Testing | frappe-testing-unit, frappe-testing-cicd |
Step 6: Generate Report
Report Structure
## Code Validation Report
### Summary
- Code Type: [type]
- Total Issues: X critical, Y warnings, Z suggestions
- Overall: [FAIL / PASS WITH WARNINGS / PASS]
### Critical Errors (Must Fix)
[Table of critical issues]
### Warnings (Should Fix)
[Table of warnings]
### Suggestions (Nice to Have)
[Table of suggestions]
### Corrected Code
[If critical errors exist, provide corrected version]
### Version Compatibility
[Compatibility matrix v14/v15/v16]
### Referenced Skills
[Which frappe-* skills were validated against]Severity Classification
| Severity | Criteria | Action Required |
|---|---|---|
| CRITICAL | Code will fail/crash | Must fix before deployment |
| HIGH | Significant bug/security issue | Should fix before deployment |
| MEDIUM | Potential issues | Fix when possible |
| LOW | Style/optimization | Optional improvement |
| SUGGESTION | Best practice | Consider for future |