Gemini CLI 报 Operation not permitted / Permission denied 怎么解决?沙箱在拦你

2026-08-08

让 Gemini CLI 写个文件,它报:

Operation not permitted

或者:

Permission denied

第一反应通常是「权限不够」,于是去 chmod、去加 sudo大概率白忙。

官方 troubleshooting 对这条给的成因是另一回事:沙箱开启时,Gemini CLI 可能会尝试被你的沙箱配置限制的操作,比如写到项目目录或系统临时目录之外的地方。

也就是说,拦你的不是操作系统,是它自己的安全边界。

一、先分清两种「没权限」

情况谁在拦怎么认怎么处理
沙箱限制Gemini CLI 自己的沙箱操作的是项目目录或系统临时目录之外的路径调整沙箱配置
真的文件权限不够操作系统操作的是项目内的路径,但那个文件/目录本身权限不对改文件权限或所有者

判断的关键是看它想动的那个路径在哪。

  • 想写 /etc//usr/、你的家目录里项目之外的地方、别的项目目录 → 沙箱
  • 想写项目里的某个文件,但那个文件是只读的、或者属于别的用户 → 系统权限

第一种情况下 chmod 是没用的——文件权限本来就是对的,是沙箱不让它出去。

二、沙箱的边界在哪

按官方的说明,沙箱允许的范围围绕着两个地方:

  • 项目目录
  • 系统临时目录

超出这两个范围的写入操作会被限制。

这个设计是合理的:一个能改代码的 AI 工具,不应该能随便动你系统里的其他东西。 边界的存在本身就是它敢放开手改文件的前提。

所以撞上这条报错时,先问一句:它要动的那个位置,是不是本来就不该让它动?

很多时候答案是「确实不该」——比如它想改全局配置、想装个系统级的东西、想写到另一个项目里。这种情况下正确的做法是让它别这么干,而不是把沙箱放开。

三、确实需要放开的话

如果那个越界操作是必要的(比如你的项目结构就是要读写某个外部目录),官方给的指引是去看官方的沙箱配置文档,了解怎么自定义沙箱配置。

本文不给具体的配置写法——沙箱配置的字段和结构属于会变的实现细节,写死在文章里很快就过期,而且配错安全边界的代价比配错别的设置大得多。 请以官方沙箱文档为准。

能给的是一条判断原则:放开范围时,放到刚好够用,别图省事直接关掉沙箱。 沙箱关掉之后,这个工具在你机器上的行为就没有边界了。

四、退出码 44:脚本里怎么认

如果你把 Gemini CLI 放进自动化流程,沙箱问题有专门的退出码。官方的退出码表:

退出码类型含义
41FatalAuthenticationError认证过程出错
42FatalInputError输入无效或缺失(仅非交互模式
44FatalSandboxError沙箱环境出错(Docker / Podman / Seatbelt)
52FatalConfigErrorsettings.json 无效或有错
53FatalTurnLimitedError达到会话最大对话轮数(仅非交互模式

拿到 44,就是沙箱层面的问题,不用去解析报错文本。

这张表里 44 那一行还透露了一个信息:沙箱的底层实现可能是 Docker、Podman 或 Seatbelt(Seatbelt 是 macOS 的沙箱机制)。所以「沙箱环境出错」不一定是策略拦截,也可能是:

  • Docker / Podman 没装、没启动、当前用户没权限用
  • 容器镜像拉不下来
  • macOS 上 Seatbelt 相关的问题

这一类跟「越界被拦」是两回事——一个是沙箱在正常工作并拦住了你,一个是沙箱本身跑不起来。

怎么分:如果它连启动都启动不了、或者报的是容器相关的错,那是后者,去检查容器运行时的状态;如果它跑得好好的、只是某个具体操作被拒,那是前者。

五、另一条容易被归错类的:EADDRINUSE

顺带说一条也跟「起不来」有关但完全不同的:

EADDRINUSE (Address already in use)

官方说明是在启动 MCP server 时出现的,成因是另一个进程已经占用了 MCP server 要绑定的端口

处理办法两条:

  • 停掉占用端口的那个进程
  • 或者给 MCP server 换一个端口

这条跟权限、沙箱都没关系,纯粹是端口冲突。认出来能省一轮排查。

最常见的诱因是:上一次没退干净,进程还在后台占着端口。 排查时先看看是不是有残留进程。

六、排查顺序

  1. 看它想动的路径在哪
    • 项目目录或系统临时目录之外 → 沙箱限制,走第 2 步
    • 项目内 → 可能是真的文件权限问题,检查文件所有者和权限位
  2. 问一句:这个操作本来就该让它做吗?
    • 不该 → 换个做法,让它别越界(这是最常见也最正确的处理
    • 该 → 查官方沙箱配置文档,放到刚好够用,别关沙箱
  3. 脚本里看退出码
    • 44 → 沙箱层面
    • 41 / 52 → 认证 / 配置,另一套处理
  4. 如果是沙箱本身起不来(容器相关报错)
    • 检查 Docker / Podman 是否安装并运行、当前用户有没有权限
  5. 看到 EADDRINUSE → 端口冲突,跟权限无关,找占端口的进程或换端口

七、几个别做的动作

  • 别急着 sudo 沙箱限制跟你的系统权限无关,提权不会让它放行,反而可能带来别的问题
  • 别直接关掉沙箱。 边界是这个工具敢放手改文件的前提
  • chmod 777 如果真因是沙箱,这么做既没用又降低了系统安全性
  • 别把 EADDRINUSE 当权限问题查。 它是端口冲突

八、沙箱在 CI 和容器里的额外麻烦

在本地开发机上,沙箱通常是透明的——它在后台工作,你几乎感觉不到。放进 CI 或者容器里,情况会复杂起来。

容器套容器的问题。 退出码 44 那一行提到沙箱底层可能用 Docker 或 Podman。如果你的 CI 本身就跑在容器里,那就是容器里再起容器——这需要额外的配置(比如挂载 Docker socket 或者用特权模式),不是开箱就能用的。撞上这种情况时,报错通常表现为沙箱起不来,而不是某个操作被拒。

权限问题。 CI runner 的用户可能没有使用容器运行时的权限。这类报错跟本文开头讲的「沙箱拦截」完全不同——一个是沙箱在正常工作,一个是沙箱压根没跑起来

判断方法:如果每一次运行都在同一个地方失败、而且失败发生在很早的阶段,那更像是沙箱起不来;如果是跑到某个具体操作才失败,那才是被拦。

CI 里还有一个不相关但经常同时撞上的坑:Gemini CLI 在 CI 环境里可能不进交互模式。官方说明成因是底层的 is-in-ci 包会检测 CICONTINUOUS_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。沙箱配置的具体字段请以官方沙箱文档为准,本文不复述会变的实现细节。

相关阅读

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。