Skill125 repo starsupdated 2mo ago
frappe-errors-clientscripts
>
Install in Claude Code
Copygit clone --depth 1 https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package /tmp/frappe-errors-clientscripts && cp -r /tmp/frappe-errors-clientscripts/skills/source/errors/frappe-errors-clientscripts ~/.claude/skills/frappe-errors-clientscriptsThen start a new Claude Code session; the skill loads automatically.
Definition
SKILL.md
# Client Script Errors — Diagnosis and Resolution
Cross-refs: `frappe-syntax-clientscripts` (syntax), `frappe-impl-clientscripts` (workflows), `frappe-errors-serverscripts` (server-side).
---
## Error Diagnosis Flowchart
```
ERROR IN CLIENT SCRIPT
│
├─► TypeError: Cannot read properties of undefined
│ ├─► "frm.doc.fieldname" → Field does not exist on DocType
│ ├─► "r.message.value" → Server returned null/error
│ └─► "row.fieldname" in child table → Row not fetched correctly
│
├─► frappe.call fails silently
│ ├─► Missing error callback → Add error handler
│ ├─► 403 Forbidden → Method not whitelisted (@frappe.whitelist)
│ ├─► 417 Expectation Failed → Server-side frappe.throw()
│ └─► 401 Unauthorized → Session expired or CSRF token invalid
│
├─► Uncaught (in promise) → Missing try/catch on async frappe.call
│
├─► Field appears blank after set_value → Timing issue (setup vs refresh)
│
├─► cur_frm is undefined → Using cur_frm in list/report context
│
└─► frappe.throw() does not prevent save → Used outside validate event
```
---
## Error Message → Cause → Fix Table
| Error Message | Cause | Fix |
|---------------|-------|-----|
| `TypeError: Cannot read properties of undefined (reading 'fieldname')` | Field does not exist on DocType or doc not loaded | ALWAYS check `frm.doc` exists before accessing fields |
| `TypeError: frm.set_value is not a function` | Using `cur_frm` shortcut that is undefined | ALWAYS use the `frm` parameter from event handler |
| `Uncaught (in promise)` | Unhandled async rejection from frappe.call | ALWAYS wrap async calls in try/catch |
| `CSRFTokenError` / `403 with CSRF` | Token mismatch after session timeout | ALWAYS use `frappe.call()` (handles CSRF automatically) |
| `Not permitted` / 403 on frappe.call | Server method missing `@frappe.whitelist()` | ALWAYS add `@frappe.whitelist()` decorator to API methods |
| `frappe.throw() not preventing save` | `frappe.throw()` used outside `validate` event | ALWAYS use `frappe.throw()` only in `validate` |
| `field not found: xyz` in set_query | Fieldname typo or field not in child table | Verify exact fieldname against DocType definition |
| `row.item_code is undefined` | Accessing child row wrong — `locals` not synced | Use `frappe.get_doc(cdt, cdn)` in child table events |
| `frm.set_value not working` | Called in `setup` before form fully loaded | Move field-setting logic to `refresh` event |
| `Maximum call stack exceeded` | Circular trigger — field change fires own handler | Use `frm.flags` guard to break recursion |
---
## Critical Error Patterns
### 1. cur_frm vs frm: The #1 Beginner Mistake
```javascript
// ❌ WRONG — cur_frm is undefined in many contexts
frappe.ui.form.on('Sales Order', {
customer(frm) {
cur_frm.set_value('territory', 'Default'); // BREAKS in list view
}
});
// ✅ CORRECT — ALWAYS use the frm parameter
frappe.ui.form.on('Sales Order', {
customer(frm) {
frm.set_value('territory', 'Default');
}
});
```
**Rule**: NEVER use `cur_frm`. ALWAYS use the `frm` parameter passed to every event handler.
### 2. Async/Await: Silent Failure Without try/catch
```javascript
// ❌ WRONG — Unhandled rejection crashes silently
frappe.ui.form.on('Sales Order', {
async customer(frm) {
let r = await frappe.call({
method: 'myapp.api.get_data',
args: { customer: frm.doc.customer }
});
frm.set_value('credit_limit', r.message.limit); // r.message may be null
}
});
// ✅ CORRECT — try/catch with null check
frappe.ui.form.on('Sales Order', {
async customer(frm) {
if (!frm.doc.customer) return;
try {
let r = await frappe.call({
method: 'myapp.api.get_data',
args: { customer: frm.doc.customer }
});
if (r.message) {
frm.set_value('credit_limit', r.message.limit || 0);
}
} catch (error) {
console.error('Customer fetch failed:', error);
frappe.show_alert({
message: __('Could not load customer details'),
indicator: 'red'
}, 5);
}
}
});
```
### 3. Child Table Access: Wrong Pattern
```javascript
// ❌ WRONG — frm.doc.items[0] may not reflect latest state
frappe.ui.form.on('Sales Order Item', {
item_code(frm, cdt, cdn) {
let row = frm.doc.items.find(r => r.name === cdn); // fragile
row.rate = 100; // Does not trigger UI refresh
}
});
// ✅ CORRECT — Use frappe.get_doc and frappe.model.set_value
frappe.ui.form.on('Sales Order Item', {
item_code(frm, cdt, cdn) {
let row = frappe.get_doc(cdt, cdn);
if (!row.item_code) return;
frappe.model.set_value(cdt, cdn, 'rate', 100); // Triggers refresh
}
});
```
### 4. Timing: setup vs refresh
```javascript
// ❌ WRONG — set_value in setup, form not ready
frappe.ui.form.on('Sales Order', {
setup(frm) {
frm.set_value('company', 'My Company'); // May not work
}
});
// ✅ CORRECT — set_query in setup, set_value in refresh/onload
frappe.ui.form.on('Sales Order', {
setup(frm) {
// Filters belong in setup
frm.set_query('customer', () => ({ filters: { disabled: 0 } }));
},
refresh(frm) {
// Value changes belong in refresh (or onload for new docs)
if (frm.is_new()) {
frm.set_value('company', 'My Company');
}
}
});
```
### 5. frappe.throw() Scope: Only Works in validate
```javascript
// ❌ WRONG — throw in customer change does NOT prevent save
frappe.ui.form.on('Sales Order', {
customer(frm) {
if (!frm.doc.customer) {
frappe.throw(__('Customer required')); // Stops script, NOT save
}
}
});
// ✅ CORRECT — throw in validate prevents save
frappe.ui.form.on('Sales Order', {
customer(frm) {
if (!frm.doc.customer) {
frappe.msgprint({ message: __('Customer required'), indicator: 'orange' });