From 0cde4a945c2ce6b0c05bfc97d9a6a74f00fc47fd Mon Sep 17 00:00:00 2001
From: shixy <18705837259@163.com>
Date: Mon, 6 Apr 2026 02:59:27 -0700
Subject: [PATCH 1/2] fix(history_text): clear seen_reads on
write_file/patch_file to prevent stale read deduplication
A path-based deduplication in history_text() would skip read_file calls
even when a write_file/patch_file on the same path had occurred between
the two reads. This caused the LLM to see stale file content.
The fix clears the seen_reads entry for a path when a write occurs, so
the next read of that path is always shown.
---
mini_coding_agent.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/mini_coding_agent.py b/mini_coding_agent.py
index 236a3a4..69c8dbf 100644
--- a/mini_coding_agent.py
+++ b/mini_coding_agent.py
@@ -407,6 +407,9 @@ def history_text(self):
recent_start = max(0, len(history) - 6)
for index, item in enumerate(history):
recent = index >= recent_start
+ if item["role"] == "tool" and item["name"] in ("write_file", "patch_file"):
+ path = str(item["args"].get("path", ""))
+ seen_reads.discard(path)
if item["role"] == "tool" and item["name"] == "read_file" and not recent:
path = str(item["args"].get("path", ""))
if path in seen_reads:
From d135bfd7311231d1da04be57f69b60aff49f7256 Mon Sep 17 00:00:00 2001
From: shixy <18705837259@163.com>
Date: Mon, 6 Apr 2026 04:46:49 -0700
Subject: [PATCH 2/2] test(history_text): add regression tests for read
deduplication with write tracking
- test_history_text_deduplicates_reads_but_not_after_write: verifies a read
following a write is not incorrectly deduplicated (regression test for the
stale-read bug)
- test_history_text_deduplicates_unchanged_repeated_reads: verifies that
repeated reads with no intervening write are still correctly deduplicated
---
tests/test_mini_coding_agent.py | 63 +++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/tests/test_mini_coding_agent.py b/tests/test_mini_coding_agent.py
index e5a4298..4e30f77 100644
--- a/tests/test_mini_coding_agent.py
+++ b/tests/test_mini_coding_agent.py
@@ -254,6 +254,69 @@ def test_welcome_screen_keeps_box_shape_for_long_paths(tmp_path):
assert "commands: Commands:" not in welcome
+def _make_filler(i):
+ return {"role": "tool", "name": "list_files", "args": {}, "content": "", "created_at": str(i)}
+
+
+def test_history_text_deduplicates_reads_but_not_after_write(tmp_path):
+ """read_file deduplication must not skip a read that follows a write.
+
+ Realistic prior-turn history (non-recent window):
+ user: "update config"
+ assistant: read_file config
+ tool: config v1 (content: setting=true)
+ assistant: write_file config
+ tool: wrote
+ assistant: read_file config
+ tool: config v2 (content: setting=false) <- MUST NOT be skipped
+
+ Without fix: seen_reads={"config"} after first read; write does NOT clear it;
+ second read is wrongly skipped (LLM sees stale content).
+ With fix: write clears seen_reads, second read is correctly shown.
+ """
+ agent = build_agent(tmp_path, [])
+
+ # Simulate a prior turn with read->write->read on the same file
+ # history_length=13, recent_start=7 (indices 0-6 non-recent, 7-12 recent)
+ agent.record({"role": "user", "content": "update config", "created_at": "0"}) # index 0
+ agent.record({"role": "assistant", "content": '{"name":"read_file","args":{"path":"config.txt"}}', "created_at": "1"})
+ agent.record({"role": "tool", "name": "read_file", "args": {"path": "config.txt"}, "content": "# config.txt\n 1: setting=true\n", "created_at": "2"}) # index 2, non-recent, ADDED
+ agent.record({"role": "assistant", "content": '{"name":"write_file","args":{"path":"config.txt","content":"setting=false\n"}}', "created_at": "3"})
+ agent.record({"role": "tool", "name": "write_file", "args": {"path": "config.txt", "content": "setting=false\n"}, "content": "wrote config.txt", "created_at": "4"}) # index 4, non-recent
+ agent.record({"role": "assistant", "content": '{"name":"read_file","args":{"path":"config.txt"}}', "created_at": "5"})
+ agent.record({"role": "tool", "name": "read_file", "args": {"path": "config.txt"}, "content": "# config.txt\n 1: setting=false\n", "created_at": "6"}) # index 6, non-recent, ADDED (write cleared dedup)
+ # recent entries
+ for i in range(7, 13):
+ agent.record(_make_filler(i))
+
+ history = agent.history_text()
+
+ # Both read contents appear exactly once (check full line to avoid JSON false positives)
+ assert "# config.txt\n 1: setting=true\n" in history
+ assert "# config.txt\n 1: setting=false\n" in history
+ # Also verify duplicate read (setting=true, same path) does NOT appear twice
+ assert history.count("setting=true") == 1
+
+
+def test_history_text_deduplicates_unchanged_repeated_reads(tmp_path):
+ """read_file deduplication should still skip repeated reads with no write in between."""
+ agent = build_agent(tmp_path, [])
+
+ # Realistic: two identical reads with no write between them
+ # history_length=10, recent_start=4 (indices 0-3 non-recent, 4-9 recent)
+ agent.record({"role": "user", "content": "check logs", "created_at": "0"}) # index 0
+ agent.record({"role": "assistant", "content": '{"name":"read_file","args":{"path":"log.txt"}}', "created_at": "1"})
+ agent.record({"role": "tool", "name": "read_file", "args": {"path": "log.txt"}, "content": "# log.txt\n 1: stable\n", "created_at": "2"}) # index 2, non-recent, ADDED
+ agent.record({"role": "assistant", "content": '{"name":"read_file","args":{"path":"log.txt"}}', "created_at": "3"}) # index 3, non-recent, SKIPPED (dup)
+ for i in range(4, 10):
+ agent.record(_make_filler(i)) # indices 4-9, recent
+
+ history = agent.history_text()
+
+ # Only first read should appear; duplicates must be skipped
+ assert history.count("stable") == 1
+
+
def test_ollama_client_posts_expected_payload():
captured = {}