
Frappe Agent Debugger
- 27 installs
- 159 repo stars
- Updated July 8, 2026
- openaec-foundation/frappe_claude_skill_package
Helps with debugging tasks.
About
frappe-agent-debugger is a Claude Code skill for debugging. It helps solo builders move faster with AI-assisted development.
- frappe-agent-debugger
- Debugging
- AI-coding skill
Frappe Agent Debugger by the numbers
- 27 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #372 of 596 Debugging skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/frappe_claude_skill_package --skill frappe-agent-debuggerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 27 |
|---|---|
| repo stars | ★ 159 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/frappe_claude_skill_package ↗ |
What it does
Helps with debugging tasks.
Files
Frappe Debugging Agent
Systematically diagnoses Frappe/ERPNext issues by classifying errors, locating relevant code, and applying targeted diagnosis checklists.
Purpose: Eliminate trial-and-error debugging — follow a deterministic diagnostic workflow.
When to Use This Agent
ERROR ANALYSIS TRIGGER
|
+-- Python traceback or error message
| "ImportError: cannot import name X from frappe"
| --> USE THIS AGENT
|
+-- JavaScript console error
| "Uncaught TypeError: frm.set_value is not a function"
| --> USE THIS AGENT
|
+-- Silent failure (no error, wrong behavior)
| "Server Script runs but nothing happens"
| --> USE THIS AGENT
|
+-- Scheduler/background job failure
| "Job X failed" in scheduler logs
| --> USE THIS AGENT
|
+-- Build/asset errors
| "Module not found" or blank page after build
| --> USE THIS AGENTDebugging Workflow
STEP 1: CLASSIFY ERROR TYPE
Python | JavaScript | Database | Permission | Hook | Scheduler | Build
STEP 2: IDENTIFY THE MECHANISM
Controller | Server Script | Client Script | Hook | Scheduler | API
STEP 3: LOCATE RELEVANT CODE
Use Frappe file path conventions to find source
STEP 4: APPLY DIAGNOSIS CHECKLIST
Run type-specific checklist for the error class
STEP 5: SUGGEST FIX
Provide corrected code + reference relevant frappe-* skillsSee references/workflow.md for detailed steps.
Step 1: Error Classification
| Error Type | Indicators | Primary Tool |
|---|---|---|
| Python | Traceback with .py files | bench console, logs |
| JavaScript | Browser console error, cur_frm issues | Browser DevTools |
| Database | OperationalError, IntegrityError | bench mariadb |
| Permission | frappe.PermissionError, 403 responses | Permission Inspector |
| Hook | Errors after bench migrate, wrong events | bench doctor |
| Scheduler | bench doctor warnings, RQ failures | Scheduler logs |
| Build | Missing assets, blank page, module errors | bench build --verbose |
Step 2: Mechanism Identification
| Symptom | Likely Mechanism |
|---|---|
| Error during form save/submit | Controller or Server Script (validate/on_submit) |
| Error on page load | Client Script or Web Template |
| Error message from API call | Whitelisted method or REST API handler |
| Error in background | Scheduler event or frappe.enqueue() job |
Error after bench migrate | Hook configuration or patch |
Error after bench build | Frontend asset pipeline |
Step 3: File Path Conventions
ALWAYS check these locations based on the mechanism:
| Mechanism | File Path Pattern |
|---|---|
| Controller | apps/{app}/{app}/{module}/{doctype}/{doctype}.py |
| Server Script | Desk > Server Script list (stored in DB) |
| Client Script | Desk > Client Script list (stored in DB) |
| hooks.py | apps/{app}/{app}/hooks.py |
| Scheduler | apps/{app}/{app}/tasks.py or hooks.py scheduler_events |
| Whitelisted | apps/{app}/{app}/{module}/*.py (search for @frappe.whitelist) |
| Jinja | apps/{app}/{app}/templates/ |
| Patches | apps/{app}/{app}/patches/ |
Step 4: Diagnosis Checklists (Quick Reference)
Python Errors
| Error Pattern | Likely Cause | Fix |
|---|---|---|
AttributeError: 'NoneType' | frappe.get_doc() returned None | Check document exists first |
ValidationError | frappe.throw() in validate | Read the message — it IS the diagnosis |
ImportError | Wrong import path or Server Script using imports | Server Scripts CANNOT import |
LinkValidationError | Referenced document does not exist | Verify Link field target exists |
TimestampMismatchError | Concurrent edit conflict | Reload document before save |
DuplicateEntryException | Unique constraint violation | Check naming series or unique fields |
MandatoryError | Required field is empty | Set field before save/submit |
InvalidStatusError | Wrong docstatus transition | Follow 0→1→2 sequence |
CircularLinkingError | Self-referencing parent-child | Fix document hierarchy |
JavaScript Errors
| Error Pattern | Likely Cause | Fix |
|---|---|---|
frm.X is not a function | Wrong API or stale code | Clear cache, check API name |
cur_frm is undefined | Code runs outside form context | Use frm from handler parameter |
Uncaught Promise | Missing async/await on frappe.call | Add callback or await |
field undefined in frm.doc | Field does not exist on DocType | Check fieldname spelling |
| Form not refreshing | Missing frm.refresh_fields() | Add refresh after set_value |
Database Errors
| Error Pattern | Likely Cause | Fix |
|---|---|---|
OperationalError: 1054 | Column does not exist | Run bench migrate |
OperationalError: 1146 | Table does not exist | Run bench migrate |
IntegrityError: 1062 | Duplicate primary key | Check naming/autoname |
IntegrityError: 1452 | Foreign key violation | Linked document missing |
OperationalError: 1213 | Deadlock | Reduce transaction scope |
InternalError: 1366 | Invalid character for charset | Check input encoding |
Permission Errors
| Error Pattern | Likely Cause | Fix |
|---|---|---|
frappe.PermissionError | User lacks role permission | Check Role Permission Manager |
| 403 on API call | Missing frappe.has_permission() or wrong @frappe.whitelist(allow_guest=True) | Add permission check or guest flag |
| Empty list view | User Permissions filtering | Check User Permission for that user |
| Cannot submit | No Submit permission for role | Add Submit perm in DocType |
Debug Tools
bench console (Python REPL)
bench --site {site} console
# Then:
frappe.get_doc("Sales Invoice", "SINV-00001") # Inspect document
frappe.db.sql("SELECT name FROM `tabSales Invoice` LIMIT 5") # Raw SQL
frappe.get_hooks("doc_events") # Inspect active hooks
frappe.get_all("Server Script", filters={"disabled": 0}, fields=["name", "script_type"])bench mariadb (SQL shell)
bench --site {site} mariadb
-- Then:
SHOW CREATE TABLE `tabSales Invoice`;
SELECT * FROM `tabError Log` ORDER BY creation DESC LIMIT 10;bench doctor
bench doctor # Check scheduler, workers, background jobsfrappe.logger()
logger = frappe.logger("my_debug", allow_site=True)
logger.info(f"Variable value: {my_var}")
# Logs to: sites/{site}/logs/my_debug.logBrowser DevTools
Console tab → JavaScript errors
Network tab → Failed API calls (check response body for traceback)
Application tab → Session/cookie issuesLog File Locations
| Log | Path | Contains |
|---|---|---|
| Frappe web | sites/{site}/logs/frappe.log | Web request errors |
| Worker | sites/{site}/logs/worker.log | Background job errors |
| Scheduler | sites/{site}/logs/scheduler.log | Scheduled task output |
| Custom logger | sites/{site}/logs/{name}.log | frappe.logger("{name}") output |
| Bench | ~/.bench/logs/bench.log | Bench command output |
| Error Log DocType | Desk > Error Log | UI-accessible error records |
| Supervisor | /var/log/supervisor/ | Process manager logs |
| nginx | /var/log/nginx/ | HTTP request/proxy errors |
Common Error Patterns Table
| Error Message | Likely Cause | Fix | Relevant Skill |
|---|---|---|---|
Import not allowed in Server Scripts | Using import in Server Script | Use frappe.utils.* or move to Controller | frappe-errors-serverscripts |
Cannot read properties of undefined | JS accessing field before form load | Add frm.doc.field null check | frappe-errors-clientscripts |
DocType X not found | Missing app install or migration | bench migrate or bench install-app | frappe-ops-bench |
Scheduler is not running | Workers stopped | bench doctor, restart workers | frappe-ops-bench |
BrokenPipeError | gunicorn timeout on long operation | Use frappe.enqueue() for long tasks | frappe-impl-scheduler |
ModuleNotFoundError | Python package not installed | bench pip install {pkg} | frappe-ops-bench |
Duplicate name | Name collision in naming series | Check autoname or naming_series | frappe-syntax-doctypes |
Insufficient Permission | Missing role for operation | Check Role Permissions | frappe-core-permissions |
Cannot edit submitted document | Modifying docstatus=1 doc | Use amend_doc() or cancel first | frappe-errors-controllers |
Invalid column | Schema out of sync | bench migrate | frappe-errors-database |
Agent Output Format
ALWAYS produce debugging output in this format:
## Debug Report
### Error Classification
**Type**: [Python/JS/Database/Permission/Hook/Scheduler/Build]
**Mechanism**: [Controller/Server Script/Client Script/Hook/etc.]
### Root Cause
[One-sentence diagnosis]
### Evidence
- [What log/traceback line confirms this]
- [What code path is involved]
### Fix
[Corrected code or configuration change]
### Verification Steps
1. [How to confirm the fix works]
2. [What to check in logs/UI]
### Referenced Skills
- `frappe-*`: [what was consulted]Debugging Decision Tree
ERROR RECEIVED
|
+-- Has traceback?
| +-- YES: Read LAST line first (actual error)
| | +-- Contains ".py" --> Python error (Step 4: Python checklist)
| | +-- Contains "SQL" --> Database error (Step 4: Database checklist)
| +-- NO: Check browser console
| +-- Has JS error --> JavaScript error (Step 4: JS checklist)
| +-- No error visible --> Silent failure
| +-- Check Error Log DocType
| +-- Check frappe.log
| +-- Add frappe.logger() statements
|
+-- Error after bench command?
| +-- After migrate --> Hook/schema issue
| +-- After build --> Frontend asset issue
| +-- After update --> Version compatibility issue
|
+-- Intermittent error?
+-- Check scheduler logs
+-- Check worker logs
+-- Check for race conditions (TimestampMismatchError)See references/checklists.md for complete diagnosis checklists. See references/examples.md for debugging walkthrough examples. See references/advanced-debugging.md for VS Code DAP setup, bench console patterns, mariadb diagnostics, and profiling tools.
Advanced Debugging Reference
VS Code Debug Adapter Protocol (DAP)
launch.json for Frappe Bench
Create .vscode/launch.json in the frappe-bench/apps/frappe directory:
{
"version": "0.2.0",
"configurations": [
{
"name": "Bench Web",
"type": "python",
"request": "launch",
"justMyCode": false,
"program": "${workspaceFolder}/frappe/frappe/utils/bench_helper.py",
"args": [
"frappe", "serve", "--port", "8000", "--noreload", "--nothreading"
],
"python": "${workspaceFolder}/../env/bin/python",
"cwd": "${workspaceFolder}/../sites",
"env": {
"DEV_SERVER": "1"
}
}
]
}Configuration Notes
| Flag | Why Required |
|---|---|
--noreload | Disables Werkzeug autoreload — VS Code debugger cannot attach to reloaded processes |
--nothreading | Disables multithreading — breakpoints only work reliably in single-threaded mode |
DEV_SERVER: "1" | Required for Socket.io when running bench serve directly instead of bench start |
justMyCode: false | Allows stepping into Frappe framework code, not just your app code |
Attaching to Gunicorn Workers
For production-like debugging, attach to a running gunicorn worker:
{
"name": "Attach to Gunicorn",
"type": "python",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"justMyCode": false,
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "/home/frappe/frappe-bench/apps/frappe"
}
]
}To enable, add debugpy to the gunicorn worker:
# In your app's __init__.py or a startup hook (TEMPORARY — remove after debugging)
import debugpy
debugpy.listen(("0.0.0.0", 5678))
# debugpy.wait_for_client() # Uncomment to pause until debugger connectsInstall debugpy: bench pip install debugpy
WARNING: NEVER leave debugpy in production code. ALWAYS remove after debugging.
Debug Configuration for Background Workers
Background jobs (RQ workers) run in separate processes. To debug:
{
"name": "Bench Worker",
"type": "python",
"request": "launch",
"justMyCode": false,
"program": "${workspaceFolder}/../env/bin/bench",
"args": [
"worker", "--queue", "default"
],
"python": "${workspaceFolder}/../env/bin/python",
"cwd": "${workspaceFolder}/../sites",
"env": {
"DEV_SERVER": "1"
}
}Breakpoints in Python Controllers
1. Open the controller file (e.g., apps/erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.py) 2. Set breakpoints on lines inside lifecycle methods (validate, on_submit, etc.) 3. Start the "Bench Web" debug configuration 4. Trigger the action in browser (save, submit the document) 5. VS Code pauses at the breakpoint — inspect self.doc, frappe.session, local variables
Alternative — pdb inline breakpoints (when VS Code is not available):
def validate(self):
import pdb; pdb.set_trace() # Execution pauses here in terminal
# Or for Python 3.7+:
breakpoint()ALWAYS use --noreload --nothreading with pdb, otherwise the terminal is unusable.
---
bench console — Advanced Usage
Starting the Console
bench --site mysite consoleOpens an iPython shell (or standard Python shell) with Frappe initialized and connected to the site database.
Document Inspection Patterns
# Fetch and inspect a document
doc = frappe.get_doc("Sales Invoice", "SINV-00001")
doc.as_dict() # Full document as dictionary
doc.items # Child table rows
doc.docstatus # 0=Draft, 1=Submitted, 2=Cancelled
doc.meta.get_field("customer") # Field metadata
# List documents with filters
frappe.get_all("Sales Invoice",
filters={"docstatus": 1, "customer": "Test Customer"},
fields=["name", "grand_total", "posting_date"],
order_by="posting_date desc",
limit=10
)
# Check if document exists
frappe.db.exists("Sales Invoice", "SINV-00001")
# Get single value
frappe.db.get_value("Sales Invoice", "SINV-00001", "grand_total")Direct SQL Queries
# Raw SQL (returns list of tuples)
frappe.db.sql("SELECT name, grand_total FROM `tabSales Invoice` LIMIT 5")
# With as_dict for named access
frappe.db.sql("SELECT name, grand_total FROM `tabSales Invoice` LIMIT 5", as_dict=True)
# Parameterized queries (ALWAYS use this — prevents SQL injection)
frappe.db.sql(
"SELECT name FROM `tabSales Invoice` WHERE customer = %s AND docstatus = %s",
("Test Customer", 1),
as_dict=True
)
# Count records
frappe.db.count("Sales Invoice", filters={"docstatus": 1})Permission Testing
# Switch user context for permission testing
frappe.set_user("user@example.com")
# Check what this user can see
frappe.get_all("Sales Invoice", limit=5) # Respects user permissions
# Check specific permission
frappe.has_permission("Sales Invoice", "read", doc="SINV-00001")
frappe.has_permission("Sales Invoice", "write", user="user@example.com")
# Inspect user roles
frappe.get_roles("user@example.com")
# Reset back to Administrator
frappe.set_user("Administrator")Hook and Configuration Inspection
# View all doc_events hooks
import json
print(json.dumps(frappe.get_hooks("doc_events"), indent=2))
# View scheduler events
print(json.dumps(frappe.get_hooks("scheduler_events"), indent=2))
# View active Server Scripts
frappe.get_all("Server Script",
filters={"disabled": 0},
fields=["name", "script_type", "reference_doctype", "doctype_event"]
)
# View site configuration
frappe.conf # Entire site_config.json as object
frappe.conf.db_name
frappe.conf.get("monitor")Running Methods Directly
# Call a whitelisted method
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note
result = make_delivery_note("SINV-00001")
# Trigger a document method
doc = frappe.get_doc("Sales Invoice", "SINV-00001")
doc.run_method("validate") # Run validate without saving
# Test enqueued jobs
from myapp.mymodule import my_long_task
my_long_task() # Run synchronously in console for debugging---
mariadb Console — Direct Database Access
Starting the Console
bench --site mysite mariadbOpens a MariaDB/MySQL shell connected to the site database.
Common Diagnostic Queries
-- Check table structure
DESCRIBE `tabSales Invoice`;
SHOW CREATE TABLE `tabSales Invoice`;
-- Check indexes (important for query performance)
SHOW INDEX FROM `tabSales Invoice`;
-- Check table sizes
SELECT
table_name,
ROUND(data_length / 1024 / 1024, 2) AS data_mb,
ROUND(index_length / 1024 / 1024, 2) AS index_mb,
table_rows
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY data_length DESC
LIMIT 20;
-- Recent Error Log entries
SELECT name, method, error, creation
FROM `tabError Log`
ORDER BY creation DESC
LIMIT 10;
-- Check recent schema changes (Patch Log)
SELECT name, creation
FROM `tabPatch Log`
ORDER BY creation DESC
LIMIT 10;
-- Find orphaned child table records
SELECT ct.name
FROM `tabSales Invoice Item` ct
LEFT JOIN `tabSales Invoice` p ON ct.parent = p.name
WHERE p.name IS NULL;
-- Check active sessions
SELECT user, device, last_request, status
FROM `tabSessions`
WHERE TIMESTAMPDIFF(MINUTE, last_request, NOW()) < 30;
-- Inspect naming series counters
SELECT * FROM `tabSeries` WHERE name LIKE 'SINV%';
-- Check for locked documents
SELECT name, modified_by, modified
FROM `tabSales Invoice`
WHERE _locked = 1;Schema Verification
-- Verify a column exists after bench migrate
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'tabSales Invoice'
AND COLUMN_NAME = 'custom_field_name';
-- Compare DocType definition vs actual table
-- (run in bench console, not mariadb)
-- frappe.get_meta("Sales Invoice").get_field("fieldname")---
Profiling and Monitoring
Frappe Recorder — Request/SQL Profiler
The Recorder captures requests and background jobs with full SQL query details.
How to use:
1. Login as Administrator 2. Open "Recorder" from Awesomebar 3. Click Start (optionally enable cProfile for Python profiling) 4. Perform the action to profile in another browser tab 5. Click Stop 6. Click on a captured request to inspect
What each capture shows:
| Field | Description |
|---|---|
| Path | URL or API endpoint hit |
| Command | Dotted path to the Python method executed |
| Duration | Total request time |
| Queries | Number of SQL queries |
| Query time | Time spent in database |
| Headers | Full request headers |
| Form data | POST data sent |
Per-query details:
- Exact SQL query text
- Query duration
- Full Python stack trace (shows which code triggered the query)
EXPLAINoutput (query execution plan)
cProfile mode: Enable the cProfile checkbox to get Python function-level profiling. This adds significant overhead — disable immediately after debugging.
Export/Import: Captures can be exported as JSON and imported on another site for offline analysis.
WARNING: Recorder adds overhead. NEVER leave it running in production.
bench CLI Profiling
Profile a specific method from the command line:
# Profile a server method
bench --site mysite --profile execute erpnext.projects.doctype.task.task.set_tasks_as_overdue
# Profile a database method
bench --site mysite execute frappe.db.get_database_sizebench doctor — Health Check
bench doctorChecks:
- Scheduler status (enabled/disabled)
- Worker status (running/stopped)
- Failed background jobs in RQ queues
- Pending jobs count
Use this FIRST when diagnosing scheduler or background job issues.
Monitor Module
Enable request/job monitoring by adding to site_config.json:
{
"monitor": 1
}Logs request and job metadata to sites/{site}/logs/monitor.json.log.
Sample request entry:
{
"duration": 807142,
"request": {
"ip": "127.0.0.1",
"method": "GET",
"path": "/api/method/frappe.realtime.get_user_info"
},
"transaction_type": "request"
}Sample job entry:
{
"duration": 1364,
"job": {
"method": "frappe.ping",
"scheduled": false
},
"transaction_type": "job"
}Data is buffered in Redis and flushed periodically via frappe.monitor.flush.
System Health Report
Search "System Health Report" in the Awesomebar. Checks:
- Background jobs status
- Scheduler health
- Database connectivity
- Cache (Redis) status
- Email queue
- Error log volume
- Storage usage
- Backup status
RQ Job Monitoring
Two virtual doctypes accessible from the Desk:
- RQ Worker: Shows all workers, their status, current job, and job counts
- RQ Job: Shows all background jobs, filterable by queue and status (queued, started, failed, finished)
Debugging Stuck Processes
Send SIGUSR1 to print thread stack traces:
kill -SIGUSR1 <PID>Stack traces appear in:
- Web workers:
bench/logs/web.error.log - Background workers:
bench/logs/worker.error.log - Scheduler:
bench/logs/schedule.error.log
---
Quick Reference: Which Tool When
| Scenario | Tool |
|---|---|
| Python error in controller | VS Code breakpoints + bench console |
| Slow API response | Frappe Recorder (check SQL count + query time) |
| Background job failing | bench doctor + worker logs |
| Permission issue | bench console + frappe.set_user() + frappe.has_permission() |
| Schema mismatch | bench mariadb + DESCRIBE + bench migrate |
| Memory/CPU issues | Monitor module + System Health Report |
| Stuck process | kill -SIGUSR1 <PID> + error logs |
| Intermittent failure | Monitor module (monitor.json.log) + Error Log DocType |
Debugging Checklists
Pre-Debug Information Gathering
ALWAYS collect this information before starting diagnosis:
- [ ] Exact error message or traceback (copy-paste, not paraphrased)
- [ ] When does it occur? (form save, submit, page load, scheduled, API call)
- [ ] Is it reproducible? (always, sometimes, first-time only)
- [ ] What changed recently? (code deploy, bench update, new app installed)
- [ ] Frappe/ERPNext version (
bench version) - [ ] Site name and environment (development, staging, production)
Python Error Checklist
Server Script Errors
- [ ] No
importstatements (Server Scripts CANNOT import) - [ ] Using
docvariable (NOTself, NOTdocument) - [ ] Correct event selected (validate vs on_update vs on_submit)
- [ ] No
doc.save()inside the script (causes infinite loop) - [ ] Null checks before accessing nested attributes
- [ ]
frappe.throw()for user-facing errors (NOTraise) - [ ] Script is enabled (not disabled in Server Script list)
- [ ] Correct DocType reference in Server Script configuration
Controller Errors
- [ ]
super().method()called in overrides - [ ] NOT modifying
self.*inon_update(useself.db_set()instead) - [ ] NOT calling
self.save()inside lifecycle hooks - [ ] Imports at module level (top of file)
- [ ]
frappe.throw()for validation errors - [ ] Transaction awareness (changes before
frappe.throw()are rolled back)
Whitelisted Method Errors
- [ ]
@frappe.whitelist()decorator present - [ ]
allow_guest=Trueif called by guest users - [ ] Parameter types match what frontend sends (all strings from JS)
- [ ] Return value is JSON-serializable
- [ ] Permission check inside method body
JavaScript Error Checklist
Client Script Errors
- [ ] NO server-side calls (
frappe.db.*,frappe.get_doc()) - [ ]
frappe.call()uses callback or async/await - [ ]
frm.refresh_fields()afterfrm.set_value() - [ ] Using
frmparameter (NOTcur_frm) - [ ] Field names match DocType definition exactly
- [ ] Script type matches purpose (Form, List, etc.)
- [ ] Correct DocType reference in Client Script configuration
- [ ] Check
frm.doc.__islocalbefore accessing saved-only fields
API Call Errors
- [ ] Correct method path in
frappe.call() - [ ] Method is whitelisted on server
- [ ] Arguments match server function signature
- [ ] Response handler checks for
r.message(notr.data) - [ ] Error callback handles failures gracefully
Database Error Checklist
- [ ] Run
bench migrateafter any DocType field change - [ ] Check table exists:
bench mariadb→SHOW TABLES LIKE 'tab%' - [ ] Check column exists:
DESCRIBE \tab{DocType}\`` - [ ] Check for duplicate entries causing IntegrityError
- [ ] Check foreign key references exist (Link field targets)
- [ ] Check character encoding for special characters
- [ ] Verify autoname/naming_series configuration
Permission Error Checklist
- [ ] Role has the required permission level (read, write, create, submit, cancel, delete)
- [ ] User has the role assigned
- [ ] User Permission restrictions checked (per-document filtering)
- [ ]
if_ownerpermission considered - [ ] For API:
@frappe.whitelist()decorator present - [ ] For guest access:
allow_guest=Truein whitelist - [ ] Custom permission checks in code use
frappe.has_permission()
Hook Error Checklist
- [ ] hooks.py has valid Python syntax
- [ ] Function dotted paths are correct and exist
- [ ] Event names are spelled correctly
- [ ]
bench migraterun after hooks.py changes - [ ] No circular imports in referenced functions
- [ ]
required_appslists all dependencies - [ ] v16-only hooks not used on v14/v15
Scheduler Error Checklist
- [ ] Scheduler is enabled:
bench doctor - [ ] Workers are running: check supervisor/systemd
- [ ] Job function path is correct in hooks.py
- [ ]
frappe.init()andfrappe.connect()for standalone scripts - [ ] Long-running jobs use
frappe.enqueue()(not direct execution) - [ ] Check RQ failed queue:
bench doctor - [ ] Check scheduler log:
sites/{site}/logs/scheduler.log
Build Error Checklist
- [ ]
bench buildcompletes without errors - [ ] Node.js version compatible (check
.nvmrcorpackage.json) - [ ]
yarn installcompleted inapps/{app} - [ ] Frontend file paths correct in
hooks.py(app_include_js/css) - [ ] No circular imports in JavaScript modules
- [ ] Clear browser cache after rebuild
- [ ] For production:
bench build --production
Post-Fix Verification Checklist
After applying any fix, ALWAYS verify:
- [ ] Original error no longer occurs
- [ ] No new errors introduced (check Error Log DocType)
- [ ] Related functionality still works (regression check)
- [ ] Fix works on target Frappe version
- [ ] Logs are clean:
tail -f sites/{site}/logs/frappe.log
Debugging Examples
Example 1: Server Script Import Error
Input
Error: ImportError: import not allowed in Server Scripts
Script: "Calculate Total" on Sales Invoice validateDebug Walkthrough
Step 1 — Classify: Python error (ImportError in Server Script)
Step 2 — Mechanism: Server Script (validate event)
Step 3 — Locate: Desk > Server Script > "Calculate Total"
Step 4 — Diagnose:
# BROKEN — Server Scripts CANNOT import
import json
data = json.loads(doc.custom_data)
total = sum(item["amount"] for item in data)
doc.grand_total = totalRoot cause: Server Scripts run in a restricted sandbox. import statements are forbidden.
Step 5 — Fix:
# CORRECT — use frappe.utils or built-in functions
data = frappe.parse_json(doc.custom_data)
total = sum(item["amount"] for item in data)
doc.grand_total = totalVerification: Save the Server Script, open a Sales Invoice, modify and save — no error.
Referenced Skills: frappe-errors-serverscripts, frappe-syntax-serverscripts
---
Example 2: Client Script Async Error
Input
User reports: "I click Calculate button but nothing happens"
No visible error in the formDebug Walkthrough
Step 1 — Classify: JavaScript error (silent failure, check browser console)
Step 2 — Mechanism: Client Script (custom button)
Step 3 — Locate: Browser DevTools Console shows:
Uncaught TypeError: Cannot read properties of undefined (reading 'message')Step 4 — Diagnose:
// BROKEN — frappe.call is async, result is undefined synchronously
frappe.ui.form.on('Sales Invoice', {
refresh(frm) {
frm.add_custom_button('Calculate', () => {
let result = frappe.call({
method: 'myapp.api.calculate',
args: { invoice: frm.doc.name }
});
frm.set_value('grand_total', result.message); // result is undefined!
});
}
});Root cause: frappe.call() is asynchronous. The return value is not the response.
Step 5 — Fix:
// CORRECT — use callback or async/await
frappe.ui.form.on('Sales Invoice', {
refresh(frm) {
frm.add_custom_button('Calculate', () => {
frappe.call({
method: 'myapp.api.calculate',
args: { invoice: frm.doc.name },
callback(r) {
if (r.message) {
frm.set_value('grand_total', r.message);
}
}
});
});
}
});Verification: Click Calculate button, check that grand_total updates.
Referenced Skills: frappe-errors-clientscripts, frappe-impl-clientscripts
---
Example 3: Database Column Missing After Deploy
Input
OperationalError: (1054, "Unknown column 'custom_approval_status' in 'field list'")
Traceback points to: frappe/model/db_query.pyDebug Walkthrough
Step 1 — Classify: Database error (OperationalError 1054 — column missing)
Step 2 — Mechanism: Database query (column does not exist in table)
Step 3 — Locate:
bench --site mysite mariadb
# Then:
DESCRIBE `tabSales Invoice`;
# Confirm: custom_approval_status column is NOT presentStep 4 — Diagnose: The Custom Field or DocType field was added but bench migrate was not run on this site. The schema is out of sync.
Step 5 — Fix:
# Run migration to sync database schema
bench --site mysite migrate
# Verify column now exists
bench --site mysite mariadb -e "DESCRIBE \`tabSales Invoice\`" | grep custom_approval_statusVerification: Repeat the operation that caused the error — it should succeed.
Referenced Skills: frappe-errors-database, frappe-ops-bench
---
Example 4: Permission Error on API Call
Input
frappe.PermissionError: No permission to read Sales Invoice
User: john@example.com, Role: Sales User
But John CAN see Sales Invoices in the list view!Debug Walkthrough
Step 1 — Classify: Permission error
Step 2 — Mechanism: API call (whitelisted method or REST endpoint)
Step 3 — Locate:
# In bench console, check permissions
frappe.set_user("john@example.com")
frappe.has_permission("Sales Invoice", "read") # Returns True
frappe.has_permission("Sales Invoice", "read", doc="SINV-00042") # Returns False!Step 4 — Diagnose: User Permission exists that restricts John to specific company records. SINV-00042 belongs to a different company.
# Check User Permissions
frappe.get_all("User Permission",
filters={"user": "john@example.com", "allow": "Company"},
fields=["for_value"])
# Returns: [{"for_value": "My Company LLC"}]
# Check invoice company
frappe.db.get_value("Sales Invoice", "SINV-00042", "company")
# Returns: "Other Company Inc"Step 5 — Fix: Either: 1. Add User Permission for "Other Company Inc" for John, OR 2. Remove the Company-level User Permission restriction if John should see all
Verification: Re-run frappe.has_permission("Sales Invoice", "read", doc="SINV-00042") — returns True.
Referenced Skills: frappe-core-permissions, frappe-errors-permissions
---
Example 5: Scheduler Job Silently Failing
Input
"My scheduled task should run every hour but nothing happens"
No errors visible in the UIDebug Walkthrough
Step 1 — Classify: Scheduler issue (silent failure)
Step 2 — Mechanism: Scheduler event (hooks.py scheduler_events)
Step 3 — Locate:
# Check scheduler health
bench doctor
# Output: "Scheduler is disabled for mysite"Step 4 — Diagnose:
# Check scheduler status
bench --site mysite scheduler status
# Output: disabled
# Also check worker status
bench --site mysite worker statusRoot cause: Scheduler was disabled (common after bench update or site restore).
Step 5 — Fix:
# Enable scheduler
bench --site mysite scheduler enable
# Verify
bench doctor
# Should show: scheduler is running
# Check logs for next execution
tail -f sites/mysite/logs/scheduler.logVerification: Wait for next scheduled interval, confirm task executes in scheduler.log.
Referenced Skills: frappe-ops-bench, frappe-impl-scheduler
---
Example 6: v16 extend_doctype_class Missing super()
Input
ValueError: Workflow State not set during Sales Invoice submission
Works in v15, fails in v16 after migrationDebug Walkthrough
Step 1 — Classify: Python error (v16 migration issue)
Step 2 — Mechanism: Controller using extend_doctype_class (v16)
Step 3 — Locate:
# In hooks.py (v16 pattern):
# extend_doctype_class = {"Sales Invoice": "myapp.overrides.CustomSalesInvoice"}Step 4 — Diagnose:
# BROKEN — missing super() call
class CustomSalesInvoice(SalesInvoice):
def on_submit(self):
# Custom logic only — skips ERPNext's on_submit!
self.create_delivery_note()Root cause: Without super().on_submit(), the base class workflow state transition is skipped.
Step 5 — Fix:
# CORRECT — ALWAYS call super() first in extend_doctype_class
class CustomSalesInvoice(SalesInvoice):
def on_submit(self):
super().on_submit() # REQUIRED — runs ERPNext's on_submit
self.create_delivery_note()Verification: Submit a Sales Invoice — workflow state transitions correctly and custom logic runs.
Referenced Skills: frappe-errors-controllers, frappe-syntax-controllers
Debugging Workflow — Detailed Steps
Step 1: Classify Error Type
Input Analysis
ALWAYS start by reading the error input carefully:
1. If traceback provided: Read the LAST line first — it contains the actual exception 2. If error message only: Match against the Common Error Patterns Table in SKILL.md 3. If symptom description: Ask for Error Log DocType entries or browser console output
Classification Rules
| Input Contains | Classification |
|---|---|
.py file paths in traceback | Python |
frappe. Python exceptions | Python |
TypeError, Uncaught, browser console | JavaScript |
OperationalError, IntegrityError, SQL keywords | Database |
PermissionError, 403, Insufficient Permission | Permission |
hooks.py, after bench migrate | Hook |
worker, scheduler, RQ, enqueue | Scheduler |
Module not found (JS), blank page, bench build | Build |
Step 2: Identify the Mechanism
Traceback Path Analysis
Read the traceback to identify which Frappe mechanism is involved:
| Path in Traceback | Mechanism |
|---|---|
{app}/{module}/{doctype}/{doctype}.py | Controller |
frappe/core/doctype/server_script/ | Server Script |
frappe/handler.py → @frappe.whitelist | Whitelisted method |
frappe/tasks.py or rq/worker.py | Background job |
frappe/website/ | Website/portal |
frappe/patches/ | Migration patch |
No Traceback Available
If no traceback, determine mechanism from context:
1. Error on form interaction → Client Script or Controller 2. Error on API call → Whitelisted method or Server Script API 3. Error in background → Scheduler or enqueue job 4. Error after deployment change → Hook or patch
Step 3: Locate Relevant Code
For Controller Issues
# Find the controller file
find apps/ -path "*/{doctype_folder}/{doctype_folder}.py" -type f
# Example: Sales Invoice controller
# apps/erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.pyFor Server Script Issues
# In bench console:
frappe.get_all("Server Script",
filters={"reference_doctype": "Sales Invoice", "disabled": 0},
fields=["name", "script_type", "doctype_event"])For Hook Issues
# In bench console:
import json
hooks = frappe.get_hooks("doc_events")
print(json.dumps(hooks.get("Sales Invoice", {}), indent=2))For Client Script Issues
# In bench console:
frappe.get_all("Client Script",
filters={"dt": "Sales Invoice", "enabled": 1},
fields=["name", "script"])Step 4: Apply Diagnosis Checklist
Python Error Diagnosis
1. Read the exception type and message 2. Check if it is a Frappe-specific exception (see SKILL.md table) 3. Identify the line number in the traceback 4. Check variable state at that point using bench console 5. Verify the code against frappe-errors-* skills
JavaScript Error Diagnosis
1. Open Browser DevTools Console tab 2. Reproduce the error 3. Check the Network tab for failed API calls 4. Read the response body of failed calls (contains Python traceback) 5. Verify Client Script syntax against frappe-syntax-clientscripts
Database Error Diagnosis
1. Connect via bench mariadb 2. Check table structure: SHOW CREATE TABLE \tab{DocType}\` 3. Check recent schema changes: SELECT * FROM \tabPatch Log\ ORDER BY creation DESC LIMIT 10 4. Verify column existence for the field causing the error 5. Run bench migrate` if schema is out of sync
Permission Error Diagnosis
1. Check Role Permission Manager for the DocType 2. Check User Permissions for the specific user 3. Verify frappe.has_permission() returns expected result in bench console 4. Check if ignore_permissions=True was incorrectly used (or missing) 5. For API: verify @frappe.whitelist() decorator and allow_guest flag
Step 5: Suggest Fix
Fix Quality Rules
- ALWAYS provide the corrected code, not just a description
- ALWAYS reference the relevant
frappe-*skill - ALWAYS include verification steps
- NEVER suggest
ignore_permissions=Trueas a fix for permission errors - NEVER suggest disabling validation as a fix
- ALWAYS consider version compatibility (v14/v15/v16)
Fix Verification
After suggesting a fix, ALWAYS include:
1. How to test the fix (specific steps) 2. What to check in logs after applying 3. How to confirm the root cause is resolved (not just suppressed)
Escalation Rules
If the debugging workflow does not resolve the issue:
| Condition | Action |
|---|---|
| Error is in Frappe core code | Check Frappe GitHub issues |
| Error only on specific version | Reference frappe-agent-migrator for version-specific bugs |
| Error involves multiple apps | Reference frappe-agent-architect for dependency issues |
| Error requires code review | Reference frappe-agent-validator for comprehensive check |