From d27547c12007d3dad2252a817caec4f8bb6d356f Mon Sep 17 00:00:00 2001 From: zhoukailian <2415699291@qq.com> Date: Thu, 23 Apr 2026 04:55:45 +0000 Subject: [PATCH] fix(skill): bootstrap xiaoju env for isolated cron runs Affected: docs/requirements/xiaoju-checkin-requirements.md, docs/design/xiaoju-checkin-workflow.md, checkin/xiaojuchongdian/skill/overall/SKILL.md, checkin/xiaojuchongdian/src/config.py Reference: docs/design/xiaoju-checkin-workflow.md --- .../xiaojuchongdian/skill/overall/SKILL.md | 5 ++ checkin/xiaojuchongdian/src/config.py | 76 +++++++++++++++++++ docs/design/xiaoju-checkin-workflow.md | 27 +++++++ .../xiaoju-checkin-requirements.md | 22 ++++++ 4 files changed, 130 insertions(+) diff --git a/checkin/xiaojuchongdian/skill/overall/SKILL.md b/checkin/xiaojuchongdian/skill/overall/SKILL.md index a2565f1..5e8888a 100644 --- a/checkin/xiaojuchongdian/skill/overall/SKILL.md +++ b/checkin/xiaojuchongdian/skill/overall/SKILL.md @@ -36,6 +36,11 @@ Required: - `DAILYHUB_XIAOJU_TOKEN_ID` - `DAILYHUB_XIAOJU_AM_DID` +Runtime loading rule: +- prefer already-exported process env +- if isolated cron/runtime env is missing required Xiaoju variables, auto-load defaults from common local `.env` files, especially the repository `.env` +- auto-loaded defaults must not override explicitly injected env values + When auth credentials are missing or expired, switch to `xiaoju-get-params` to refresh, then continue only after the refreshed credentials have been persisted and validated through the same config source used by downstream check-in runs. `DAILYHUB_XIAOJU_AM_DID` is a device fingerprint and is not generated by SMS login; preserve the existing value for this account, or ask the user to capture it once from real app traffic. diff --git a/checkin/xiaojuchongdian/src/config.py b/checkin/xiaojuchongdian/src/config.py index 8702782..c8c2af9 100644 --- a/checkin/xiaojuchongdian/src/config.py +++ b/checkin/xiaojuchongdian/src/config.py @@ -4,7 +4,9 @@ import logging import os +import shlex from dataclasses import dataclass +from pathlib import Path logger = logging.getLogger(__name__) @@ -13,7 +15,78 @@ class ConfigError(ValueError): """Raised when required runtime configuration is missing or invalid.""" +def _parse_env_assignment(raw_line: str) -> tuple[str, str] | None: + line = raw_line.strip() + if not line or line.startswith("#"): + return None + if line.startswith("export "): + line = line[len("export ") :].strip() + if "=" not in line: + return None + key, value = line.split("=", 1) + key = key.strip() + if not key: + return None + value = value.strip() + if not value: + return key, "" + try: + parsed = shlex.split(value, comments=False, posix=True) + except ValueError: + parsed = [value] + if len(parsed) == 1: + return key, parsed[0] + return key, value + + +def _load_env_file(path: Path) -> bool: + if not path.is_file(): + return False + loaded_any = False + for raw_line in path.read_text(encoding="utf-8").splitlines(): + parsed = _parse_env_assignment(raw_line) + if not parsed: + continue + key, value = parsed + os.environ.setdefault(key, value) + loaded_any = True + if loaded_any: + logger.info("loaded Xiaoju env defaults from %s", path) + return loaded_any + + +def _bootstrap_env() -> None: + current_file = Path(__file__).resolve() + repo_root = current_file.parents[3] + candidates = [ + Path.cwd() / ".env", + repo_root / ".env", + repo_root.parent / ".env", + Path("/root/DailyHub/.env"), + Path("/root/.env"), + ] + seen: set[Path] = set() + for candidate in candidates: + resolved = candidate.resolve(strict=False) + if resolved in seen: + continue + seen.add(resolved) + _load_env_file(resolved) + + +_BOOTSTRAPPED_ENV = False + + +def _ensure_env_bootstrapped() -> None: + global _BOOTSTRAPPED_ENV + if _BOOTSTRAPPED_ENV: + return + _bootstrap_env() + _BOOTSTRAPPED_ENV = True + + def _get_required_env(name: str) -> str: + _ensure_env_bootstrapped() value = os.getenv(name, "").strip() if not value: raise ConfigError(f"missing required env: {name}") @@ -21,6 +94,7 @@ def _get_required_env(name: str) -> str: def _get_optional_env(name: str, default: str) -> str: + _ensure_env_bootstrapped() raw = os.getenv(name) if raw is None: return default @@ -36,6 +110,7 @@ def _get_optional_env(name: str, default: str) -> str: def _get_int_env(name: str, default: int) -> int: + _ensure_env_bootstrapped() raw = os.getenv(name) if raw is None or raw.strip() == "": return default @@ -46,6 +121,7 @@ def _get_int_env(name: str, default: int) -> int: def _get_float_env(name: str, default: float) -> float: + _ensure_env_bootstrapped() raw = os.getenv(name) if raw is None or raw.strip() == "": return default diff --git a/docs/design/xiaoju-checkin-workflow.md b/docs/design/xiaoju-checkin-workflow.md index a0a3c2b..5aacd77 100644 --- a/docs/design/xiaoju-checkin-workflow.md +++ b/docs/design/xiaoju-checkin-workflow.md @@ -99,6 +99,33 @@ class TaskModule(Protocol): 4. 失败原因语义化(认证失败、业务失败、网络失败) 5. CLI 输出 JSON,便于机器消费 +### 6.1 隔离运行时配置补载设计 + +问题背景: +- OpenClaw 的 isolated cron session 不保证继承登录 shell 中的环境变量 +- 小桔签到任务过去直接依赖 `os.getenv` 读取 `DAILYHUB_XIAOJU_*` +- 一旦 cron session 没有这些变量,就会在 `status` 前置阶段直接抛出 `missing required env` + +设计要求: +1. 配置模块在首次读取环境变量前,先执行一次轻量级 bootstrap +2. bootstrap 优先保留当前进程里已存在的显式环境变量 +3. 若变量缺失,则从常见本地 `.env` 位置补齐默认值 +4. `.env` 解析仅支持简单 `KEY=VALUE` / `export KEY=VALUE` 赋值格式,满足当前 DailyHub 配置源即可 +5. 仅使用 `os.environ.setdefault(...)` 注入,避免覆盖调用方显式传入的值 +6. bootstrap 应幂等,避免在单次进程中重复加载 + +候选 `.env` 路径: +- 当前工作目录 `.env` +- 仓库根目录 `.env` +- 仓库父目录 `.env` +- `/root/DailyHub/.env` +- `/root/.env` + +预期收益: +- 手动运行与 cron 隔离运行共享同一配置源 +- 失败模式从“隐式缺 env”收敛为“补载后仍缺 env” +- 降低 cron 因环境继承差异导致的伪故障 + --- ## 7. 调用建议 diff --git a/docs/requirements/xiaoju-checkin-requirements.md b/docs/requirements/xiaoju-checkin-requirements.md index 13cabb1..1319b1e 100644 --- a/docs/requirements/xiaoju-checkin-requirements.md +++ b/docs/requirements/xiaoju-checkin-requirements.md @@ -57,11 +57,29 @@ - `DAILYHUB_XIAOJU_TICKET` - `DAILYHUB_XIAOJU_TOKEN` - `DAILYHUB_XIAOJU_TOKEN_ID` +- `DAILYHUB_XIAOJU_AM_DID` - `DAILYHUB_XIAOJU_APP_ID` - `DAILYHUB_XIAOJU_AM_CHANNEL` - `DAILYHUB_XIAOJU_SOURCE` - `DAILYHUB_XIAOJU_TTID` +### 隔离运行时配置加载要求 + +为兼容 OpenClaw 的 isolated cron session,配置加载必须遵循以下顺序: + +1. 优先读取当前进程已经显式导出的环境变量 +2. 若关键小桔凭证缺失,则自动从本地常见 `.env` 配置源补载 +3. 至少必须覆盖以下候选路径: + - 当前工作目录下的 `.env` + - DailyHub 仓库根目录下的 `.env` + - `/root/DailyHub/.env` + - `/root/.env` + +补载规则: +- 仅在进程内补齐缺失变量,不覆盖已经显式注入的值 +- 补载后必须继续使用同一配置源完成后续 `status/run` 校验 +- 若补载后仍缺失关键变量,应明确返回缺失字段,不能伪装成成功执行 + ### 认证失败自愈策略 自动恢复机制覆盖两个场景: @@ -119,6 +137,7 @@ 3. 已签到场景重复执行不报错 4. 所有认证信息来自环境变量,无硬编码密钥 5. 日志中可看到每个步骤开始/结束与错误原因 +6. 在隔离环境(未预先导出 `DAILYHUB_XIAOJU_*`)下,只要本地 `.env` 完整,`status` 仍能成功读取配置并执行 --- @@ -136,6 +155,9 @@ python3 -m checkin.xiaojuchongdian.src.main run --task xiaoju.checkin --verify-r # 4) 仅查询状态 python3 -m checkin.xiaojuchongdian.src.main status --task xiaoju.checkin + +# 5) 隔离环境回归(不继承 shell 中的小桔变量) +env -i PATH="$PATH" HOME="$HOME" bash -lc 'cd /path/to/DailyHub && python3 -m checkin.xiaojuchongdian.src.main status --task xiaoju.checkin' ``` ---