Recover from empty verified edit calls

ober

40477bfd852cf03f8a6335bea17887b4da3560dd

diff --git a/src/jcode/core/verified-run.ss b/src/jcode/core/verified-run.ss
index 3512201..39abe23 100644
--- a/src/jcode/core/verified-run.ss
+++ b/src/jcode/core/verified-run.ss
@@ -1609,6 +1609,16 @@
 (def (missing-read-path-message)
   "read requires a path. Do not retry empty read(). Use list(path=\".\") to inspect the current directory, or read(path=\"Makefile\"), read(path=\"README.md\"), read(path=\"AGENTS.md\"), or read(path=\"test/run-tests.sh\") for a specific file that exists. If you already understand the repo, use edit/write with the first complete draft, then verify().")
 
+(def (missing-file-tool-path-message tool)
+  (string-append
+    (symbol->string tool)
+    " requires a path. Do not retry empty "
+    (symbol->string tool)
+    "(). Use list(path=\".\") to inspect the current directory, or read/head/tail/wc with a concrete current-repo file path such as Makefile, README.md, AGENTS.md, or test/run-tests.sh. If you already understand the repo, use edit/write with the first complete draft, then verify()."))
+
+(def (missing-edit-args-message)
+  "edit/write requires concrete arguments. Do not retry empty edit() or write(). To create or replace a file, call edit(path=\"life.ss\", content=<complete file contents>). To patch existing code, call edit(path=\"life.ss\", old_str=<exact current text>, new_str=<replacement>) or use line_edit/replace_def/replace_range. Then call verify().")
+
 (def (slice-content content args)
   (let* ((line-no (arg-int args "line" 0))
          (start-line (arg-int args "start" 0))
@@ -3258,9 +3268,10 @@
          (required-full-rewrite?
            (required-repair-full-rewrite? cwd path content old-str line-no)))
     (cond
-      ((not path) (error 'edit "missing path arg"))
+      ((not path)
+       (raise-recoverable-tool-error (missing-edit-args-message) 'edit))
       ((and (not content) (not old-str))
-       (error 'edit "missing content arg or old_str/new_str args"))
+       (raise-recoverable-tool-error (missing-edit-args-message) 'edit))
       ((placeholder-only-content? (or new-str content))
        (guard-placeholder-content! 'edit path (or new-str content)))
       ((write-scope-error "edit" (scope-path cwd path))
@@ -3619,7 +3630,9 @@
               (let ((path (arg-path args #f)))
                 (if path
                   (read-lines-at path cwd 'tail (line-tool-count args 50))
-                  "tail: missing path"))))
+                  (raise-recoverable-tool-error
+                    (missing-file-tool-path-message 'tail)
+                    'tail)))))
             '()))
         (head-def
           (make-tool-def
@@ -3630,7 +3643,9 @@
               (let ((path (arg-path args #f)))
                 (if path
                   (read-lines-at path cwd 'head (line-tool-count args 50))
-                  "head: missing path"))))
+                  (raise-recoverable-tool-error
+                    (missing-file-tool-path-message 'head)
+                    'head)))))
             '()))
         (wc-def
           (make-tool-def
@@ -3641,7 +3656,9 @@
               (let ((path (arg-path args #f)))
                 (if path
                   (string-append (line-count-for path cwd) " " path)
-                  "wc: missing path"))))
+                  (raise-recoverable-tool-error
+                    (missing-file-tool-path-message 'wc)
+                    'wc)))))
             '()))
         (balance-def
           (make-tool-def
diff --git a/test/run.ss b/test/run.ss
index d7c94db..e9ab7f8 100644
--- a/test/run.ss
+++ b/test/run.ss
@@ -2389,6 +2389,48 @@
   (safe-delete-test-file! target-path))
 
 (let* ([vr-dir "/tmp"]
+       [target "jcode-verified-empty-edit-recovery.txt"]
+       [target-path (string-append vr-dir "/" target)]
+       [tool-results '()]
+       [wf (coding-workflow (string-append "grep -q ok " target) vr-dir)]
+       [resp (scripted-responder
+               (list
+                 (list (make-wtool-call "head" '() #f))
+                 (list (make-wtool-call "wc" '() #f))
+                 (list (make-wtool-call "write" '() #f))
+                 (list (make-wtool-call "edit" '() #f))
+                 (list (make-wtool-call "edit" (list (cons "path" target)
+                                                      (cons "content" "ok\n")) #f))
+                 (list (make-wtool-call "verify" '() #f))
+                 (list (make-wtool-call "done" '(("summary" . "empty-edit-recovered")) #f))))])
+  (safe-delete-test-file! target-path)
+  (let ([result (run-workflow wf "recover from empty write/edit calls" resp
+                  (list (cons 'max-iterations 10)
+                        (cons 'max-tool-errors 0)
+                        (cons 'on-message
+                          (lambda (m)
+                            (when (equal? (message-role m) "tool")
+                              (set! tool-results
+                                (cons (message-content m) tool-results)))))))])
+    (check! "verified-run: empty inspect and edit calls recover with zero hard errors"
+            result "empty-edit-recovered")
+    (check-pred! "verified-run: empty edit/write gives recoverable concrete args"
+      (reverse tool-results)
+      (lambda (xs)
+        (let loop ((ys xs) (saw-head #f) (saw-edit #f))
+          (if (null? ys)
+            (and saw-head saw-edit)
+            (loop (cdr ys)
+                  (or saw-head
+                      (and (str-contains? (car ys) "[ToolRecoverableError]")
+                           (str-contains? (car ys) "head requires a path")))
+                  (or saw-edit
+                      (and (str-contains? (car ys) "[ToolRecoverableError]")
+                           (str-contains? (car ys) "edit/write requires concrete arguments")
+                           (str-contains? (car ys) "edit(path=\"life.ss\"")))))))))
+  (safe-delete-test-file! target-path))
+
+(let* ([vr-dir "/tmp"]
        [fixture "jcode-verified-run-budget-fixture.txt"]
        [fixture-path (string-append vr-dir "/" fixture)]
        [target "jcode-verified-run-budget-out.txt"]