diff --git a/s04_hooks/README.en.md b/s04_hooks/README.en.md index 1a910309b..d2123b4e2 100644 --- a/s04_hooks/README.en.md +++ b/s04_hooks/README.en.md @@ -57,6 +57,7 @@ Extensions are added via `register_hook()`. The loop only calls `trigger_hooks() ## How It Works **Hook registry**: a dict mapping event names to callback lists. +**Fault isolation**: Individual hook failures don't prevent other hooks from executing. ```python HOOKS = { @@ -70,11 +71,24 @@ def register_hook(event: str, callback): HOOKS[event].append(callback) def trigger_hooks(event: str, *args): + """Execute all registered hooks for an event with fault isolation. + + Individual hook failures do not prevent other hooks from executing. + Returns the first non-None blocking result, or None if all hooks pass. + """ + blocking_result = None + for callback in HOOKS[event]: - result = callback(*args) - if result is not None: # return value ≠ None → hook says "stop" - return result - return None + try: + result = callback(*args) + # Capture the first blocking result to prevent execution + if result is not None and blocking_result is None: + blocking_result = result + except Exception as e: + # Print error but continue executing remaining hooks + print(f"\033[31m[HOOK ERROR] {event} -> {callback.__name__}: {e}\033[0m") + + return blocking_result ``` In the teaching version, PreToolUse returning non-None means block execution; Stop returning non-None means force continuation. UserPromptSubmit and PostToolUse return values are unused. diff --git a/s04_hooks/README.ja.md b/s04_hooks/README.ja.md index 71c659fa4..50d23e733 100644 --- a/s04_hooks/README.ja.md +++ b/s04_hooks/README.ja.md @@ -57,6 +57,7 @@ s03 のループと権限ロジックは完全に保持される。唯一の変 ## 仕組み **フック登録簿**:イベント名をコールバックリストにマッピングする辞書。 +**例外分離**:個々のフック失敗が他のフック実行を妨げない。 ```python HOOKS = { @@ -70,11 +71,24 @@ def register_hook(event: str, callback): HOOKS[event].append(callback) def trigger_hooks(event: str, *args): + """Execute all registered hooks for an event with fault isolation. + + Individual hook failures do not prevent other hooks from executing. + Returns the first non-None blocking result, or None if all hooks pass. + """ + blocking_result = None + for callback in HOOKS[event]: - result = callback(*args) - if result is not None: # 戻り値 ≠ None → フックが「止め」と指示 - return result - return None + try: + result = callback(*args) + # Capture the first blocking result to prevent execution + if result is not None and blocking_result is None: + blocking_result = result + except Exception as e: + # Print error but continue executing remaining hooks + print(f"\033[31m[HOOK ERROR] {event} -> {callback.__name__}: {e}\033[0m") + + return blocking_result ``` 教学版では、PreToolUse の非 None 戻り値は実行阻止を意味し、Stop の非 None 戻り値は強制続行を意味する。UserPromptSubmit と PostToolUse の戻り値は未使用。 diff --git a/s04_hooks/README.md b/s04_hooks/README.md index fc0e2e4f3..c11008a12 100644 --- a/s04_hooks/README.md +++ b/s04_hooks/README.md @@ -57,6 +57,7 @@ s03 的循环和权限逻辑完全保留。唯一的变动是把 `check_permissi ## 工作原理 **hook 注册表**:一个字典,事件名映射到回调列表。 +**异常隔离**:单个 hook 失败不影响其他 hook 执行。 ```python HOOKS = { @@ -70,11 +71,24 @@ def register_hook(event: str, callback): HOOKS[event].append(callback) def trigger_hooks(event: str, *args): + """Execute all registered hooks for an event with fault isolation. + + Individual hook failures do not prevent other hooks from executing. + Returns the first non-None blocking result, or None if all hooks pass. + """ + blocking_result = None + for callback in HOOKS[event]: - result = callback(*args) - if result is not None: # 返回值 ≠ None → hook 说"停" - return result - return None + try: + result = callback(*args) + # Capture the first blocking result to prevent execution + if result is not None and blocking_result is None: + blocking_result = result + except Exception as e: + # Print error but continue executing remaining hooks + print(f"\033[31m[HOOK ERROR] {event} -> {callback.__name__}: {e}\033[0m") + + return blocking_result ``` 教学版中,PreToolUse 的非 None 返回值会阻止本次工具执行,Stop 的非 None 返回值会强制续跑。UserPromptSubmit 和 PostToolUse 的返回值未被使用。 diff --git a/s04_hooks/code.py b/s04_hooks/code.py index 756bf7d6d..751193b8a 100644 --- a/s04_hooks/code.py +++ b/s04_hooks/code.py @@ -162,11 +162,19 @@ def register_hook(event: str, callback): HOOKS[event].append(callback) def trigger_hooks(event: str, *args): + blocking_result = None + for callback in HOOKS[event]: - result = callback(*args) - if result is not None: # teaching shortcut: block this tool call - return result - return None + try: + result = callback(*args) + # Capture the first blocking result to prevent execution + if result is not None and blocking_result is None: + blocking_result = result + except Exception as e: + # Print error but continue executing remaining hooks + print(f"\033[31m[HOOK ERROR] {event} -> {callback.__name__}: {e}\033[0m") + + return blocking_result # s03 permission check logic, now wrapped as a hook