对应源码 hooks.rs:179-196: match output.status.code() { Some(0) => HookCommandOutcome::Allow { message }, // 成功=允许 Some(2) => HookCommandOutcome::Deny { message }, // 2=拒绝 Some(code) => HookCommandOutcome::Warn { ... }, // 其他=警告 None => HookComman
()
| 357 | # ============================================================ |
| 358 | |
| 359 | def lesson_5_exit_codes(): |
| 360 | """ |
| 361 | 对应源码 hooks.rs:179-196: |
| 362 | match output.status.code() { |
| 363 | Some(0) => HookCommandOutcome::Allow { message }, // 成功=允许 |
| 364 | Some(2) => HookCommandOutcome::Deny { message }, // 2=拒绝 |
| 365 | Some(code) => HookCommandOutcome::Warn { ... }, // 其他=警告 |
| 366 | None => HookCommandOutcome::Warn { ... }, // 被信号杀死 |
| 367 | } |
| 368 | |
| 369 | 退出码 (exit code) 是进程结束时返回给父进程的一个数字: |
| 370 | - 0 = 成功 |
| 371 | - 非 0 = 失败 (具体含义由程序自定) |
| 372 | |
| 373 | Claude Code 的 Hook 协议: |
| 374 | - 0 = Allow (允许工具执行) |
| 375 | - 2 = Deny (拒绝工具执行) |
| 376 | - 其他 = Warn (警告, 但不阻止) |
| 377 | """ |
| 378 | print("=" * 60) |
| 379 | print("第五课: 退出码 — 进程的遗言") |
| 380 | print("=" * 60) |
| 381 | |
| 382 | # ---- 演示不同退出码 ---- |
| 383 | print() |
| 384 | print(" Claude Code Hook 退出码协议:") |
| 385 | print(" ─────────────────────────────") |
| 386 | |
| 387 | test_cases = [ |
| 388 | (0, "Allow", "工具执行被允许"), |
| 389 | (2, "Deny", "工具执行被拒绝"), |
| 390 | (1, "Warn", "警告, 但继续执行"), |
| 391 | (42, "Warn", "未知退出码, 也视为警告"), |
| 392 | ] |
| 393 | |
| 394 | for exit_code, outcome, meaning in test_cases: |
| 395 | result = subprocess.run( |
| 396 | ["python3", "-c", f"import sys; print('hook output'); sys.exit({exit_code})"], |
| 397 | capture_output=True, |
| 398 | text=True, |
| 399 | ) |
| 400 | print(f" exit({exit_code}) → returncode={result.returncode:>2}" |
| 401 | f" → {outcome:<5} | {meaning}") |
| 402 | |
| 403 | # ---- 在 Python 中实现 Hook 退出码解析 ---- |
| 404 | print() |
| 405 | print(" 用 Python 重现 hooks.rs 的退出码处理:") |
| 406 | print(" ─────────────────────────────────────") |
| 407 | |
| 408 | def interpret_hook_exit_code( |
| 409 | returncode: int | None, |
| 410 | stdout: str, |
| 411 | stderr: str, |
| 412 | command: str, |
| 413 | tool_name: str, |
| 414 | ) -> dict: |
| 415 | """ |
| 416 | 对应 hooks.rs:179-196 |
no test coverage detected