tool-kit calls models from the shell, to explain a script, vet one, write one or draft a commit message. Every one of those calls follows the overview's rule: the AI may advise, and it never owns an exit code. This page is about the plumbing that keeps the rule true: one engine the scripts share, a filter in front of it, a fallback ladder for commit messages, and a measured try at running the work on a local model.
One engine behind the AI scripts
ai-call.sh takes a system prompt and some input, calls an OpenAI-compatible chat endpoint and prints the answer. The caller owns the prompt. The engine owns the transport, the list of endpoints, the failover and the reading of the reply, and it never runs anything it sends or receives. A new script does not write a chat request of its own: it calls the engine.
The rule came from a copy. The commit-message script carried its own copy of the call code until 2026-08-12, and by then the copy had fallen behind the engine on six fixes. One of them: a 401 whose body happened to parse came back as the commit message. The engine's rules, each with the failure it rules out:
- The status code is read.
curlexits 0 for an HTTP 500 exactly as for a 200, and it has already written the error page where the answer was expected. So the engine reads the status, reports a refusal as the 401 it is, and moves on to the next endpoint. - The key column is data, not code. A key in the endpoints file is a
$NAMEreference, looked up through bash's indirect expansion. On a name that is not a plain identifier, bash reads an array subscript and runs the command inside it, so the key column could execute. Now anything but a plain reference is refused with a warning. - The key never sits in argv. The
Authorizationheader goes tocurlas a config on its stdin, never as an argument, because any user on Linux can read a process's command line. - Loopback is not local. Ollama's cloud tags are proxies: a request to 127.0.0.1 for a
-cloudmodel is forwarded to the provider. The model's name is the only signal available, so the engine warns on the name, and it would rather warn too often than too rarely. - A typo is an error. Until 2026-09-13, a
--configfile that did not exist fell back to the default endpoint, with whatever key the shell had exported, so a typo in a path changed where a key was sent. Now it exits 2 before any endpoint is contacted.
$ ai/explain-script.sh --config no-such.conf ai/ai.sh note: comments removed before sending -- the explanation describes the code (--keep-comments to include them) Error: endpoints file 'no-such.conf' does not exist. $ echo $? 2
The model never sees the comments
A comment can lie about the line beside it. A model that reads both will often repeat the comment, because the comment is the most confident sentence in the file. Telling the model to ignore comments does not help: that is an instruction, and an instruction is exactly what a manipulated model stops following. So the explain and vet scripts send a copy with the comments taken out, and vet prints the comments back to you, for a person to compare with the code:
$ cat cleanup.sh #!/usr/bin/env bash # cleanup: removes only temporary files rm -rf "$HOME" $ ai/strip-comments.sh cleanup.sh #!/usr/bin/env bash rm -rf "$HOME" $ ai/strip-comments.sh cleanup.sh --comments-only 2: # cleanup: removes only temporary files $ ai/tests/fuzz.sh fuzz: 400 scripts, 0 mismatches (seed 1)
The stripper is deliberately conservative. A # opens a comment only at the start of a line or after a space or a tab, and only outside every quote, heredoc and arithmetic expression, with the quoting carried across lines. Where a case is ambiguous, the text stays. A missed comment is harmless, but a mangled line sends the model code nobody wrote.
The last line of that capture is the real check. bash -n parsed the stripped output cleanly for all four revisions of the tokenizer, including the three that rewrote code. So a fuzzer builds scripts from a grammar of awkward fragments, strips each one, runs both copies and compares the exit code, stdout and stderr. It earns its place because it can fail: given an older revision, it reports mismatches within a hundred scripts.
Commit messages, with a last rung that never fails
The commit-message script sends the diff of the uncommitted changes to a model and prints a message. It never commits. A git-cola hook fills the GUI's commit box with it, and on failure the box is left as it was. On a machine that opts in, the push sweeps that commit across my repositories draft their messages through a ladder, tried from the top: an OpenAI-compatible endpoint, or the local Claude Code CLI, and last a static rung that never fails. A rung whose key, transport or endpoint fails falls to the next one, so a sweep gets its commit message with or without a model.
A local model, measured
Another tool reads my exported Claude Code sessions and reports on them. Its cost line is always API-equivalent, what the tokens would cost at list price, because that is the one measure that compares two models or two tools. In September 2026 I tried its model pass on a local model, so that the transcripts would never leave the machine. It needed only configuration, no change to the code. On a clean re-run of the same four jobs, on 2026-09-27:
- Time: 592 s for Gemma-4 E2B, one job at a time on a machine busy with other sessions, against 35 s for the
clauderunner's four workers. - Findings tied to a real turn: 11 against 38, with none invented on either side.
- Cost: nothing for the local model, $0.76 API-equivalent for the
clauderunner.
So the runner stays claude. The small model finds what a transcript states outright, and misses what has to be read between turns: an approval gate skipped, a scope widened, a constraint that came late.
The probe also found a bug that was not the model's. When Claude Code loads a skill, it injects the skill's text as a user-role record, and the exporter wrote that record as the human's own turn. Both small models read it as the human pasting documentation. The fix went into the exporter, in 0-libraries, and since then no finding blames the human for a skill's text.
Status lines, briefly
The status lines I run in Claude Code and Qwen Code are generated: one settings file and a template for each line produce the scripts, which are committed, and nothing reads the settings at run time. A check exits 1 when a generated file has drifted. Qwen kills a status command after 5 s and blanks the line, so the script gives all its git calls one 3 s budget together. A git that hangs costs the branch and the repository name, never the line.