From 2292098784c766ccb80f6d23bac34617f922df22 Mon Sep 17 00:00:00 2001 From: marksverdhei Date: Sat, 27 Jun 2026 19:31:56 +0200 Subject: [PATCH] fix(hat): don't rename to a stem-less dotfile on a degenerate model answer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `sanitize_name` appended the original extension unconditionally, so an empty or unusable answer (e.g. a reasoning model that spent its whole budget inside and emitted no name, or punctuation like "." / "/") produced a stem-less name like ".jpg". That's non-empty, so it slips past the caller's `-z "$suggested"` guard and renames the file to a hidden, stem-less file — effectively losing it. Strip leftover dots/dashes from the stem and, if nothing remains, emit no name so the empty-guard rejects the rename and reports "(no suggestion)". Adds 3 regression tests (empty / think-only / punctuation-only → no name). All 80 bats tests pass. Co-Authored-By: Claude Opus 4.8 --- hat | 7 ++++++- test.sh | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/hat b/hat index 020a5b8..33373d7 100755 --- a/hat +++ b/hat @@ -527,7 +527,12 @@ name = re.sub(r"-+", "-", name).strip("-") if preserve_ext == "true" and orig_ext: if "." in name: name = name.rsplit(".", 1)[0] - name = name + "." + orig_ext + name = name.strip("-.") + # An empty stem means the model gave nothing usable (e.g. a reasoning model + # that spent its budget inside and emitted no answer). Emit no name + # so the caller's empty-guard rejects it, instead of renaming the file to a + # stem-less dotfile like ".jpg". + name = name + "." + orig_ext if name else "" print(name) SANITIZE } diff --git a/test.sh b/test.sh index 2f61b85..66bc502 100755 --- a/test.sh +++ b/test.sh @@ -126,6 +126,26 @@ teardown_file() { assert_output "second-name.txt" } +# Regression: a degenerate model answer (empty / think-only / punctuation) must +# NOT become a stem-less dotfile like ".jpg" — that slips past the caller's +# empty-name guard and renames the file to a hidden, stem-less file. Emit +# nothing so the rename is rejected. + +@test "sanitize: empty answer yields no name (not a dotfile)" { + run sanitize_name "" "true" "jpg" + assert_output "" +} + +@test "sanitize: think-only answer yields no name" { + run sanitize_name "spent my whole budget reasoning" "true" "jpg" + assert_output "" +} + +@test "sanitize: punctuation-only answer yields no name" { + run sanitize_name "." "true" "jpg" + assert_output "" +} + # ── Binary detection ───────────────────────────────────────────────── @test "binary: text file is not binary" {