name: write-file-recovery-procedure category: software-development description: Recovery procedure when write_file accidentally overwrites a large file instead of appending. Covers detection, data recovery via session search, and safe reconstruction using terminal/cat instead of write_file for appends. trigger: write_file was used to 'append' to a file but actually overwrote it, destroying existing content; or any time you need to append to a large existing file
write_file 覆盖恢复流程
核心教训:write_file 总是覆盖整个文件
write_file() 总是完全覆盖目标文件,从不追加。 即使文件有 3500+ 行且你只想加 100 行,使用 write_file 会销毁全部内容。
❗ 致命陷阱:read_file + execute_code + write_file 链导致行号污染
场景:在 execute_code() 内部调用 read_file() 读取大文件内容到变量,然后拼上新内容用 write_file() 写回。
问题:read_file() 返回的内容包含行号前缀格式 LINE_NUM|CONTENT(例如 375|text)。将此字符串直接传给 write_file() 会导致每一行都被行号污染。
# ❌ 错误写法 — 文件被行号污染
from hermes_tools import read_file, write_file
content = read_file(path="big_file.md") # 返回 " 1|# Title\n 2|\n..."
write_file(path="big_file.md", content=content['content'] + new_stuff)
# 结果:文件每行变成 " 1|# Title" " 2|" 等
正确做法:永远通过 terminal cat 来读取和拼接,或使用 shell 管道:
# ✅ 正确做法:用 terminal 读取绝对内容
from hermes_tools import terminal, write_file
content = terminal("cat /path/to/file.md")
# content['output'] 是纯文件内容,无行号
mport terminal, write_file
content = terminal("cat /path/to/file.md")
# content['output'] 是纯文件内容,无行号# ✅ 追加的正确姿势:写临时文件 + cat >>
write_file(path="/tmp/new_part.md", content="新的章节内容...")
terminal("cat /tmp/new_part.md >> /path/to/file.md")
terminal("rm /tmp/new_part.md")
检测是否发生覆盖
执行 write_file 后立即检查文件行数:
如果行数骤降(如从 3542 降到 2),则已发生覆盖。恢复流程(当文件被覆盖时)
第一步:不要惊慌,不要重复写入
一旦发现覆盖,不要再对同一个文件执行 write_file——每次都会覆盖上一次的结果。
第二步:搜索会话历史恢复数据
from hermes_tools import session_search, terminal, read_file
# 搜索相关的历史会话,获取文件内容描述
results = session_search(query="关键文件名 或 关键章节标题")
# 从会话摘要中提取技术内容
# 会话摘要通常包含章节清单和关键发现
第三步:分部件重建(推荐方案)
不要试图一次性 write_file 整个重建——内容太长会被工具拒绝或出问题。
正确方案:分部件写入独立文件,然后 cat 拼接:
from hermes_tools import write_file, terminal
# 1. 写出第一部分(覆盖原有)
write_file(path="/root/part1.txt", content="...第一章到第十章的内容...")
# 2. 写出其余部分为独立文件
write_file(path="/root/part2.txt", content="...第十一章到三十章的内容...")
write_file(path="/root/part3.txt", content="...第三十一章到四十五章的内容...")
write_file(path="/root/part4.txt", content="...新增章节的内容...")
# 3. 用 terminal 的 cat 命令拼接——不触发 write_file 的覆盖行为
terminal(command="cat /root/part1.txt /root/part2.txt /root/part3.txt /root/part4.txt > /path/to/target.md")
# 4. 验证
terminal(command="wc -l /path/to/target.md")
第四步:验证文件完整性
# 检查章节标题完整性
terminal(command='grep -n "^## " /path/to/target.md')
# 检查文件末尾
read_file(path="/path/to/target.md", offset=-30)
# 检查文件大小
terminal(command="wc -lc /path/to/target.md")
如何安全追加内容到已有文件
任何时候需要在文件末尾添加内容(不是覆盖),使用以下方法之一: inal(command="wc -lc /path/to/target.md")
## 如何安全追加内容到已有文件
任何时候需要**在文件末尾添加内容**(不是覆盖),使用以下方法之一:### 方法一:terminal + cat/heredoc
```bash
cat >> /path/to/existing.md << 'EOF'
## 新章节标题
新内容......
EOF
方法二:Python 写临时文件然后 cat 拼接
from hermes_tools import terminal, write_file
# 写新增部分为独立文件
write_file(path="/root/new_content.md", content="新内容...")
# 拼接
terminal(command="cat /root/new_content.md >> /path/to/existing.md")
# 清理
terminal(command="rm /root/new_content.md")
方法三:execute_code 用 terminal 追加
from hermes_tools import terminal, write_file
# 写新增部分为独立临时文件(小文件,安全)
write_file(path="/tmp/new_content.md", content="新内容...")
# 用 cat 追加到目标
terminal(command="cat /tmp/new_content.md >> /path/to/existing.md")
# 清理临时文件
terminal(command="rm /tmp/new_content.md")
⚠️ 千万不要在 execute_code 内部用
read_file读取大文件内容后拼上新字符串传给write_file— read_file 返回的行号前缀格式LINE_NUM|CONTENT会污染文件。见上方「行号污染」陷阱。
🚨 子代理写文件危险(delegate_task + write_file 致死模式)
这是本文件第三次丢失的根因。当使用 delegate_task 派生子代理执行任务时:
危险模式
# 在父代理中:
delegate_task(goal="研究并追加到 /path/to/study.md", ...)
# 子代理内部调用 write_file("/path/to/study.md", ...) 或直接使用 write_file
# → write_file 是完整覆盖!子代理不知道父代理的文件有多大
# → 如果子代理写的是 partial content(如只写了新内容),整个文件被截断
致命因果链:
1. 父代理有大文件(5262 行)
2. 派生子代理 append 任务
3. 子代理内部执行 write_file("/path/to/study.md", new_content_only)
4. 文件被截断为 179 行——5300 行内容丢失
子代理安全追加协议
永远不要让子代理直接 write_file 到父代理的已有文件。使用以下安全模式:
模式 A:子代理写 staging 文件,父代理 cat 追加
# 父代理中:
永远不要**让子代理直接 write_file 到父代理的已有文件。使用以下安全模式:
#### 模式 A:子代理写 staging 文件,父代理 cat 追加
```python
# 父代理中:# 1. 派生子代理,让子代理写 staging 文件
delegate_task(
goal="研究并输出到 /tmp/vrchat_new_chapter.md",
context="...",
toolsets=["file", "terminal"]
)
# 2. 父代理从 staging 文件 cat 追加到目标
terminal("cat /tmp/vrchat_new_chapter.md >> /root/study.md")
terminal("wc -l /root/study.md") # 验证
terminal("rm /tmp/vrchat_new_chapter.md")
模式 B:父代理通过 terminal 分块重建(当文件已被截断时)
# 1. 每一块写为独立 staging 文件
write_file(path="/tmp/part1.md", content="...")
write_file(path="/tmp/part2.md", content="...")
write_file(path="/tmp/part3.md", content="...")
# 2. 用终端 cat 拼接——不触发 write_file 覆盖
terminal("cat /tmp/part1.md /tmp/part2.md /tmp/part3.md > /path/to/study.md")
# 3. 验证
terminal("wc -l /path/to/study.md")
子代理的安全边界规则
- ⛔ 子代理永远不能 write_file 到父代理已知存在的文件
- ✅ 子代理写 staging 文件到
/tmp/或专用目录 - ✅ 父代理负责将 staging 文件安全地合并(用
cat >>) - ✅ 父代理在合后后验证文件完整性(行数 + grep 章节标题)
- ⚠️ 如果必须让子代理直接操作目标文件,使用
terminal("cat >>")而非write_file
预防措施
- 永远不要对已知存在的大文件使用 write_file 来"追加"——它总是覆盖
- 写新文件时,先用
wc -l验证文件状态 - 对重要文件,定期保存备份(cp .bak)
- 连续 write_file 操作时要特别警惕——第二次调用会覆盖第一次的结果
紧急恢复:patch() 插入到错误位置
patch() 使用 old_string 作为匹配锚点。如果 old_string 不够唯一或文件结构发生变化,内容可能被插入到完全错误的位置。典型表现:文件行数暴增,但新内容出现在中间而非末尾。
检测
恢复流程(本会话经验)
当 patch() 将大量新内容插入到文件中间时:
-
不要恐慌或再次写入 — 每次写入都会让情况更糟
-
使用 Python 诊断错位范围:
-
重建文件:切割 + 重排 + 合并
关键原理:错位插入的内容在文件中的位置是连续的区块。你需要找到这个区块的起止行号,然后:
# 假设:
# - [0:good_end] 是插入点之前正确的所有内容
# - [bad_start:bad_end] 是被错位插入的文本(要挪到末尾)
# - [good_end:bad_start] 是原本应在此处的旧内容(被挤压)
# - [bad_end:] 是文件末尾的其余正确内容
new_lines = (lines[0:good_end] + # 前段正确内容
lines[good_end:bad_start] + # 被挤压的旧内容
lines[bad_start:bad_end] + # 错位的新内容(挪到末尾)
lines[bad_end:]) # 剩余正确内容
-
备份原始文件后再写入:
-
写入修复后的内容(使用
write_file重写或 Python 写入) -
验证完整性:
本会话的实际案例
在 3700+ 行的 .md 文件中使用 patch() 追加 3 个新章节(约 580 行):
- old_string 不够唯一(|--- 分隔符匹配了多处),导致内容被插入到 Ch50 之后而非 Ch65 之后
- 恢复使用了 Python 切割-重排-合并,将错位的区块移动到正确位置
- 还发现了次级问题:\\n 字面量未被正确转义、## 下次学习计划 出现两次
- 最终文件从 3761 行缩减到 3752 行(去除了重复的旧内容)
避免 patch() 插入错位的策略
- 提供唯一上下文:
old_string包含至少 3-5 行独特的上下文,不要只用|---或空行 - 优先追加到文件末尾:如果目标是追加,使用
cat >>或 Python append,不要用patch() - patch() 只用于替换:查找并替换文件中少量文本(1-20 行),不适合做大规模插入
- 验证插入位置:patch 后立即检查: (1-20 行),不适合做大规模插入
- 验证插入位置:patch 后立即检查:
bash grep -n "^## 新章节标题" /path/to/file## 扩展:write_file 的适用场景
| 场景 | write_file 是否适合 | 替代方案 |
|---|---|---|
| 创建新文件 | ✅ | - |
| 重写现有文件 | ✅ | - |
| 追加到现有文件 | ❌ 会覆盖 | cat >> 或 Python 拼接 |
| 修改文件部分内容 | ✅ 推荐 | 使用 patch 工具(但注意唯一性匹配) |
| 在大文件中间插入大量内容 | ⚠️ 危险 | 见下方「patch 错位恢复」章节 |