Gemini CLI 报 Operation not permitted / Permission denied 怎么解决?沙箱在拦你
让 Gemini CLI 写个文件,它报:
Operation not permitted
或者:
Permission denied
第一反应通常是「权限不够」,于是去 chmod、去加 sudo。大概率白忙。
官方 troubleshooting 对这条给的成因是另一回事:沙箱开启时,Gemini CLI 可能会尝试被你的沙箱配置限制的操作,比如写到项目目录或系统临时目录之外的地方。
也就是说,拦你的不是操作系统,是它自己的安全边界。
一、先分清两种「没权限」
| 情况 | 谁在拦 | 怎么认 | 怎么处理 |
|---|---|---|---|
| 沙箱限制 | Gemini CLI 自己的沙箱 | 操作的是项目目录或系统临时目录之外的路径 | 调整沙箱配置 |
| 真的文件权限不够 | 操作系统 | 操作的是项目内的路径,但那个文件/目录本身权限不对 | 改文件权限或所有者 |
判断的关键是看它想动的那个路径在哪。
- 想写
/etc/、/usr/、你的家目录里项目之外的地方、别的项目目录 → 沙箱 - 想写项目里的某个文件,但那个文件是只读的、或者属于别的用户 → 系统权限
第一种情况下 chmod 是没用的——文件权限本来就是对的,是沙箱不让它出去。
二、沙箱的边界在哪
按官方的说明,沙箱允许的范围围绕着两个地方:
- 项目目录
- 系统临时目录
超出这两个范围的写入操作会被限制。
这个设计是合理的:一个能改代码的 AI 工具,不应该能随便动你系统里的其他东西。 边界的存在本身就是它敢放开手改文件的前提。
所以撞上这条报错时,先问一句:它要动的那个位置,是不是本来就不该让它动?
很多时候答案是「确实不该」——比如它想改全局配置、想装个系统级的东西、想写到另一个项目里。这种情况下正确的做法是让它别这么干,而不是把沙箱放开。
三、确实需要放开的话
如果那个越界操作是必要的(比如你的项目结构就是要读写某个外部目录),官方给的指引是去看官方的沙箱配置文档,了解怎么自定义沙箱配置。
本文不给具体的配置写法——沙箱配置的字段和结构属于会变的实现细节,写死在文章里很快就过期,而且配错安全边界的代价比配错别的设置大得多。 请以官方沙箱文档为准。
能给的是一条判断原则:放开范围时,放到刚好够用,别图省事直接关掉沙箱。 沙箱关掉之后,这个工具在你机器上的行为就没有边界了。
四、退出码 44:脚本里怎么认
如果你把 Gemini CLI 放进自动化流程,沙箱问题有专门的退出码。官方的退出码表:
| 退出码 | 类型 | 含义 |
|---|---|---|
| 41 | FatalAuthenticationError | 认证过程出错 |
| 42 | FatalInputError | 输入无效或缺失(仅非交互模式) |
| 44 | FatalSandboxError | 沙箱环境出错(Docker / Podman / Seatbelt) |
| 52 | FatalConfigError | settings.json 无效或有错 |
| 53 | FatalTurnLimitedError | 达到会话最大对话轮数(仅非交互模式) |
拿到 44,就是沙箱层面的问题,不用去解析报错文本。
这张表里 44 那一行还透露了一个信息:沙箱的底层实现可能是 Docker、Podman 或 Seatbelt(Seatbelt 是 macOS 的沙箱机制)。所以「沙箱环境出错」不一定是策略拦截,也可能是:
- Docker / Podman 没装、没启动、当前用户没权限用
- 容器镜像拉不下来
- macOS 上 Seatbelt 相关的问题
这一类跟「越界被拦」是两回事——一个是沙箱在正常工作并拦住了你,一个是沙箱本身跑不起来。
怎么分:如果它连启动都启动不了、或者报的是容器相关的错,那是后者,去检查容器运行时的状态;如果它跑得好好的、只是某个具体操作被拒,那是前者。
五、另一条容易被归错类的:EADDRINUSE
顺带说一条也跟「起不来」有关但完全不同的:
EADDRINUSE (Address already in use)
官方说明是在启动 MCP server 时出现的,成因是另一个进程已经占用了 MCP server 要绑定的端口。
处理办法两条:
- 停掉占用端口的那个进程
- 或者给 MCP server 换一个端口
这条跟权限、沙箱都没关系,纯粹是端口冲突。认出来能省一轮排查。
最常见的诱因是:上一次没退干净,进程还在后台占着端口。 排查时先看看是不是有残留进程。
六、排查顺序
- 看它想动的路径在哪
- 项目目录或系统临时目录之外 → 沙箱限制,走第 2 步
- 项目内 → 可能是真的文件权限问题,检查文件所有者和权限位
- 问一句:这个操作本来就该让它做吗?
- 不该 → 换个做法,让它别越界(这是最常见也最正确的处理)
- 该 → 查官方沙箱配置文档,放到刚好够用,别关沙箱
- 脚本里看退出码
- 44 → 沙箱层面
- 41 / 52 → 认证 / 配置,另一套处理
- 如果是沙箱本身起不来(容器相关报错)
- 检查 Docker / Podman 是否安装并运行、当前用户有没有权限
- 看到
EADDRINUSE→ 端口冲突,跟权限无关,找占端口的进程或换端口
七、几个别做的动作
- ❌ 别急着
sudo。 沙箱限制跟你的系统权限无关,提权不会让它放行,反而可能带来别的问题 - ❌ 别直接关掉沙箱。 边界是这个工具敢放手改文件的前提
- ❌ 别
chmod 777。 如果真因是沙箱,这么做既没用又降低了系统安全性 - ❌ 别把
EADDRINUSE当权限问题查。 它是端口冲突
八、沙箱在 CI 和容器里的额外麻烦
在本地开发机上,沙箱通常是透明的——它在后台工作,你几乎感觉不到。放进 CI 或者容器里,情况会复杂起来。
容器套容器的问题。 退出码 44 那一行提到沙箱底层可能用 Docker 或 Podman。如果你的 CI 本身就跑在容器里,那就是容器里再起容器——这需要额外的配置(比如挂载 Docker socket 或者用特权模式),不是开箱就能用的。撞上这种情况时,报错通常表现为沙箱起不来,而不是某个操作被拒。
权限问题。 CI runner 的用户可能没有使用容器运行时的权限。这类报错跟本文开头讲的「沙箱拦截」完全不同——一个是沙箱在正常工作,一个是沙箱压根没跑起来。
判断方法:如果每一次运行都在同一个地方失败、而且失败发生在很早的阶段,那更像是沙箱起不来;如果是跑到某个具体操作才失败,那才是被拦。
CI 里还有一个不相关但经常同时撞上的坑:Gemini CLI 在 CI 环境里可能不进交互模式。官方说明成因是底层的 is-in-ci 包会检测 CI、CONTINUOUS_INTEGRATION,以及任何 CI_ 前缀的环境变量。你自己定义的 CI_TOKEN 也会触发。官方给的绕法是临时取消:
env -u CI_TOKEN gemini
把这两件事放在一起说,是因为在 CI 里它们经常同时出现,而症状(跑不起来、没反应)很像,容易互相掩盖。排查时先用退出码分开:44 是沙箱,其他情况再看是不是交互模式的问题。
九、总结
Operation not permitted/Permission denied在 Gemini CLI 里通常是沙箱在拦,不是系统权限不够。- 判断关键是看目标路径:在项目目录或系统临时目录之外的,就是沙箱。
- 先问「这个操作本来就该做吗」——多数时候答案是不该,换个做法比放开沙箱好。
- 确实要放开,查官方沙箱配置文档,放到刚好够用,别关掉。
- 退出码 44 =
FatalSandboxError,脚本里靠它分流。注意它也覆盖「沙箱本身跑不起来」(Docker / Podman / Seatbelt)。 EADDRINUSE是端口冲突,跟权限和沙箱都没关系。
本文所引官方内容来自 google-gemini/gemini-cli 仓库自带的 troubleshooting 文档,核对日 2026-08-08。沙箱配置的具体字段请以官方沙箱文档为准,本文不复述会变的实现细节。