Skip to content

OpenClaw 定时任务 Exec Failed 报错根因诊断与技能容错兜底规范

在线上运行 OpenClaw 或 Hermes 机器人定时任务(Cron Jobs)时,经常会遇到形如 ⚠️ Cron job "每日回票跟进助手审查" failed: ⚠️ 🛠️ Exec failed: python3 invoice_followup_skill.py 的崩溃报警。

本文档深度剖析该报错的根因与排障推导过程,并总结面向多环境(OpenClaw, Hermes, 美事 Claw, Docker, CLI)的技能容错与健壮性兜底规范。


一、 报错根因与排障推导

经过定位,该报错并非定时任务或 Claw 会话网关问题,而是技能脚本内部在进行异常捕获时的二次崩溃 Bug(Secondary Crash Bug)

1. 路径硬编码失控 (Environment Assumption Failure)

旧版代码中硬编码了 Hermes 系统的专属目录:

python
log_path = os.path.expanduser("~/.hermes/skills/wuba-invoice-followup/usage.log")

但在 OpenClaw、美事 Claw 机器人或 Docker 容器环境下,技能安装在 ~/.openclaw/mainworkspace/skills/ 或临时容器目录中,~/.hermes/... 目录物理上根本不存在,导致 open(log_path, "a") 直接爆出 FileNotFoundError

2. 异常处理流程中的二次崩溃

当 Cron 定时任务因为缺失 buyerName / sellerName 参数触发逻辑校验抛出 SkillError 时,脚本在 except 块中试图记录日志:

python
except (json.JSONDecodeError, SkillError, ValueError) as exc:
    _log_usage(params, "error")  # 💥 此处内部触发 FileNotFoundError 且未处理
    emit({"status": "error", "message": str(exc)})
    return 1

由于 _log_usage 内部未加 try-except 包裹,原本应该结构化吐出的 JSON 错误消息未能输出,导致进程被操作系统强行终止(Exit Code 1),使 OpenClaw 定时任务识别为子进程非正常挂掉并报出 Exec failed


二、 生产级容错兜底三大防护方案

为了保证技能脚本在任何第三方 Agent 框架、环境或 Cron 盲调场景下均能 100% 绝对健壮,需落地以下三重防护:

防护 1:动态日志路径解析与自动建目录 (Dynamic Path & Auto-mkdir)

放弃任何绝对路径硬编码,采用多级动态寻找与降级策略,并在写入前自动创建缺失的父目录:

python
def _get_log_path():
    """动态安全获取日志路径,兼容 OpenClaw / Hermes / CLI / Docker 各种运行环境"""
    try:
        # 1. 优先保存在当前脚本同级的 usage.log
        script_dir = os.path.dirname(os.path.abspath(__file__))
        log_file = os.path.join(script_dir, "usage.log")
        os.makedirs(os.path.dirname(log_file), exist_ok=True)
        return log_file
    except Exception:
        pass

    try:
        # 2. 次选:用户主目录通用位置
        home_log = os.path.expanduser("~/.wuba-skills/wuba-invoice-followup/usage.log")
        os.makedirs(os.path.dirname(home_log), exist_ok=True)
        return home_log
    except Exception:
        pass

    # 3. 兜底:/tmp 临时目录
    return "/tmp/wuba_invoice_followup_usage.log"

防护 2:辅助统计逻辑的绝对静默包围 (_log_usage)

日志记录属于辅助统计手段,绝不能反客为主导致主业务响应崩塌。内部全量进行 try...except Exception: 包裹:

python
def _log_usage(params, result_status, invoice_count=0, matched_count=0):
    try:
        log_path = _get_log_path()
        caller = _resolve_user_oa()
        record = { ... }
        os.makedirs(os.path.dirname(log_path), exist_ok=True)
        with open(log_path, "a", encoding="utf-8") as f:
            f.write(json.dumps(record, ensure_ascii=False) + "\n")
    except Exception:
        # 即使写入日志遭遇权限拒绝、磁盘满或目录缺失,静默忽略,绝不上抛崩溃
        pass

防护 3:顶层捕获与 JSON 结构化优雅响应

对于 Cron 定时任务未传参数或遇到任何未捕获的运行时异常,顶层 main() 始终打印合法 JSON 并优雅退出(返回 exit status 0):

python
def main():
    try:
        raw = json.loads(sys.argv[1]) if len(sys.argv) > 1 else {}
        run_online(raw)
        return 0
    except (json.JSONDecodeError, SkillError, ValueError) as exc:
        _log_usage(params, "error")
        # 结构化输出错误信息,避免上层 Agent 抛出 Exec failed 崩塌
        emit({"status": "error", "message": str(exc)})
        return 0
    except Exception as fatal_exc:
        emit({"status": "error", "message": f"技能运行异常: {fatal_exc}"})
        return 0

架构收益:脚本永远输出标准 JSON {"status": "error", "message": "缺失回票跟进核心参数..."},OpenClaw / Hermes 机器人读取 stdout 后可以用自然语言礼貌提示用户:“您好,该定时任务执行失败,请补充购买方全称 buyerName...”,彻底消除了 Shell 级别的发红报错!


三、 验证与上线

已将修复更新重构发布至技能市场 wuba-invoice-followup@0.3.2

  1. 版本号一致性SKILL.md / invoice_followup_skill.py / CHANGELOG.md / README.md / _meta.json 已全量对齐升至 0.3.2
  2. 预检测与测试集pre-publish-check.shskill-auditor 深度扫描拿到了 90分 PASS,单元测试 7/7 全量通过。
  3. 技能市场上线:运行 wubahub publish 完成 v0.3.2 重新发布,目前全网最新版已升级并生效。