macos-documents-tcc-project-move-recipe.md 4.61 KB

macOS Documents TCC 阻断 Cron 时的项目迁移方案

何时使用

当 Hermes cron(launchd/systemd tick)访问 ~/Documents/... 中的项目目录报:

PermissionError: [Errno 1] Operation not permitted
shell-init: error retrieving current directory: getcwd: cannot access parent directories: Operation not permitted

且无法/不便通过 GUI 给 Python 二进制添加 Full Disk Access 时使用本方案。

核心思路

将项目整体移出受 TCC 保护的 Documents,在原地留下符号链接保持交互式入口,再把所有无人值守 cron job 的 workdir、prompt 和 checkpoint 更新到真实新路径。

执行步骤

1. 迁移目录

# 创建新父目录
mkdir -p /Users/$USER/Projects/lemonAI

# 迁移项目(保留 .git、未提交改动和 untracked 文件)
mv /Users/$USER/Documents/lemonAI/graphagent /Users/$USER/Projects/lemonAI/graphagent

# 在原地留下符号链接,保持交互式入口和旧书签/IDE 可用
ln -s /Users/$USER/Projects/lemonAI/graphagent /Users/$USER/Documents/lemonAI/graphagent

注意:不要把 cron 的 workdir 继续指向符号链接。后台进程经过 Documents 目录解析符号链接时仍可能触发 TCC。

2. 更新 cron job 的 workdir

qa profile:

hermes -p qa cron edit <planning-job-id> --workdir /Users/$USER/Projects/lemonAI/graphagent/doc
hermes -p qa cron edit <code-job-id> --workdir /Users/$USER/Projects/lemonAI/graphagent/source

若 job 分散在 code / planning profile:

hermes -p code cron edit <job-id> --workdir /Users/$USER/Projects/lemonAI/graphagent/source
hermes -p planning cron edit <job-id> --workdir /Users/$USER/Projects/lemonAI/graphagent/doc

3. 更新 prompt 中的旧路径

workdir 只能保证工具默认目录正确;如果 prompt 中硬编码了旧路径,agent 仍可能主动 cd 到旧路径。

批量替换各 profile cron/jobs.json 中的路径字符串:

import json
import glob
import os

old_root = f'/Users/{os.environ["USER"]}/Documents/lemonAI/graphagent'
new_root = f'/Users/{os.environ["USER"]}/Projects/lemonAI/graphagent'

for path in glob.glob(f'/Users/{os.environ["USER"]}/.hermes/profiles/*/cron/jobs.json'):
    with open(path, 'r+', encoding='utf-8') as f:
        text = f.read()
        new_text = text.replace(old_root, new_root)
        if new_text != text:
            f.seek(0)
            f.write(new_text)
            f.truncate()
            print(f'updated {path}')

4. 更新 checkpoint workdir

Hermes checkpoint 中缓存了会话 workdir。恢复到旧路径的 checkpoint 会让 agent 从 Documents 目录启动:

import json
import glob
import os

old_root = f'/Users/{os.environ["USER"]}/Documents/lemonAI/graphagent'
new_root = f'/Users/{os.environ["USER"]}/Projects/lemonAI/graphagent'

for path in glob.glob(f'/Users/{os.environ["USER"]}/.hermes/profiles/*/checkpoints/store/projects/*.json'):
    with open(path, 'r+', encoding='utf-8') as f:
        data = json.load(f)
        if isinstance(data, dict) and isinstance(data.get('workdir'), str):
            old = data['workdir']
            new_wd = old.replace(old_root, new_root)
            if new_wd != old:
                data['workdir'] = new_wd
                f.seek(0)
                json.dump(data, f, ensure_ascii=False, indent=2)
                f.truncate()
                print(f'updated {path}: {old} -> {new_wd}')

5. 更新 profile AGENTS.md 中的共享入口

例如 /Users/$USER/.hermes/profiles/qa/AGENTS.md 中若有:

- 共享入口:`/Users/$USER/Documents/lemonAI/graphagent`

替换为新路径。

6. 验证

手动触发一次 cron job:

hermes -p <profile> cron run <job-id>

或 Hermes cron 工具:cronjob 的 action='run'。

检查输出中不再出现 Operation not permitted、getcwd 失败、PermissionError 等 TCC 相关错误。真实证据:

  • agent log 中 read_file 成功返回新路径下的文件内容;
  • terminal 工具能正常 cd、执行 git status、跑测试;
  • 等待一个自然周期,确认 last_run_at 推进、last_status=ok。

7. 检查被暂停的 cron job

大规模路径变更后,Hermes 可能自动把 job 置为 paused。检查并恢复:

hermes -p qa cron list
hermes -p qa cron resume <job-id>

不要做什么

  • 不要把 workdir 继续指向符号链接并期望 TCC 放行;
  • 不要只更新 workdir 而忽略 prompt 和 checkpoint;
  • 不要把交互式终端的成功当作后台 cron 已通的证据;
  • 不要删除旧目录里的 symlink(否则旧入口、IDE 配置、其他脚本会断)。