终端里手动运行脚本正常,但 Claude Code 操作后没有任何效果。
判断:适合——如果你通过 SSH 在远程 Mac 上运行 Claude Code,并希望 Hook 自动检查、格式化或构建,按“事件与 matcher → 配置与脚本 → SSH 环境 → 退出状态与日志”逐层排查;不适合——如果你期待 Hook 代替 CI 的完整权限、隔离和失败告警机制。
这篇指南适合通过 SSH 使用远程 Mac 开发的工程师,也适合维护项目共享 Hook 配置、需要在 macOS 上自动运行检查或构建脚本的团队。
如果你只在本地终端执行脚本,或希望从头学习 Hook 配置,这里重点讨论的远程会话差异可能不是你的首要问题。
先把“没运行”拆成可验证的故障
“Claude Code Hooks 远程 Mac 不执行”不等于同一种故障。事件可能没有发生,matcher 可能没有匹配;也可能脚本根本没启动,或者脚本运行了,但输出没有以你预期的方式生效。把这几种情况分开,能避免一上来重装工具、放宽权限,反而引入新问题。
先记下 Claude Code 版本、会话启动方式、当前项目目录,以及触发问题时你让 Claude 执行了什么操作。随后打开会话中的 /hooks,确认当前能看到的 Hook 配置;官方命令说明将 /hooks 列为查看工具事件 Hook 配置的入口。更多检查入口见官方命令参考和配置调试说明。
| 观察到的现象 | 优先检查 | 可作出的判断 |
|---|---|---|
| 测试操作发生,但没有标记文件或日志 | 事件、matcher、设置作用范围 | 配置没加载或匹配条件不符 |
| Hook 配置可见,但脚本没有留下痕迹 | 文件路径、shebang、执行权限 | Hook 已配置,脚本可能未启动 |
| 脚本有输出或错误,但任务没按预期变化 | 退出码、输出格式、事件处理规则 | 脚本运行过,结果可能未被正确处理 |
这个区分也解释了一个常见误判:手动在 SSH 终端里运行脚本成功,只能证明终端当前环境能找到并运行它,不能证明 Claude Code 的 Hook 使用了相同的配置、路径或环境变量。
事件没发生,还是 matcher 没匹配?
先核对你选的事件是否对应实际操作,再查 matcher。Hook 是按事件触发的;不同事件与工具调用、会话生命周期的关系不同。不要只看配置里存在 PostToolUse,还要确认它监听的工具和实际操作一致。事件定义、matcher、输入输出格式都应以官方 Hook 参考和官方 Hook 指南为准。
例如,若配置绑定在工具调用之后,而你只是直接在 SSH shell 中运行脚本,这个操作本身并不是 Claude Code 的工具调用,当然不能用它证明对应 Hook 已触发。反过来,如果你确实让 Claude 执行了符合条件的工具操作,却没有任何日志,再查看 matcher 是否匹配那个工具名,而不是你心里预期的文件名或命令描述。
用临时标记验证配置是否加载
先用一段仅写入临时文件的脚本做探针,不要一开始接入格式化、删除或部署命令。以下配置是排查示例:把脚本路径和 matcher 按你的实际项目与事件调整。它只用于验证 Hook 是否被调用,不代表本站在远程 Mac 上做过复测。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "$HOME/.claude/hooks/hook-probe.sh"
}
]
}
]
}
}
脚本示例:
#!/bin/sh
printf 'hook-invoked\n' >> "$HOME/.claude-hook-probe.log"
通过 SSH 在远程主机上检查脚本路径,并确认它能执行:
ls -l "$HOME/.claude/hooks/hook-probe.sh"
chmod +x "$HOME/.claude/hooks/hook-probe.sh"
再让 Claude 执行一次确实匹配该工具的低风险操作,最后检查标记文件:
cat "$HOME/.claude-hook-probe.log"
若日志没有出现,优先回查配置是否加载、事件与 matcher 是否对得上;有日志但检查、格式化或构建结果仍不理想,就继续查脚本本身和 Hook 输出。
配置写对了,当前会话真的读到了吗?
同一个项目可能同时存在用户级与项目级设置,也可能有本地设置覆盖共享配置。先确认你改的是正在使用的设置文件,再查看配置优先级;否则,编辑一个未被当前会话采用的文件,会让排查看起来像“改了但没变化”。设置文件的作用范围和优先级可对照官方设置说明。
对于共享项目配置,要区分“团队提交的设置”和“当前用户的本地设置”;后者适合个人排查,但不等于其他成员也会读取相同配置。更改后通过 /hooks 检查会话实际识别到的配置,再重新执行触发操作。这样可以把“文件里写了什么”与“会话实际加载了什么”分开。
第一步:脚本能否从远程 Mac 上启动?
确认配置中的路径确实存在于远程主机,而不是只存在于你的本地电脑。若脚本使用 #!/bin/bash 或其他 shebang,检查该路径下的解释器能否启动;同时检查脚本有没有执行权限。脚本路径、解释器和权限任一不成立,都会让“事件已经触发”与“脚本成功执行”成为两件不同的事。
用 SSH 直接检查这些条件:
test -f "$HOME/.claude/hooks/hook-probe.sh" && echo "script exists"
head -n 1 "$HOME/.claude/hooks/hook-probe.sh"
ls -l "$HOME/.claude/hooks/hook-probe.sh"
如果你从设置中调用的是项目脚本,也要确认远程工作树里存在这个文件。尤其是共享配置写了相对路径时,不要假设它相对于脚本所在目录解析;先用最小测试记录实际目录,再决定是否改成明确的路径。Hook 的命令配置方式与输入输出约定,按前文的官方示例和参考核对,别凭其他工具的配置格式推断。
第二步:对照 SSH 环境变量与工作目录
SSH 环境变量是远程排障的重点之一。登录后的交互式终端可能读取 shell 启动文件,而 Claude Code 启动 Hook 时看到的 PATH、HOME、项目目录和必要变量可能不同。因此,终端中执行 brew 或项目命令成功,并不能证明 Hook 能找到同一命令。
把诊断信息限制为必要字段,不要把 API 密钥、令牌或完整环境转储到日志:
printf 'PATH=%s\n' "$PATH"
printf 'HOME=%s\n' "$HOME"
pwd
command -v brew
command -v xcodebuild
将这些结果与 SSH 交互式终端的结果对照。如果工具存在但 command -v 找不到,考虑使用已确认的绝对路径,或在 Hook 脚本中显式设置最少量的 PATH。如果项目命令依赖当前目录,改用明确的项目目录进入方式,并记录 pwd 验证,而不是依赖你从哪个目录启动 SSH。
Claude Code 支持通过设置中的环境变量等方式影响会话行为,具体变量与设置入口应查官方环境变量参考。这里的关键不是把所有变量都复制过去,而是找出命令实际依赖的那几个,并只在适当范围内提供。
第三步:脚本执行后,结果为什么没体现?
脚本运行了却没有预期结果时,分别看标准输出、标准错误和退出码。再核对 Hook 事件的结果处理规则:不同事件可能会把 Hook 输出用于不同目的,不能假设任何一行 echo 都会直接显示在对话里,或自动改变 Claude 的后续行为。输入输出与退出码的处理方式可参照前文的官方 Hook 参考。
为了定位原因,可暂时把输出重定向到你有权限访问的日志文件,并保留脚本退出状态:
/path/to/project/check.sh > "$HOME/hook-stdout.log" 2> "$HOME/hook-stderr.log"
status=$?
printf 'exit=%s\n' "$status"
如果你用 Claude Code 的调试日志排查,先在触发操作前启用调试,再复现问题;只在故障发生后才开启,通常无法回看此前的执行过程。按前文的官方配置调试文档检查配置加载,再查命令参考中的调试说明,避免把“没有对话输出”直接等同于“脚本没有运行”。
调试日志和脚本日志可能包含项目路径、命令参数或环境信息。共享给团队前先检查并删去凭据、个人目录和不必要的源码内容。
用真实 macOS 工具链复验,并保留安全边界
临时标记出现后,逐步替换为真正的检查任务:先运行只读检查,再验证格式化或测试,最后才考虑构建等可能产生输出文件的操作。每一步都确认任务失败时会返回有意义的错误状态,也确认它不会误删文件、覆盖产物或把凭据写进日志。
不要为了让 Hook“跑起来”而关闭权限控制。Hook 的脚本权限、Claude Code 的工具权限与项目文件的访问范围是不同的控制面;把其中一项放宽,不会修复错误的事件、路径或 PATH,却可能扩大命令造成的影响。安全设置与权限边界可参考官方安全文档和权限配置说明。
如果你的团队正评估不同开发环境,先通过远程 Mac 方案比较判断哪些任务确实需要 macOS 工具链。Hook 排查解决的是执行链问题,不会替代 CI 的隔离、审计、凭据管理和失败通知;这些仍应由项目团队负责。
常见问题
为什么配置了 Hook,操作之后还是没有日志?
先确认 Claude Code 实际执行了对应工具操作,再核对 Hook 事件和 matcher。直接在 SSH 终端运行脚本不等于触发了 Claude Code 的工具事件;用临时标记脚本并通过 /hooks 检查当前会话加载情况,能分辨“没触发”和“脚本内部没留下记录”。
SSH 下找不到命令,是不是要重装工具?
通常先比较远程交互式终端与 Hook 的 PATH、工作目录和必要环境变量,再检查命令是否存在于远程主机。若只是在 Hook 环境中找不到,可针对脚本设置最小且明确的路径;重装工具不会自动让不同启动环境拥有相同的变量。
脚本权限和工作目录要在哪里确认?
在远程 Mac 上用 ls -l 检查脚本是否存在及执行位,用 head -n 1 检查 shebang,再在脚本中临时记录 pwd。这比从本地电脑推断远程文件权限或目录结构可靠;确认完成后删除临时日志,避免把诊断信息长期留在主机上。
去哪里看 Hook 的失败原因?
先看脚本自己的标准错误和退出码,再通过 /hooks 核对当前配置,并在复现前启用 Claude Code 调试日志。对照对应事件的官方输出规则判断结果是否会显示在对话中;检查日志后,删除或脱敏其中可能包含的路径、命令参数及敏感信息。
如果你当前的 SSH 会话和远程主机已经具备所需工具链,先按上述顺序验证 Hook,再决定是否需要改变环境。若问题的根因是缺少可持续访问的 macOS 开发主机,而不是某条配置或脚本错误,可进一步了解 OakVM 联系方式,评估租用真实远程 Mac 是否适合你的临时测试或开发需求;若你需要长期稳定的高负载主机,或必须直接连接物理设备,则应先比较自购方案与其他可行环境,再作决定。
常见问题
为什么配置了 Hook,Claude Code 操作后却没有任何反应?
先确认触发的操作确实对应所选 Hook 事件,再检查 matcher 是否匹配实际工具名称;同时用会写入临时标记的无副作用脚本,确认配置被当前会话加载。终端里手动运行脚本成功,只能证明脚本可运行,不能证明 Claude Code 会触发它。
SSH 登录远程 Mac 后,Hook 提示找不到命令该从哪里查?
在远程 Mac 上分别比较交互式终端与 Hook 执行时的 PATH、当前目录及必需环境变量。对 Homebrew、项目内脚本和 macOS 工具链使用确认过的绝对路径;若 Hook 仍找不到命令,先把环境差异写入仅供排查的日志,再决定如何调整启动方式。
怎么确认 Hook 脚本权限和工作目录没有问题?
通过 SSH 登录后检查配置指定的脚本是否存在、shebang 中的解释器能否运行,并用 ls -l 查看执行位;再用 pwd 和脚本内的临时日志确认运行目录。不要仅凭本地电脑上的路径或权限判断远程主机状态,两边可能不是同一文件系统。
Hook 运行失败时去哪里看日志,如何区分脚本报错?
先查看脚本的标准输出、标准错误和退出码,再用 Claude Code 的 /hooks 检查当前会话加载的 Hook 配置,并按官方调试说明启用日志。对照对应事件的输出处理规则,判断错误来自脚本本身,还是来自 Claude Code 对 Hook 返回值的解释。
在稳定的远程 Mac 环境中继续排查
OakVM 提供独享 Apple Silicon 物理节点,适合通过 SSH 验证脚本、权限与运行环境。
