ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

会改本地文件的助手,第一次用就可能删错东西:五类高频翻车

会改本地文件的助手,第一次用就可能删错东西:五类高频翻车

会改本地文件的助手,第一次用就可能删错东西:五类高频翻车

【免费下载链接】gemini-cliAn open-source AI agent that brings the power of Gemini directly into your terminal.项目地址: https://gitcode.com/GitHub_Trending/gemi/gemini-cli

Gemini CLI 的定位很直白:把模型放进终端,允许它读你的仓库、写你的文件、跑你的命令。这种能力的另一面是——它第一次启动时,你对它的默认值一无所知,而它对你要保护什么也一无所知。

社区对它的评价也印证了这种"能力先行、可靠性待磨合"的阶段特征:开源、免费额度大、上手极快,但同时"接近早期预览版,距稳定可靠还有距离"。这篇文章不谈跑分,只谈一件事:一个会落盘修改的 agent,在头几次使用时最容易从哪些地方翻车,以及仓库里已经写好了哪些刹车。

以下所有结论都能在当前仓库源码与文档中找到对应实现,涉及的具体文件会以仓库相对路径标注。


一、为什么"第一次用"最危险:默认值清单

在动手之前,先把几个关键默认值摆出来,它们共同决定了你第一次运行时的风险基线。

行为默认状态依据
工具执行确认default,逐个询问命令行参数
自动批准编辑工具需显式auto_edit或yolo同上
Checkpoint(修改前快照)关闭Checkpointing
受信任目录(Trusted Folders)关闭Trusted Folders
沙箱需显式开启Sandboxing
Plan Mode默认可用,可设为默认模式Plan Mode

注意这两项的对照关系:"询问"是开的,但"快照"和"信任校验"是关的。

这意味着默认状态下,工具调用会停下来问你;一旦你因为"它在跑测试、它在整理目录"而连续按下确认,剩下的唯一安全网就是你自己对 diff 的阅读速度。而 Chekpointing 默认关闭,意味着那一步点击没有任何可回退的落点。


二、五类高频翻车

第一类:陌生仓库的项目级配置,先于你的判断生效

这是最容易被忽略、也最"第一次"的一类。克隆一个仓库、cd进去、直接gemini,是所有人的标准起手式。

问题在于,项目级配置是随仓库一起进来的。Trusted Folders 的文档把机制写得很清楚:开启后,首次进入某个目录会弹出信任对话框,并在**发现阶段(discovery phase)**列出该项目里的 Commands、MCP Servers、Hooks、Skills,以及Setting overrides,也就是项目级settings.json对你的全局配置的覆盖;如果发现"自动批准某些工具"或"关闭安全沙箱"这类危险设置,CLI 会显式告警。

但这项特性默认是关闭的。默认关闭时,你不会看到这个对话框,项目配置会按正常工作流加载。

一旦文件夹被判定为不受信任,防线反而是完整的:Trusted Folders 列出了七条限制——不加载.gemini/settings.json、不加载项目.env、禁止扩展装卸、工具自动接受被强制关闭(即使你全局开了自动批准)、不自动加载内存文件、不连接 MCP、不加载自定义命令。换句话说,那个对话框不是"多问一句",而是这七条限制的总开关。

怎么防:在跑任何陌生仓库之前,先做只读侦察——cat .gemini/settings.json、看有没有.gemini/commands、.gemini/hooks、.gemini/skills。想长期解决就把信任机制打开:

{ "security": { "folderTrust": { "enabled": true } } }

CI/无头场景下没有交互界面,未受信任的目录会直接抛FatalUntrustedWorkspaceError退出,此时才使用--skip-trust或GEMINI_CLI_TRUST_WORKSPACE=true临时放行——而不是把它们当成日常启动参数。

第二类:write_file是全量覆盖,"省略的部分"真的会落盘

write_file的语义在 文件系统工具参考 里只有一句话:写入内容到指定文件,存在则覆盖。

覆盖本身不是 bug,问题在于模型输出天然倾向于"节省":改一个 800 行的文件,它可能只写出改动附近的内容,然后在中间留一行:

// rest of methods ...

如果这行被当成文件内容写进去,你的文件就在语法上被截断了。仓库对这个场景是有专门防御的——write-file.ts 在参数校验阶段调用 omissionPlaceholderDetector.ts,识别rest of code ...、unchanged methods ...、// rest of method ...这类占位符,并直接拒绝本次调用:

const omissionPlaceholders = detectOmissionPlaceholders(params.content); if (omissionPlaceholders.length > 0) { return "`content` contains an omission placeholder (for example 'rest of methods ...'). Provide complete file content."; }

这恰恰说明它曾经是真实痛点:检测器是"事后补的补丁",而不是"从来不会发生"。

还有一层更隐蔽的行为:写入前会走ensureCorrectFileContent,对内容做一次模型侧修正(转义、缩进等),再由 diff 展示。好处是确认框里你看到的就是最终要落盘的文本;坏处是——如果你不看清 diff 就按y,你批准的并不是模型原始输出,而是修正后的版本。

怎么防:

  • 在提示词里明确"只允许用replace做局部修改,不要重写整文件";
  • 大文件改动一律走replace,让old_string的精确匹配成为你的锚点;
  • 确认框里的 diff 是最后的关卡,看到大面积删除(整段-行)就按n。

第三类:replace的匹配歧义,改对了字符串、改错了位置

replace被设计成"精确、局部"的工具:默认要求old_string在文件中恰好出现一次。edit.ts 里的校验逻辑非常明确:

if (!params.allow_multiple && exactOccurrences > 1) { // Failed to edit, expected 1 occurrence but found N }

失败信息也写得很到位:0 次匹配时提示你"如果之前的编辑改动了文件,或你要改的行不在最近读取窗口内,请先用 ReadFile 检查目标行再重试"。

真正的风险出现在你主动放开的时候。allow_multiple: true会把所有匹配全部替换——如果你给的是一个足够短、足够常见的片段(return null;、if (!user)、一个空行组合),它会在多个函数里同时命中。这类修改在事后 review 时极难发现,因为每一处单独看都"像是改对了"。

另一类失败来自上下文不足:模型凭记忆写old_string,与磁盘上的实际文本(缩进、行尾、注释)存在细微差异,触发修正路径;修正仍失败后,它可能退回到"那我用write_file重写整个文件"——第二类翻车就此接上第一类。

怎么防:

  • 提示词里要求"old_string必须包含至少 5 行上下文";
  • 除非明确要全局替换,否则永远不要建议模型开allow_multiple;
  • 对关键文件,先@引用进上下文,再让它改。上下文越完整,old_string越准,回退到整文件重写的概率越低。

第四类:shell 的破坏半径不受工作区约束

文件工具是有边界意识的。config.ts 的validatePathAccess会拒绝工作区与项目临时目录之外的路径:

Path not in workspace: Attempted path "..." resolves outside the allowed workspace directories: ...

paths.ts 里还专门写了resolveDefensiveToolPath,处理模型给出的@src/foo.ts这类引用前缀——如果字面路径不存在,会剥掉@,避免真的创建一个名为@src的目录。这些都是"文件工具层"的防线。

但run_shell_command是另一回事。它执行的是你系统上的真实 shell 命令(Windows 用powershell.exe -NoProfile -Command,其他平台用bash -c),工作目录只是起点,不是边界。rm -rf ~/xxx、> ../../important.txt、git checkout -- .都可以跨出项目目录。

仓库对 shell 做的是"识别 + 提示",而不是"拦截":

  • shell.ts 会检测命令替换语法(detectCommandSubstitution),命中后直接拒绝执行,这是防注入的;
  • 确认弹窗会解析根命令、列出untrustedFlags(未经审查的参数),并把"构建/测试命令 + 构建文件被改过"标为安全警告;
  • write_file改了package.json这类构建文件时,会通过recordModifiedBuildFile记账,随后跑npm install时弹出提示。

最后这条设计很值得注意:它防的不是"删错文件",而是**"先改package.json,再触发依赖安装"这条供应链路径**。这也是为什么"只读计划、集中执行"的模式在安全上更有意义。

怎么防:

  • 不要对 agent 说"帮我清理一下构建产物"这种开放式命令,改成枚举式:"删除dist/和.turbo/两个目录";
  • 首次使用一律default确认模式,逐条看命令;
  • 需要放开时用沙箱,而不是用yolo。Sandboxing 明确:容器内工作目录以相同绝对路径挂载,系统其余部分不可见;GEMINI_SANDBOX=docker一行即可。

第五类:自动批准 + 默认关闭的 checkpoint,等于没有后悔药

auto_edit会自动批准replace与write_file,只对其他工具(如 shell)保留询问;yolo则批准一切。这两者的定位在 参数表 里写得很清楚,--yolo已经标记为 deprecated,推荐统一走--approval-mode。

它们的共同点是:跳过的正是那个能看到 diff 的确认框。而默认状态下,write_file/replace执行前的快照(checkpoint)是关闭的。

这两件事叠在一起,构成最坏的组合:没有 diff 可看,也没有快照可退。

更细节的一层:使用--yolo时沙箱会被默认启用,这是仓库里少见的"负负得正"——放开确认的同时收紧了执行边界。但这个默认只在 yolo 下生效,auto_edit并不会自动给你套沙箱。

怎么防:

{ "general": { "checkpointing": { "enabled": true } } }

注意文档里的提醒:--checkpointing命令行开关在 0.11.0 已被移除,只能用settings.json开启。很多人"以为开了",其实是旧文章里的用法。


三、动手前的快照与回滚准备

回滚能力分为两套机制,边界完全不同,混用会出事。

Checkpoint:文件级的时间点

开启后,每次你批准一个会修改文件系统的工具(write_file、replace),CLI 会自动创建 checkpoint,包含三样东西:

  1. Git 快照:提交到~/.gemini/history/<project_hash>下的影子仓库,不影响你自己的.git;
  2. 会话历史;
  3. 即将执行的那次工具调用本身。

恢复走/restore:

/restore /restore 2025-06-22T10-00-00_000Z-my-file.txt-write_file

检查点文件名由时间戳、被改文件名、工具名拼成,恢复后文件与会话都回到那一刻,并且原始工具调用会重新出现,让你决定重跑、修改还是放弃。这个"重放"设计比单纯的文件回滚更有价值:你可以改一句提示词,然后立刻重试同一步。

有一个必须知道的副作用:恢复的语义是"把项目里所有文件还原到快照状态"。如果你在快照之后手动改了别的文件,那些改动也会被一起覆盖。所以在/restore之前,先把手上的未提交改动处理掉。

Rewind:会话级的回退

/rewind(或连按两次Esc)是另一套:选一个交互点,然后选择"回退会话和代码"、"只回退会话"、"只回退代码"。

它的边界在 Rewind 里写得很清楚,而且是本文最重要的一条警示:

Rewinding only affects file changes made by the AI's edit tools. It doesnotundo manual edits you've made or changes triggered by the shell tool (!).

也就是说,rm -rf删掉的东西,/rewind救不回来;只有编辑类工具造成的改动在它的管辖范围内。第四类翻车恰恰落在它的盲区里。

所以正确的准备顺序是:

  1. 进仓库先git status确认工作区干净,或者至少知道哪些改动没提交;
  2. 打开 checkpointing;
  3. 对"要跑破坏性命令"的场景,改用沙箱而不是依赖回滚;
  4. 区分清楚:编辑失误找/rewind或/restore,命令失误只能靠备份或沙箱。

四、把风险压到最低:配置与提示词

1. 用 Plan Mode 做第一次接触

Plan Mode 是只读环境,默认可用:

gemini --approval-mode=plan

它会先研究代码、和你确认策略,再写一份 Markdown 计划到 plans 目录,按Ctrl+X可以用外部编辑器直接改计划、写批注。对"第一次进这个仓库"的场景,这比任何提示词都有效——你可以在零写入的前提下,看清它打算怎么动手。

一个细节值得记住:enter_plan_mode工具在 YOLO 模式下不可用。也就是说,一旦进了 yolo,你就失去了"让模型自己退一步做规划"的路径。

2. 用策略引擎做硬性拒绝

策略引擎 支持allow/deny/ask_user三种决策,按优先级取最高。放在用户级目录~/.gemini/policies/*.toml:

[[rule]] toolName = "run_shell_command" commandPrefix = "rm -rf" decision = "deny" priority = 100
[[rule]] toolName = "run_shell_command" commandRegex = "git (commit|push)" decision = "ask_user" priority = 100

deny比"每次都点拒绝"更彻底:对全局规则(不带argsPattern),被拒绝的工具会被完全从模型的工具列表里移除,模型根本看不到它,既安全又省上下文。

两个坑需要提前说明:

  • commandPrefix和commandRegex不能写在同一条规则里;
  • Workspace 层级的策略目前不生效,写在项目.gemini/policies下不会起作用,请使用 User 或 Admin 策略。这一点在文档里以警告形式明确标注,很容易被忽略。

3. 用.geminiignore缩小可见面

.geminiignore语法与.gitignore基本一致,支持!取反:

.env local-db-dump.sql /archive/ *.md !README.md

但要说清它的性质:它约束的是尊重该文件的工具(文件发现、搜索、@引用等)的可见范围,文档也明确"这些文件对其他服务(如 Git)仍然可见"。不要把它当成权限边界——shell 工具可以直接cat任何文件。它的价值在于减少误读、减少上下文污染,以及降低敏感内容被模型顺手带进日志的概率。

4. 提示词里写清楚"不许做什么"

配置管的是能力边界,提示词管的是意图边界。第一次使用时,把这几句固定加在任务前面,收益远大于任何调优:

  • "只修改src/foo.ts,其他文件保持不动。"
  • "使用replace做局部修改,不要用write_file重写整个文件。"
  • "不要执行任何安装、删除、git push或修改 lockfile 的命令。"
  • "先给出将要修改的位置和 diff 摘要,等我确认后再动手。"

这四条分别对应本文的四类翻车:越界修改、全量覆盖、破坏性命令、无确认执行。

5. 自动批准只给"可重建的工作区"

auto_edit并非不能用,它的适用条件很具体:这个仓库可以被一条命令完全重建(容器、临时目录、干净的 worktree),而且你不介意丢掉未提交的改动。在真实业务仓库里,它省下的几次回车,抵不上一次误覆盖的排查成本。


结语:把"信任"拆成可验证的几步

这个类别的工具真正的转折点,不是模型变多强,而是默认值是否站在"不丢数据"这一侧。从代码看,这个仓库已经准备了相当完整的刹车系统:路径校验、占位符检测、匹配次数校验、命令替换检测、构建文件记账、checkpoint、策略引擎、多层沙箱。

问题在于,这些刹车大多默认不踩:checkpoint 关着,文件夹信任关着,沙箱要显式开。它们更像是"给知道自己在做什么的人准备的开关",而不是"给第一次使用的人准备的护栏"。

所以第一次使用的正确姿势不是"先试试看它能做什么",而是先花三分钟做三件事:看清仓库里的项目级配置、把工作区变成可回滚的状态、把第一次交互限制在只读模式。能力是它给的,边界得你自己划。

【免费下载链接】gemini-cliAn open-source AI agent that brings the power of Gemini directly into your terminal.项目地址: https://gitcode.com/GitHub_Trending/gemi/gemini-cli

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表