docs: Added common pitfalls docs for custom bash scripts

2026-07-14 12:46:17 -06:00
parent b88c6bc579
commit 69a57d8872
+111
@@ -336,6 +336,117 @@ argc_some_field="$(jq -r '.some_field' <<< "$LLM_AGENT_RAW_JSON")"
--- ---
# Output Handling and Common Pitfalls
Coyote captures your tool's result from the `$LLM_OUTPUT` file (not from stdout), and it applies a few rules that are
easy to trip over. The bundled `fs_*` tools follow the patterns below; your own tools should too.
## An empty result is shown to the model as `"DONE"`
When a tool exits `0` but writes **nothing** to `$LLM_OUTPUT`, Coyote has no content to return, so the model receives
the bare string `"DONE"`. The model cannot distinguish that from a broken tool. It has no idea whether the file was
empty, the search had no hits, or the script silently failed.
Always write *something* for every successful outcome, including the "nothing found" cases:
```bash
# BAD: a search that finds nothing writes nothing, so the model just sees "DONE"
main() {
grep -rn "$argc_pattern" . >> "$LLM_OUTPUT"
}
# GOOD: empty results get an explicit, informative message
main() {
local results
results=$(grep -rn "$argc_pattern" . 2>/dev/null) || true
if [[ -z "$results" ]]; then
echo "No matches found for: $argc_pattern" >> "$LLM_OUTPUT"
return 0
fi
echo "$results" >> "$LLM_OUTPUT"
}
```
The same applies to reading an empty file, listing an empty directory, or an offset/range that lands past the end of
the data. If a "no result" is a normal outcome, say so explicitly.
## Errors written to `$LLM_OUTPUT` are discarded on a non-zero exit
Coyote reads `$LLM_OUTPUT` **only when the tool exits `0`**. On a non-zero exit it instead returns
`Tool call '<name>' exited with code N` plus whatever the tool printed to **stdout/stderr**. The `$LLM_OUTPUT` file is
ignored. So the following hides your helpful message; the model only sees `exited with code 1`:
```bash
# BAD: the message lands in $LLM_OUTPUT, then the non-zero exit makes Coyote ignore it
main() {
if [[ ! -d "$argc_path" ]]; then
echo "Error: directory not found: $argc_path" >> "$LLM_OUTPUT"
return 1
fi
}
```
Use one of these instead:
```bash
# OPTION A (real errors): print to stderr, then exit non-zero.
# Coyote captures stderr and attaches it to the error the model sees.
main() {
if [[ ! -d "$argc_path" ]]; then
echo "Error: directory not found: $argc_path" >&2
exit 1
fi
}
# OPTION B (expected "empty"/"not found" outcomes that are not real failures):
# write the note to $LLM_OUTPUT and exit 0 so Coyote returns it as normal content.
main() {
if [[ ! -d "$argc_path" ]]; then
echo "No such directory: $argc_path" >> "$LLM_OUTPUT"
return 0
fi
}
```
## Preserve a file's final line when it has no trailing newline
Two common idioms silently drop the last line of a file that does not end in `\n`:
- `while IFS= read -r line; do ...; done` stops before the final unterminated line, because `read` returns non-zero on it.
- `wc -l` counts *newlines*, so it under-reports the line count by one for such files.
```bash
# BAD: drops the final line when the file ends without a newline; total is off by one
total=$(wc -l < "$file")
while IFS= read -r line; do
printf '%s\n' "$line"
done < "$file"
# GOOD: `|| [[ -n "$line" ]]` processes the final unterminated line; awk counts it correctly
total=$(awk 'END { print NR }' "$file")
while IFS= read -r line || [[ -n "$line" ]]; do
printf '%s\n' "$line"
done < "$file"
```
## Type numeric options with `<INT>` / `<NUM>`
An `@option` with no value notation compiles to a **string** in the JSON schema, so the model tends to send `"359"`
instead of `359`. Add an `<INT>` (integer) or `<NUM>` (floating point) notation so the schema advertises the right type:
```bash
# BAD: schema type is "string"; the model sends "359"
# @option --offset The line to start from
# GOOD: schema type is "integer"; the model sends 359
# @option --offset <INT> The line to start from
```
Your script still receives the value as text in `argc_offset` either way (Bash has no typed variables), but the schema
now guides the model and lets strict providers validate the call.
---
# Prompt Helpers # Prompt Helpers
It's often useful to create interactive prompts for our bash tools so that our tools can get input from It's often useful to create interactive prompts for our bash tools so that our tools can get input from
users. users.