diff --git a/chat.el b/chat.el index d5e3b59..9dcaad4 100644 --- a/chat.el +++ b/chat.el @@ -1706,7 +1706,17 @@ fn dispatch_tool(tool_name: String, tool_input: String) -> String { if !path_within_root(path, root) { return json_safe("denied: path is outside the agent workspace root") } - fs_write(resolve_in_root(path, root), content) + // BUG-29 (Receipt Contract rule 1): fs_write's result was ignored, so a + // failed write (missing/unwritable dir, disk full) still returned + // {"ok":true} — a false receipt fed straight to the model. Check the + // return (1 = all bytes written, 0 = fail), the same honest-write pattern + // as the safety-contact fix. A read-back via fs_read is deliberately NOT + // used here: fs_read arms the runtime's one-shot binary send length, + // which truncated longer HTTP responses (the #96 failure mode). + let write_ok: Int = fs_write(resolve_in_root(path, root), content) + if write_ok == 0 { + return json_safe("{\"error\":\"write failed\"}") + } return json_safe("{\"ok\":true}") } if str_eq(tool_name, "web_get") { @@ -1784,8 +1794,22 @@ fn dispatch_tool(tool_name: String, tool_input: String) -> String { if str_eq(content, "") { 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) - 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}") } if str_eq(tool_name, "remember") {