Token导航 LogoToken导航TokenDH.com
待分类权限需确认github未标认证来源可访问许可证需确认审计通过

erpnext-errors-hookserpnext 错误挂钩

Agent Skill

erpnext-errors-hooks 用于记录任务执行中的错误、用户纠正、经验和能力缺口,适合在 Codex、Claude、Cursor、Gemini CLI 中希望让 Agent 持续沉淀问题、修正和最佳实践时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

1,069

周安装

45

GitHub Stars

87

下载量

374
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:erpnext-errors-hooks(erpnext 错误挂钩)
来源仓库:https://github.com/openaec-foundation/erpnext_anthropic_claude_development_skill_package
仓库路径:skills/erpnext-errors-hooks
安装命令:
npx skills add https://github.com/openaec-foundation/erpnext_anthropic_claude_development_skill_package --skill erpnext-errors-hooks
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/openaec-foundation/erpnext_anthropic_claude_development_skill_package --skill erpnext-errors-hooks

简介

定义 hooks.py 配置中的错误处理策略,支持周期性任务和文档生命周期事件。

  • 适用于 Codex、Claude、Cursor、Gemini CLI 中扩展系统功能时的稳定性保障。
  • 可查阅原始文档了解 silent hooks 的风险及链式调用失败的传播机制。
  • 安装前建议评估 hooks 执行频率,避免因单点故障影响整体系统可用性。
  • erpnext-errors-hooks 属于待分类类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

ERPNext Hooks - Error Handling

This skill covers error handling patterns for hooks.py configurations. For syntax, see erpnext-syntax-hooks. For implementation workflows, see erpnext-impl-hooks.

Version: v14/v15/v16 compatible


Hooks Error Handling Overview

┌─────────────────────────────────────────────────────────────────────┐
│ HOOKS HAVE UNIQUE ERROR HANDLING CHARACTERISTICS                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│ ✅ Full Python power (try/except, raise)                            │
│ ⚠️ Multiple handlers in chain - one failure affects others         │
│ ⚠️ Some hooks are silent (scheduler, permission_query)             │
│ ⚠️ Transaction behavior varies by hook type                        │
│                                                                     │
│ Key differences from controllers:                                   │
│ • doc_events runs AFTER controller methods                          │
│ • Multiple apps can register handlers (order matters!)              │
│ • Scheduler has NO user feedback - logging is critical              │
│ • Permission hooks should NEVER throw errors                        │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Main Decision: Error Handling by Hook Type

┌─────────────────────────────────────────────────────────────────────────┐
│ WHICH HOOK TYPE ARE YOU USING?                                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│ ► doc_events (validate, on_update, on_submit, etc.)                     │
│   └─► Same as controllers: frappe.throw() rolls back in validate        │
│   └─► Multiple handlers: first error stops chain                        │
│   └─► Isolate non-critical operations in try/except                     │
│                                                                         │
│ ► scheduler_events (daily, hourly, cron)                                │
│   └─► NO user feedback - frappe.log_error() is essential                │
│   └─► ALWAYS use try/except around operations                           │
│   └─► MUST call frappe.db.commit() manually                             │
│                                                                         │
│ ► permission_query_conditions                                           │
│   └─► NEVER throw errors - return empty string on error                 │
│   └─► Silent failures break list views                                  │
│   └─► Log errors but return safe fallback                               │
│                                                                         │
│ ► has_permission                                                        │
│   └─► NEVER throw errors - return False on error                        │
│   └─► Return None to defer to default permission                        │
│                                                                         │
│ ► override_doctype_class / extend_doctype_class                         │
│   └─► ALWAYS call super() in try/except                                 │
│   └─► Parent errors should usually propagate                            │
│                                                                         │
│ ► extend_bootinfo                                                       │
│   └─► Errors break page load entirely!                                  │
│   └─► ALWAYS wrap in try/except with fallback                           │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

doc_events Error Handling

Transaction Behavior (Same as Controllers)

Eventfrappe.throw() Effect
validate✅ Full rollback - document NOT saved
before_save✅ Full rollback - document NOT saved
on_update⚠️ Document IS saved, error shown
after_insert⚠️ Document IS saved, error shown
on_submit⚠️ docstatus=1, error shown
on_cancel⚠️ docstatus=2, error shown

Multiple Handler Chain

# hooks.py - Multiple apps can register handlers
# App A
doc_events = {
    "Sales Invoice": {
        "validate": "app_a.events.validate_si"  # Runs first
    }
}

# App B
doc_events = {
    "Sales Invoice": {
        "validate": "app_b.events.validate_si"  # Runs second
    }
}

# If App A throws error, App B's handler NEVER runs!

Pattern: Validate Handler

# myapp/events/sales_invoice.py
import frappe
from frappe import _

def validate(doc, method=None):
    """Validate handler with proper error handling."""
    errors = []

    # Collect validation errors
    if doc.grand_total < 0:
        errors.append(_("Total cannot be negative"))

    if doc.custom_field and not doc.customer:
        errors.append(_("Customer required when custom field is set"))

    # Throw all at once
    if errors:
        frappe.throw("<br>".join(errors))

Pattern: on_update Handler (Isolated Operations)

def on_update(doc, method=None):
    """Post-save handler with isolated operations."""
    # Critical operation - let errors propagate
    update_linked_records(doc)

    # Non-critical operations - isolate errors
    try:
        send_notification(doc)
    except Exception:
        frappe.log_error(
            frappe.get_traceback(),
            f"Notification failed for {doc.name}"
        )

    try:
        sync_to_external(doc)
    except Exception:
        frappe.log_error(
            frappe.get_traceback(),
            f"External sync failed for {doc.name}"
        )

scheduler_events Error Handling

Critical: No User Feedback!

┌─────────────────────────────────────────────────────────────────────┐
│ ⚠️  SCHEDULER TASKS HAVE NO USER - LOGGING IS ESSENTIAL             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│ • No one sees frappe.throw() - task just fails silently             │
│ • No automatic email on failure (unless configured)                 │
│ • frappe.log_error() is your ONLY debugging tool                    │
│ • Always commit changes manually                                    │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Pattern: Scheduler Task with Error Handling

# myapp/tasks.py
import frappe

def daily_sync():
    """Daily sync task with comprehensive error handling."""
    results = {
        "processed": 0,
        "errors": []
    }

    try:
        # Get records to process (ALWAYS with limit!)
        records = frappe.get_all(
            "Sales Invoice",
            filters={"sync_status": "Pending"},
            limit=500
        )

        for record in records:
            try:
                process_record(record.name)
                results["processed"] += 1
            except Exception as e:
                results["errors"].append(f"{record.name}: {str(e)}")
                frappe.log_error(
                    frappe.get_traceback(),
                    f"Sync error: {record.name}"
                )

        # REQUIRED: Commit changes
        frappe.db.commit()

    except Exception as e:
        # Log fatal errors
        frappe.log_error(
            frappe.get_traceback(),
            "Daily Sync Fatal Error"
        )
        return

    # Log summary
    if results["errors"]:
        summary = f"Processed: {results['processed']}, Errors: {len(results['errors'])}"
        frappe.log_error(
            summary + "\n\n" + "\n".join(results["errors"][:50]),
            "Daily Sync Summary"
        )

Pattern: Scheduler with Batch Commits

def process_large_dataset():
    """Process large dataset with periodic commits."""
    BATCH_SIZE = 100

    try:
        records = frappe.get_all("Item", limit=5000)
        total = len(records)

        for i in range(0, total, BATCH_SIZE):
            batch = records[i:i + BATCH_SIZE]

            for record in batch:
                try:
                    update_item(record.name)
                except Exception:
                    frappe.log_error(
                        frappe.get_traceback(),
                        f"Item update error: {record.name}"
                    )

            # Commit after each batch
            frappe.db.commit()

    except Exception:
        frappe.log_error(frappe.get_traceback(), "Batch Processing Error")

Permission Hooks Error Handling

permission_query_conditions - NEVER Throw!

# ❌ WRONG - Breaks list view entirely!
def query_conditions(user):
    if not user:
        frappe.throw("User required")  # DON'T DO THIS!
    return f"owner = '{user}'"

# ✅ CORRECT - Return safe fallback
def query_conditions(user):
    """Permission query with error handling."""
    try:
        if not user:
            user = frappe.session.user

        if "System Manager" in frappe.get_roles(user):
            return ""  # No restrictions

        return f"`tabSales Invoice`.owner = {frappe.db.escape(user)}"

    except Exception:
        frappe.log_error(
            frappe.get_traceback(),
            "Permission Query Error"
        )
        # Safe fallback - restrict to own records
        return f"`tabSales Invoice`.owner = {frappe.db.escape(frappe.session.user)}"

has_permission - NEVER Throw!

# ❌ WRONG - Breaks document access!
def has_permission(doc, user=None, permission_type=None):
    if doc.status == "Locked":
        frappe.throw("Document is locked")  # DON'T DO THIS!

# ✅ CORRECT - Return boolean or None
def has_permission(doc, user=None, permission_type=None):
    """Document permission check with error handling."""
    try:
        user = user or frappe.session.user

        # Deny access to locked documents
        if doc.status == "Locked" and permission_type == "write":
            return False

        # Custom logic
        if permission_type == "delete":
            if doc.has_linked_records():
                return False

        # Return None to defer to default permission system
        return None

    except Exception:
        frappe.log_error(
            frappe.get_traceback(),
            f"Permission check error: {doc.name}"
        )
        # Safe fallback - defer to default
        return None

Override Hooks Error Handling

override_doctype_class

# myapp/overrides.py
from erpnext.selling.doctype.sales_order.sales_order import SalesOrder
import frappe
from frappe import _

class CustomSalesOrder(SalesOrder):
    def validate(self):
        """Override with proper error handling."""
        # ALWAYS call parent first in try/except
        try:
            super().validate()
        except frappe.ValidationError:
            # Re-raise validation errors
            raise
        except Exception as e:
            frappe.log_error(frappe.get_traceback(), "Parent Validate Error")
            raise

        # Custom validation
        self.custom_validate()

    def custom_validate(self):
        if self.custom_approval_required and not self.custom_approved:
            frappe.throw(_("Approval required before saving"))

extend_doctype_class (V16+)

# myapp/extends.py
import frappe
from frappe import _

class SalesOrderExtend:
    """Extension class - only add new methods."""

    def custom_approval_check(self):
        """New method with error handling."""
        try:
            if not self.custom_approver:
                frappe.throw(_("Approver not set"))

            approver = frappe.get_doc("User", self.custom_approver)
            if not approver.enabled:
                frappe.throw(_("Approver is disabled"))

        except frappe.DoesNotExistError:
            frappe.throw(_("Approver not found"))

extend_bootinfo Error Handling

Critical: Errors Break Page Load!

# ❌ WRONG - Unhandled error breaks desk entirely!
def extend_boot(bootinfo):
    settings = frappe.get_single("My Settings")  # What if it doesn't exist?
    bootinfo.my_config = settings.config

# ✅ CORRECT - Always handle errors
def extend_boot(bootinfo):
    """Extend bootinfo with error handling."""
    try:
        if frappe.db.exists("My Settings", "My Settings"):
            settings = frappe.get_single("My Settings")
            bootinfo.my_config = settings.config or {}
        else:
            bootinfo.my_config = {}

    except Exception:
        frappe.log_error(
            frappe.get_traceback(),
            "Bootinfo Extension Error"
        )
        # Safe fallback
        bootinfo.my_config = {}

Critical Rules

✅ ALWAYS

  1. Use try/except in scheduler tasks - No user feedback otherwise
  2. Call frappe.db.commit() in scheduler - Changes aren't auto-saved
  3. Return safe fallbacks in permission hooks - Never throw
  4. Call super() in override classes - Preserve parent behavior
  5. Log errors with context - Include document name, operation
  6. Wrap extend_bootinfo in try/except - Errors break page load

❌ NEVER

  1. Don't throw in permission_query_conditions - Breaks list views
  2. Don't throw in has_permission - Breaks document access
  3. Don't assume single handler - Multiple apps can register
  4. Don't commit in doc_events - Framework handles transactions
  5. Don't ignore scheduler errors - They fail silently

Quick Reference: Error Handling by Hook

Hook TypeCan Throw?Commit?Key Pattern
doc_events (validate)✅ YES❌ NOCollect errors, throw once
doc_events (on_update)⚠️ Careful❌ NOIsolate non-critical ops
scheduler_events❌ Pointless✅ YESTry/except + log_error
permission_query_conditions❌ NEVER❌ NOReturn "" on error
has_permission❌ NEVER❌ NOReturn None on error
extend_bootinfo❌ NEVER❌ NOTry/except + fallback
override class✅ YES❌ NOsuper() + re-raise

Reference Files

FileContents
references/patterns.mdComplete error handling patterns
references/examples.mdFull working examples
references/anti-patterns.mdCommon mistakes to avoid

See Also

  • erpnext-syntax-hooks - Hooks syntax
  • erpnext-impl-hooks - Implementation workflows
  • erpnext-errors-controllers - Controller error handling
  • erpnext-errors-serverscripts - Server Script error handling

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

35.24%
按下载量换算132

Claude

27.82%
按下载量换算104

Cursor

18.94%
按下载量换算71

Gemini CLI

8.71%
按下载量换算33

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

权限需确认

当前来源未能明确判断权限范围,默认进入异常复核队列。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills