fix(chat): agent write_file/edit_file no longer return false success receipts (BUG-29) #100
@@ -1680,13 +1680,18 @@ fn dispatch_tool(tool_name: String, tool_input: String) -> String {
|
|||||||
if !path_within_root(path, root) {
|
if !path_within_root(path, root) {
|
||||||
return json_safe("denied: path is outside the agent workspace root")
|
return json_safe("denied: path is outside the agent workspace root")
|
||||||
}
|
}
|
||||||
// BUG-6 fix (2026-07-17): never claim ok without disk truth. fs_write's result was
|
// BUG-29 (Receipt Contract rule 1) + BUG-6 (disk truth): fs_write's result
|
||||||
// never checked, so a failed write reported ok — the exact false-receipt failure
|
// was ignored, so a failed write (missing/unwritable dir, disk full) still
|
||||||
// the run guards exist to kill. Verify the file landed and return the RESOLVED
|
// returned {"ok":true} — a false receipt fed straight to the model. Check
|
||||||
// path so callers and the model can only narrate what is really on disk.
|
// fs_write's return (1 = all bytes written, 0 = fail): an operation-result
|
||||||
|
// check, stronger than a post-hoc fs_exists, which false-passes when an
|
||||||
|
// overwrite fails but the stale file remains. A read-back via fs_read is
|
||||||
|
// deliberately NOT used: fs_read arms the runtime's one-shot binary send
|
||||||
|
// length, which truncated longer HTTP responses (the #96 failure mode).
|
||||||
|
// Still return the RESOLVED path so callers/model narrate only disk truth.
|
||||||
let dest: String = resolve_in_root(path, root)
|
let dest: String = resolve_in_root(path, root)
|
||||||
fs_write(dest, content)
|
let write_ok: Int = fs_write(dest, content)
|
||||||
if !fs_exists(dest) {
|
if write_ok == 0 {
|
||||||
return json_safe("{\"error\":\"write failed - nothing landed at " + dest + "\"}")
|
return json_safe("{\"error\":\"write failed - nothing landed at " + dest + "\"}")
|
||||||
}
|
}
|
||||||
return json_safe("{\"ok\":true,\"path\":\"" + dest + "\"}")
|
return json_safe("{\"ok\":true,\"path\":\"" + dest + "\"}")
|
||||||
@@ -1766,8 +1771,22 @@ fn dispatch_tool(tool_name: String, tool_input: String) -> String {
|
|||||||
if str_eq(content, "") {
|
if str_eq(content, "") {
|
||||||
return json_safe("{\"error\":\"file not found\"}")
|
return json_safe("{\"error\":\"file not found\"}")
|
||||||
}
|
}
|
||||||
|
// BUG-29 (Receipt Contract rule 1): when old_text was absent (or empty),
|
||||||
|
// str_replace was a silent no-op and the handler still claimed ok:true —
|
||||||
|
// a false receipt. Verify the text is actually present before replacing.
|
||||||
|
if str_eq(old_text, "") {
|
||||||
|
return json_safe("{\"error\":\"old_text is required\"}")
|
||||||
|
}
|
||||||
|
if !str_contains(content, old_text) {
|
||||||
|
return json_safe("{\"error\":\"old_text not found in file\"}")
|
||||||
|
}
|
||||||
let updated: String = str_replace(content, old_text, new_text)
|
let updated: String = str_replace(content, old_text, new_text)
|
||||||
fs_write(resolved, updated)
|
// BUG-29: the fs_write result was also unchecked — a failed write still
|
||||||
|
// returned ok:true. Same honest-write check as write_file above.
|
||||||
|
let write_ok: Int = fs_write(resolved, updated)
|
||||||
|
if write_ok == 0 {
|
||||||
|
return json_safe("{\"error\":\"write failed\"}")
|
||||||
|
}
|
||||||
return json_safe("{\"ok\":true}")
|
return json_safe("{\"ok\":true}")
|
||||||
}
|
}
|
||||||
if str_eq(tool_name, "remember") {
|
if str_eq(tool_name, "remember") {
|
||||||
|
|||||||
Reference in New Issue
Block a user