Skip to content

fix(file-tools): stale write_file overwrites are refused before disk mutation (#65604, salvage #65605) - #91238

Merged
teknium1 merged 4 commits into
mainfrom
kimi-port/stale-write-guard
Sep 15, 2026
Merged

teknium1 merged 4 commits into
mainfrom
kimi-port/stale-write-guard

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

Summary

write_file can no longer overwrite an existing file the task never read, or a file that changed on disk after the last read — the stale-write refusal now fires BEFORE disk mutation instead of warning after the data loss already happened. Salvage of #65605 by @danspicytaco (fixes #65604), cherry-picked with authorship preserved, plus hardening on top.

Also scouted from MoonshotAI/kimi-code#3096 (their agent-core-v2 staleGuard feature, merged this week) — same design conclusion independently: veto Edit/Write pre-execution on never-read or mtime-changed targets, refresh the baseline after own writes so consecutive self-writes stay unblocked.

Changes

  • tools/file_tools.py (contributor commits, cherry-picked):
    • New per-task full_write_baselines set: populated by full, non-partial, unredacted read_file and by successful write_file; patch intentionally does not qualify.
    • write_file_tool refuses (pre-mutation, stale_write_blocked: true) when the target exists but has no baseline, or when cross-agent/mtime staleness is detected; refusal message tells the model to re-read → merge → retry.
    • Redacted reads never bless a full overwrite (prevents persisting the «redacted:…» sentinel back into credential files — the [Bug]: redact_sensitive_text corrupts API keys in config files when read via read_file/search_files, causing 401 #35519 corruption class).
    • patch remains warning-only; new-file creation unaffected; capped container (_FULL_WRITE_BASELINES_CAP) + _new_read_tracker_task() consolidation.
  • Hardening commit (ours):
    • Redacted-read test force-enables redaction (hermetic CI has no config), matching tests/agent/test_redact.py convention.
    • test_write_verification CRLF case establishes a read baseline first — sibling test pinning the old overwrite-without-read behavior, updated to the new contract.
    • website/docs/reference/tools-reference.md documents the read-before-overwrite contract.
    • contributors/emails/ mapping for DanSpicyTaco.

Validation

Before After
write_file on unread existing file silently overwrites refused pre-mutation, file untouched
write after external on-disk edit warning attached after clobber refused pre-mutation, external edit preserved
consecutive same-task writes ok ok (own write refreshes baseline)
redacted read → full overwrite could persist redaction sentinel refused
patch / new-file creation ok unchanged

Targeted suites: 139 passed, 2 skipped (test_file_staleness, test_file_read_guards, test_file_tools, test_file_state_registry, test_cross_profile_guard, test_write_verification, test_line_ending_preservation). E2E against real file I/O in an isolated HERMES_HOME: 7/7 scenarios (unread-refusal, read→write, consecutive write, external-edit refusal, re-read heal, new file, patch untouched).

Closes #65605 (salvaged, authorship preserved). Fixes #65604.

Infographic

Stale Write Guard mission patch

@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 378fb2c — fix: paged, extracted and post-compaction reads count as a w

⚠️ Warnings

OSV vulnerability scan · View job

76 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.


CI timings · View report · View job

Wall time 8m5s vs 6m9s (+31.4%). 9 job(s) slower, 5 faster, 1 unchanged.

  • Python tests / Run tests: -75.0s
  • Docs Site / docs-site-checks: +62.0s
  • OS-specific tests / Windows-only tests: +36.0s
  • Python lints / Windows footguns (blocking): +17.0s
  • Python tests / e2e: +14.0s

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists tool/file File tools (read, write, patch, search) labels Aug 21, 2026
@Sora-bluesky

Copy link
Copy Markdown
Contributor

Cross-reference: #69403 (open, CI green) also touches tools/file_tools.py, on the read side. It makes the /dev and /proc read guard (_is_blocked_device, search_tool) fire on Windows and for container backends, so it does not touch the stale-write path this PR adds. A merge of the two branches is clean in file_tools.py; the only overlap is the import block at the top of tests/tools/test_file_tools.py. I will rebase #69403 once this lands.

@Sora-bluesky

Copy link
Copy Markdown
Contributor

#67216 (f8c6ae11) changes write_file_tool (@@ -2287,7) in tools/file_tools.py so a doubled-path warning wins over staleness _warning after the write. This PR (42fc2d1c) rewrites the same old lines (@@ -2284,23) to refuse stale writes before disk mutation. I will rebase #67216 onto this if it merges first.

@teknium1
teknium1 force-pushed the kimi-port/stale-write-guard branch from 42fc2d1 to aff7920 Compare September 15, 2026 01:53
danspicytaco and others added 4 commits September 14, 2026 20:51
Require an explicit full-file baseline before replacing existing host-visible files with write_file, and fail closed when that baseline is stale. This prevents stale conversation context from clobbering manual or external edits.\n\nRefs #65604
…seline, docs

- test_file_staleness redacted-read case now force-enables redaction
  (matches tests/agent/test_redact.py convention) so it exercises the
  sentinel path in hermetic CI where security.redact_secrets is unset.
- test_write_verification CRLF case establishes a read baseline first
  (the new guard refuses unread existing-file overwrites by design).
- tools-reference.md documents the read-before-overwrite contract.
- contributors/emails mapping for DanSpicyTaco.
…with schema

The stale-write guard now refuses write_file on an existing file the task
never read in full, so test_write_file_rewrite_hint's overwrite-without-read
fixtures were refused before the hint could be computed. Reading first is
the exact read->whole-file-rewrite pattern the hint exists for.

tools-reference.md's write_file row now mirrors the WRITE_FILE_SCHEMA
description (one-sentence contract + the recovery step) instead of a
longer paraphrase.
… baseline

The stale-overwrite refusal made write_file permanently unusable for any
existing file it could not show in one read_file page: every >2000-line
(or >100K-char) page was recorded as partial, no full baseline ever
existed, and the refusal told the model to "re-read the whole file", which
the tool cannot do. Track the line ranges each task pages through per path
at one mtime; contiguous pages from line 1 to total_lines are a full read
(a new mtime between pages restarts the coverage). The same gap hit two
siblings: the extracted-document branch (.ipynb, text-authorable) returned
before any read bookkeeping, so an existing notebook could never be
overwritten; and reset_file_dedup dropped every baseline on compaction
while keeping read_timestamps, so every write after compaction was refused
even for files unchanged on disk. Baselines now survive compaction exactly
like the dedup mtime map does — only while the recorded mtime still matches.

Refusal texts no longer embed the pre-PR "Warning: … Consider re-reading"
copy inside "Refusing to overwrite", and every refusal names a recovery the
model can perform: read the remaining pages, or use patch.
@teknium1
teknium1 force-pushed the kimi-port/stale-write-guard branch from aff7920 to 378fb2c Compare September 15, 2026 04:21
@teknium1
teknium1 merged commit dddefae into main Sep 15, 2026
37 checks passed
@teknium1
teknium1 deleted the kimi-port/stale-write-guard branch September 15, 2026 10:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Medium — degraded but workaround exists tool/file File tools (read, write, patch, search) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

write_file can clobber external edits from stale conversation context

4 participants