Skill125 repo starsupdated 2mo ago
frappe-errors-hooks
>
Install in Claude Code
Copygit clone --depth 1 https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package /tmp/frappe-errors-hooks && cp -r /tmp/frappe-errors-hooks/skills/source/errors/frappe-errors-hooks ~/.claude/skills/frappe-errors-hooksThen start a new Claude Code session; the skill loads automatically.
Definition
SKILL.md
# Frappe Hooks Error Diagnosis & Resolution
Cross-ref: `frappe-syntax-hooks` (syntax), `frappe-impl-hooks` (workflows), `frappe-errors-controllers` (controller errors).
---
## Error-to-Fix Mapping Table
| Error / Symptom | Cause | Fix |
|-----------------|-------|-----|
| Hook not firing at all | Typo in dotted path | Verify module path matches actual file location |
| `ImportError` on bench start | Wrong module path or circular import | Fix import path; break circular dependency |
| `AttributeError: module has no attribute` | Function name typo in hooks.py | Match function name exactly to Python definition |
| `app_include_js` not loading | Path missing `assets/` prefix or wrong extension | Use `"assets/myapp/js/file.js"` format |
| scheduler_events not running | Scheduler disabled or workers down | `bench scheduler enable`, check `bench doctor` |
| doc_events handler never called | DocType name misspelled in dict key | Use exact DocType name with spaces: `"Sales Invoice"` |
| `permission_query_conditions` breaks list view | SQL syntax error or frappe.throw() in handler | Return valid SQL string; NEVER throw |
| `override_doctype_class` import failure | Parent class import path changed between versions | Pin import to correct module path for target version |
| `extend_doctype_class` [v16+] method conflict | Two extensions define same method name | Rename conflicting methods; check hook resolution order |
| Fixtures not loading on install | Wrong `dt` key or DocType doesn't exist on target | Verify DocType exists before export; check filter syntax |
| `extend_bootinfo` breaks login | Unhandled exception in boot handler | Wrap ALL bootinfo code in try/except |
| Wildcard `"*"` handler breaks all saves | Unhandled exception in wildcard doc_events | ALWAYS wrap wildcard handlers in try/except |
| Hook fires but changes lost | Missing `frappe.db.commit()` in scheduler | Add explicit commit in scheduler/background tasks |
| Multiple handler chain broken | First handler throws, others never run | Isolate non-critical ops in try/except |
---
## Hook Registration Errors
### Hook Not Firing: Diagnosis Checklist
```
IS YOUR HOOK NOT FIRING?
│
├─► Check 1: Is the dotted path correct?
│ hooks.py: "myapp.events.sales.validate"
│ File: myapp/events/sales.py → def validate(doc, method=None):
│ COMMON MISTAKE: "myapp.events.sales_invoice.validate" when file is sales.py
│
├─► Check 2: Is the dict structure correct?
│ doc_events uses NESTED dict: {"Sales Invoice": {"validate": "path"}}
│ scheduler_events uses LIST: {"daily": ["path1", "path2"]}
│ permission_query uses FLAT dict: {"Sales Invoice": "path"}
│
├─► Check 3: Is bench restarted after hooks.py change?
│ ALWAYS run: bench restart (or bench clear-cache for dev)
│
├─► Check 4: Is the DocType name exact?
│ "Sales Invoice" NOT "SalesInvoice" NOT "sales_invoice"
│ Use exact DocType name as shown in Frappe UI
│
└─► Check 5: Is the app installed on the site?
bench --site mysite list-apps
```
### Circular Import Errors
```python
# ❌ CAUSES ImportError — circular dependency
# myapp/hooks.py imports from myapp.events
# myapp/events/sales.py imports from myapp.hooks
# ✅ CORRECT — break the cycle
# Move shared constants to myapp/constants.py
# Import from constants in both hooks.py and events/
```
**Rule**: NEVER import from hooks.py in your event handlers. hooks.py is read by the framework, not imported by your code.
### Wrong Dict Structure by Hook Type
```python
# ❌ WRONG — doc_events needs nested dict, not flat
doc_events = {
"Sales Invoice": "myapp.events.validate" # WRONG: string, not dict
}
# ✅ CORRECT
doc_events = {
"Sales Invoice": {
"validate": "myapp.events.sales.validate"
}
}
# ❌ WRONG — scheduler_events daily needs list
scheduler_events = {
"daily": "myapp.tasks.daily_sync" # WRONG: string, not list
}
# ✅ CORRECT
scheduler_events = {
"daily": ["myapp.tasks.daily_sync"]
}
# ❌ WRONG — cron needs nested dict with list values
scheduler_events = {
"cron": ["0 9 * * *", "myapp.tasks.morning"] # WRONG structure
}
# ✅ CORRECT
scheduler_events = {
"cron": {
"0 9 * * 1-5": ["myapp.tasks.morning_report"]
}
}
```
---
## app_include_js / app_include_css Errors
```python
# ❌ WRONG — missing assets/ prefix
app_include_js = "js/myapp.js"
# ❌ WRONG — using Python module path instead of file path
app_include_js = "myapp.public.js.myapp"
# ✅ CORRECT — full asset path
app_include_js = "assets/myapp/js/myapp.js"
# ✅ CORRECT — multiple files as list
app_include_js = ["assets/myapp/js/app.js", "assets/myapp/js/utils.js"]
app_include_css = "assets/myapp/css/myapp.css"
```
**Diagnosis**: If JS/CSS not loading, check browser DevTools Network tab for 404. Run `bench build` after adding new files. ALWAYS verify the file exists at `myapp/public/js/myapp.js`.
---
## scheduler_events Not Running
### Diagnosis Steps
```bash
# Step 1: Is scheduler enabled?
bench scheduler status
# If disabled: bench scheduler enable
# Step 2: Are workers running?
bench doctor
# Look for: "Workers online: X"
# If 0: bench start (dev) or supervisorctl restart all (prod)
# Step 3: Check Scheduled Job Log
# In Frappe UI: /api/method/frappe.client.get_list?doctype=Scheduled Job Log&limit=5
# Step 4: Check Error Log for task failures
# In Frappe UI: /app/error-log
# Step 5: Is the task registered?
bench execute frappe.utils.scheduler.get_all_tasks
```
### Common Scheduler Failures
```python
# ❌ PROBLEM: Task runs but changes not persisted
def daily_sync():
for item in frappe.get_all("Item", limit=100):
frappe.db.set_value("Item", item.name, "synced", 1)
# MISSING: frappe.db.commit() — ALL changes lost!
# ✅ FIX: ALWAYS commit in scheduler tasks
def daily_sync():
for item in frappe.get_all("Item", limit=100):
frappe.db.set_value("Item", item.name, "synced", 1)
frappe.db.commit()
# ❌ PROBLEM: Task fails silently — no debugging possible
def daily_task():
t