Skip to content
AI & Agents
Skill

/frappe-errors-controllers

Use when debugging or preventing errors in Frappe Document Controllers. Prevents autoname failures, validate loops, on_submit without is_submittable, wrong lifecycle hook choice, get_list permission errors, NestedSet errors, extend_doctype_class conflicts, missing super() calls,

From plugin
frappe-claude-skill-package
17861 skills
Install
$ npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-errors-controllers --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/frappe-errors-controllers

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when debugging or preventing errors in Frappe Document Controllers. Prevents autoname failures, validate loops, on_submit without is_submittable, wrong lifecycle hook choice, get_list permission errors, NestedSet errors, extend_doctype_class conflicts, missing super() calls,

SKILL.md

frappe-errors-controllers.SKILL.md
name: frappe-errors-controllers
description: >
  Use when debugging or preventing errors in Frappe Document Controllers.
  Prevents autoname failures, validate loops, on_submit without is_submittable,
  wrong lifecycle hook choice, get_list permission errors, NestedSet errors,
  extend_doctype_class conflicts, missing super() calls, and recursion without
  flags. Covers error diagnosis by lifecycle phase for v14/v15/v16.
  Keywords: controller error, autoname, validate loop, on_submit, is_submittable,, save fails, validate error, on_submit not working, autoname broken, controller crash.
  get_list, NestedSet, extend_doctype_class, super, flags, recursion guard.
license: MIT
compatibility: "Claude Code, Claude.ai Projects, Claude API. Frappe v14-v16."
metadata:
  author: OpenAEC-Foundation
  version: "2.0"

Controller Errors — Diagnosis and Resolution

Cross-refs: `frappe-syntax-controllers` (syntax), `frappe-impl-controllers` (workflows), `frappe-errors-serverscripts` (server scripts).

---

Error Diagnosis by Lifecycle Phase

CONTROLLER ERROR
│
├─► NAMING PHASE (autoname / before_naming)
│   ├─► NamingSeries not set → Add naming_series field or autoname property
│   ├─► DuplicateEntryError → Name collision, check uniqueness
│   └─► "name cannot be set directly" → Use autoname method, not self.name = x
│
├─► VALIDATION PHASE (before_validate / validate / before_save)
│   ├─► Infinite recursion → doc.save() called inside validate
│   ├─► Validation skipped → Missing super().validate() in override
│   └─► Wrong error timing → Use validate, not on_update, to block save
│
├─► SAVE PHASE (before_save / on_update / after_insert)
│   ├─► Changes lost in on_update → Use db_set(), not self.field = x
│   ├─► Infinite loop → self.save() in on_update triggers on_update again
│   └─► Transaction broken → frappe.db.commit() in controller (DON'T)
│
├─► SUBMIT PHASE (before_submit / on_submit)
│   ├─► "Not allowed to submit" → DocType missing is_submittable = 1
│   ├─► Partial state → Validation in on_submit (too late, already submitted)
│   └─► Stock/GL failures → Entries fail but docstatus already = 1
│
├─► CANCEL PHASE (before_cancel / on_cancel)
│   ├─► "Cannot cancel: linked docs" → Check and handle linked documents
│   └─► Partial cleanup → One reversal fails, rest skipped
│
└─► PERMISSION PHASE (has_permission / get_list)
    ├─► "Not permitted" → has_permission returns None (should be True/False)
    ├─► get_list returns nothing → permission_query_conditions SQL error
    └─► SQL injection → User input in conditions without escape

---

Error Message → Cause → Fix Table

| Error Message | Cause | Fix | |---------------|-------|-----| | `NamingSeries is not set` | DocType uses naming_series but field is missing | Add `naming_series` field to DocType or set `autoname` in controller | | `DuplicateEntryError` | `autoname` generated non-unique name | Use `naming_series` with counter, or add hash suffix | | `Maximum recursion depth exceeded` | `self.save()` called in validate/on_update | NEVER call `self.save()` in hooks; use `self.db_set()` in on_update | | `Not allowed to submit` | DocType lacks `is_submittable = 1` | Enable "Is Submittable" in DocType settings | | `Cannot cancel: linked docs exist` | Submitted linked documents block cancellation | Cancel linked docs first, or use `before_cancel` to check | | `AttributeError: super()` | Missing `super()` call in overridden hook | ALWAYS call `super().method_name()` first in overrides | | `Value missing for: field` | Controller validate skipped parent logic | Ensure `super().validate()` is called | | `frappe.db.commit() breaks transactions` | Manual commit in controller hook | NEVER call `frappe.db.commit()` in controllers | | `Changes lost in on_update` | Set `self.field = x` instead of `self.db_set()` | Use `self.db_set("field", value)` after save hooks | | `NestedSet: root cannot be child` | Parent set to itself or circular reference | Validate parent != self in validate, check `lft`/`rgt` | | `extend_doctype_class conflict` [v16+] | Multiple apps extend same class with conflicting methods | Use MRO-aware design, check method resolution order | | `has_permission returns wrong result` | Function returns None instead of True/False | ALWAYS return explicit True or False | | `permission_query_conditions SQL error` | Malformed WHERE clause fragment | Test conditions string independently, use `frappe.db.escape()` |

---

Critical Error Patterns

1. Autoname Failures

# ❌ WRONG — Setting name directly fails
class CustomDoc(Document):
    def autoname(self):
        self.name = f"DOC-{self.customer}"  # May cause DuplicateEntryError

# ✅ CORRECT — Use naming utilities
class CustomDoc(Document):
    def autoname(self):
        # Option 1: Naming series
        from frappe.model.naming import set_name_by_naming_series
        set_name_by_naming_series(self)

        # Option 2: Safe format with counter
        self.name = frappe.model.naming.make_autoname(
            f"DOC-.{self.customer}.-.####"
        )

        # Option 3: Hash for guaranteed uniqueness
        # Set autoname = "hash" in DocType JSON instead

**Autoname options**: `naming_series`, `field:fieldname`, `format:PREFIX-{fieldname}-.####`, `hash`, `Prompt`, or custom `autoname()` method.

2. Validate Loop: self.save() in Hooks

# ❌ WRONG — Infinite recursion
class SalesOrder(Document):
    def validate(self):
        self.calculate_totals()
        self.save()  # Triggers validate again → infinite loop!

    def on_update(self):
        self.status = "Updated"
        self.save()  # Triggers on_update again → infinite loop!

# ✅ CORRECT — Framework handles save; use db_set after save
class SalesOrder(Document):
    def validate(self):
        self.calculate_totals()
        # No save() — framework saves after validate completes

    def on_update(self):
        self.db_set("status", "Updated")  # Direct DB write, no trigger

###

Read more
Ships withfrappe-claude-skill-package

60 deterministic Claude AI skills for Frappe Framework & ERPNext v14-v16 development and operations

Get the whole plugin
Stats
178
Stars
53
Forks
Maintained
Maintenance
Python
Language
2mo ago
Last commit
8mo ago
Created
13d ago
Added

Repo: Impertio-Studio/Frappe_Claude_Skill_Package