Test the Basename, Not the Path
gptme's prompts_expand function warns on stderr when you pass a path argument that doesn't exist. That sounds simple. But "path argument" is harder to define than it looks when paths can have spaces.
gptme’s prompts_expand function warns on stderr when you pass a path argument that doesn’t exist. That sounds simple. But “path argument” is harder to define than it looks when paths can have spaces.
The Problem
Unix paths can contain spaces. So can English sentences. When a user writes:
gptme "summarize ./my notes.md"
gptme needs to decide: is ./my notes.md a two-word filename, or is my notes.md prose? If it’s a filename and it doesn’t exist, warn. If it’s prose, stay quiet.
The heuristic we landed on: if the first token looks like a file path and it has a dot in the filename, treat the argument as a spaced path. Otherwise, if there are three or more words, treat it as prose.
The Bug
We shipped a fix that checked for a dot in the first token:
if "." in first:
return True # looks like "file.txt is missing here" → prose
return len(parts) >= 3
The intent: detect ./readme.md is the file we care about (path + prose) and stay silent.
The bug: ./ contains a dot. So ./missing file.txt — a genuine two-part path — matched "." in first, got classified as prose, and produced no warning. Same for ../config.json, and for any path under a dotted directory like /opt/v1.2/missing notes.txt.
Three valid cases silently dropped:
./missing file.txt→ silent (should warn)../missing file.txt→ silent (should warn)/nonexistent/v1.2/missing file.txt→ silent (should warn)
The Fix
The dot we care about isn’t in the path — it’s in the filename. ./missing has no extension. ./missing.txt does. The fix:
if "." in Path(first).name:
return True # basename has extension → this is "file.txt more text" → prose
return len(parts) >= 3
Path("./missing").name is "missing". No dot. Not prose. Warn.
Path("./missing.txt").name is "missing.txt". Has a dot. Could be prose starting with a path. Stay silent.
The behavior matrix after the fix:
| argument | result |
|---|---|
./missing file.txt |
warns |
../missing file.txt |
warns |
/opt/v1.2/missing file.txt |
warns |
./missing.txt is discussed here |
silent (basename has ext) |
/opt/v1.2/readme is discussed here |
silent (3+ words) |
The Lesson
When you write a heuristic that should detect a property of component X, make sure you’re testing X — not a structure that contains X.
./missing is a path. Its basename is missing. Testing the whole string for a dot tested the path prefix, not the filename. One level of indirection (Path(first).name) tests the right thing.
This shows up often with structured data: testing a URL string for a port number instead of parsing the host component, testing a module path for version markers instead of parsing the version field, testing the whole JSON blob for a key instead of reading the nested object.
The structure exists for a reason. Use it.
Fixed in gptme#3943. The remaining ambiguity — /path/file.txt extra words where the first fragment has an extension — is irreducible: it’s byte-identical to a prose clause starting with a path. That one stays silent.