Skip to content

Troubleshooting — hotl the agent

Look up the message you saw. Text in code is what hotl prints; find yours by grepping this file for a distinctive phrase. Run hotl doctor first for setup problems — it diagnoses most of the table below in one shot.

Message or symptom Cause Fix
ANTHROPIC_API_KEY is not set. Provider is anthropic (the default) but no key. Set ANTHROPIC_API_KEY, or switch to another provider: export HOTL_MODEL=openai/<model> with OPENAI_API_KEY, or HOTL_OPENAI_BASE_URL for a local endpoint.
You have a Claude Pro/Max plan and no API key A subscription covers Claude Code and claude.ai, not third-party tools. Get a key from the Claude Console (billed per token), or run a local model. Full answer: can I use my Claude subscription?
OPENAI_API_KEY is not set (required for api.openai.com; …) openai provider against the default hosted URL, no key. Set OPENAI_API_KEY, or point HOTL_OPENAI_BASE_URL at a local server (e.g. http://localhost:11434/v1) to run keyless.
unknown provider \X` in HOTL_MODEL` HOTL_MODEL isn’t anthropic/… or openai/…. Use provider/model. openai covers all OpenAI-compatible endpoints.
doctor provider line shows FAIL Same as the above three. Fix the env vars in the shell you’ll run hotl from, then re-run hotl doctor.
WARNING — HOTL_OPENAI_BASE_URL is a non-loopback http:// URL and OPENAI_API_KEY is set Your key would cross the network unencrypted. Use https://, an SSH tunnel, or a loopback address. The run proceeds, but the key is exposed.
Message or symptom Cause Fix
The agent’s action was (denied) and you never saw a prompt Headless (-p) or non-interactive terminal — asks auto-deny. Run interactively, or add an allow-rule in config.toml for the action the run needs. See configuration.md.
An allow-rule you wrote still prompts The command has a shell operator, the path escapes the prefix via .., the target is a protected path, or (for bash) the sandbox isn’t enforced. Expected — these are the carve-outs. See permissions-and-sandbox.md. Simplify the command, or approve it by hand.
Ask shows UNSANDBOXED No kernel sandbox on this host, or HOTL_SANDBOX=off. On older Linux, none is available; on macOS ensure /usr/bin/sandbox-exec exists. bash allow-rules are disabled while unsandboxed, by design.
The agent’s git push fails with Permission denied (publickey), or aws/gcloud/npm can’t find credentials The read carve denies ~/.ssh, ~/.aws, ~/.config/gcloud, ~/.azure and the credential dotfiles to sandboxed commands. Load your key into ssh-agent (ssh-add) — the agent socket stays reachable and ssh then never opens a key file. For a one-off, press s instead of y at the ask. For a standing lift, add readable = ["~/.aws"] under [sandbox] and restart the session.
Your [[deny]] rule stops the read tool but not bash cat … A path deny reaches shell commands only when it can be expressed at the kernel: an absolute or ~/-rooted path_prefix naming a directory that exists. A floating relative prefix (.ssh/), a prefix on a path tool, a field override, and a command-subject rule (bash prefix = "curl ") cannot be. Run hotl doctor. If the path is listed under containment · reads denied to shell commands, it is enforced at the kernel. If the rule is listed under not reaching shell commands, the same line names the form to write instead — usually path_prefix = "~/.ssh" rather than ".ssh/". See deny-rules.
hotl doctor says a [[deny]] path sits inside the writable root … Landlock resolves the closest matching rule, so a write grant on an ancestor re-opens the read. The path is inside your working directory, TMPDIR, /dev, or a [sandbox].writable entry. Undeliverable at the kernel, so it is dropped rather than reported as live — the in-process deny still holds. Move the directory outside every write root if you need shell commands covered.
A [[deny]] rule you wrote started refusing things it never used to Before 0.11 a [[deny]] in config.toml was dropped before the rules loaded, so only /etc/hotl/preapproved.toml could deny. It now loads, and ~/-rooted prefixes expand. Intended — the rule does what it said. Narrow or remove the rule if that is not what you meant.
read/edit refuses a path under ~/.config/hotl or ~/.local/share/hotl, in every mode Tier A of the read carve. The session token there drives hotl’s control socket, so no approval unlocks it. Intended, and there is no override. Open the file yourself and paste what the agent needs.
Every bash ask says reads:open [sandbox].readable lifted the whole credential tier, or the probe found the carve unenforceable on this host. Check hotl doctor — it prints the resolved deny set and whether the probe certified the carve.
sandbox: the read carve could not open … in hotl doctor The carve’s descent could not open a directory, so reads under it are denied to sandboxed commands without anyone asking for that. Fail-closed, but usually a permissions oddity on that directory. Fix its mode, or expect commands reading under it to fail.
⚠ PROTECTED PATH — before an ask The write targets a write-now/execute-later file (git hook, build.rs, ssh, creds, …). Intended. Approve only if you meant to write that file; it can run code or grant access later.
A command hangs about two minutes, then fails with hotl egress: "HOST" is not in [network].allow egress = "allowlist" and the host isn’t covered. An egress prompt went up and nobody answered inside the two-minute deadline. Answer the prompt (y allows the host for the session). Nothing is recorded on a timeout, so the next attempt asks again. Add the host to [network].allow to stop being asked.
Under allowlist, an unlisted host 403s instantly with (no interactive surface …) and you were never asked Headless (-p, --schema) and sub-agents never get the egress prompt — by construction, not by a flag. Add the host to [network].allow, or run interactively.
You get an egress prompt for a host you just approved in a bash command The command was approved by an [[allow]] rule rather than by you, or the URL carried userinfo (https://a.com@b.com/), or you edited the command at the ask. Working as intended — a rule is not a human, and in the userinfo case the host your eye read is not the host being reached. Answer the prompt.
Every host prompts and allow looks right defaults = false dropped hotl’s starter list, or the entries don’t match (no ports in patterns; example.com is exact, *.example.com is the wildcard). hotl doctor prints the effective list split by source.
You want egress restriction off entirely One line: egress = "open" under [network].
Message or symptom Cause Fix
stopped — the model kept repeating: … Doom-loop guard: the model made the same tool call in a tight cycle. In ask mode you declined to continue; in bypass/dontask it stops on its own (nobody is watching). Re-prompt with a more specific instruction; the loop usually means the task was ambiguous.
stopped — \TOOL` failed too many times in a row.` A tool failed 5 consecutive times (tool-failure budget). Check the tool’s error output in the transcript; the underlying command or path is wrong.
turn limit reached / stopped after N model steps The turn spent its max_turns budget (default 100 model steps; a tool round-trip costs one). Raise [behavior] max_turns in config.toml (or HOTL_MAX_TURNS). -1 removes the cap — the turn then ends only when the model is done, the context fills, or you interrupt.
(context compacted — …) Normal: history was summarized to stay within the window. None. If it happens too early, set HOTL_CONTEXT_WINDOW to your model’s real window size.
session log is sealed / could not create session log The session log couldn’t be written (permissions, disk). Check ~/.local/share/hotl/sessions/ is writable (hotl doctor reports this).
preapproved rules at … refused The admin file isn’t root-owned, or is group/world-writable. sudo chown root /etc/hotl/preapproved.toml && sudo chmod 644 /etc/hotl/preapproved.toml
permissions.mode=auto requested, but this is a security-enforced build Expected on enforced builds; per-action asks are the build’s contract. None.
Message or symptom Cause Fix
[minified unavailable for \X`: no grammar for this file type…]` The extension isn’t one of the six supported languages. None — the plain view was served. Expected for Markdown, TOML, shell, and everything else.
[minified unavailable for \X`: the file does not parse cleanly…]` The grammar found a syntax error. Either the file really is broken, or the pinned grammar is older than the syntax the file uses (a new language edition feature). If the file is valid, the grammar is stale — that’s the note’s purpose. The plain view was served, so nothing is blocked.
[minified unavailable …: the minifier produced output it could not verify…] Self-validation caught its own output: the view failed re-parse, or its structure didn’t match the source’s. A bug guard firing, not a file problem. None needed — it degraded to the plain view, which is the designed behavior. Worth reporting with the file.
[minified unavailable …: the minified view is N bytes, over the … cap] Minified reads are whole-file or nothing, and this file exceeds 200KB even minified. None — the plain paged read was served. Use offset/limit on it as usual.
minified reads return the whole file, so \offset`/`limit` do not apply` The model passed both minified: true and a paging argument. Expected, and self-correcting: the error names the plain read. The minified view has no line numbers to page by.
\old_string` was not found in the minified view of `X`` Almost always: the text was quoted from a plain read, whose whitespace differs. Re-read with minified: true and copy from that view — or drop minified from the edit.
the matched text … is only formatting the minifier inserted old_string covered only separators the minifier synthesized, which exist nowhere in the file. Include a real token in old_string.
multi-line replacements can corrupt python indentation through the minified view A new_string containing a newline, in a language where indentation is syntax. Use a plain edit (omit minified) for that change. Deliberate refusal, not a limitation to work around.
this edit would leave \X` no longer parsing; nothing was written` The projected splice would break the file. Caught before the write. Re-check old_string/new_string against a fresh minified read. The file is untouched.
this build has no minify support A --no-default-features build (no C toolchain). The minified argument isn’t in the tool schema for such builds, so a model shouldn’t reach this. Re-issue without minified, or use a default build.
Savings look smaller than expected Comment-light or small files save 10–18%; keep_comments = true (the default) is the conservative mode; JSX-heavy .tsx saves only on its non-JSX portion. Set [minify] keep_comments = false for 44–59%, accepting that the model reads code without the why. The per-read trailer always reports the real figure.
Message or symptom Cause Fix
config.toml ignored (parse error) Malformed config.toml. Fix the TOML; a bad file is ignored wholesale (fail-closed), so no servers load until it parses.
First mcp use shows a PROTECTED PATH-style screen with a hash First use of that server (or its binary changed). Expected — approving runs that binary and lets its output into context. Verify the path/hash, then approve.
MCP call returns … timed out after 30s The server didn’t respond. Check the server runs standalone; hotl won’t hang on it.
Message or symptom Cause Fix
no session starts with \X`` No session id has that prefix. Run bare hotl resume to list ids, then use a longer prefix.
WARNING — … broken parent_id chain … on resume The session log was edited or truncated after it was written. The context is still loaded, but treat it as untrusted — a broken chain means tampering or corruption.
hotl undo: no shadow snapshots found git wasn’t available when the session ran, so nothing was snapshotted. Install git; hotl doctor warns when snapshots are disabled.
hotl undo: no agent mutations to undo in session … The agent never mutated the workspace that session, so undo refuses rather than touch files that are only yours. Nothing to do — this is the safe refusal.
hotl undo: no snapshot to restore yet The agent has mutated, but the first capture is still staging (large repos take a moment, off the turn path). Retry in a moment; hotl doctor shows warming → ready.
hotl undo failed and named a locked shadow index A snapshot is being taken right now (the lock is legitimate), or a crashed session left a stale lock. Retry in a moment. If no hotl session is running, remove the index.lock path the message printed.
A file the agent created came back after hotl undo It shouldn’t: undo removes files created after the snapshot (the pre-undo checkpoint retains them, so nothing is unrecoverable). Re-run hotl undo; if it persists, file a bug.

Report a bug hotl mislabels or a fix that’s wrong: the harness treats a repeated failure as a docs/behavior bug — file it against the repo. Not covered here: live-provider quirks — no real model has driven hotl end to end yet, so novel model behavior is expected and worth reporting.