Troubleshooting
Common issues and fixes for Untether. If your agent isn't responding, messages aren't arriving, or something looks off — start here.
Common issues and fixes for Untether. If your agent isn’t responding, messages aren’t arriving, or something looks off — start here.
Quick diagnostics
Before diving into specific issues, run these two commands:
untether --debug # start with debug logging → writes debug.log
untether doctor # preflight check: token, chat, topics, files, voice, engines
$ untether doctor
✓ bot token valid (@my_untether_bot)
✓ chat 123456789 reachable
✓ engine codex found at /usr/local/bin/codex
✓ engine claude found at /usr/local/bin/claude
✗ engine opencode not found
✓ voice transcription configured
✓ file transfer directory exists
Bot not responding
Symptoms: You send a message but the bot doesn’t reply at all.
- Check that Untether is running:
- Terminal: Look at the terminal where you ran
untether— is it still running? - Linux (systemd):
systemctl --user status untether
- Terminal: Look at the terminal where you ran
- Verify your bot token:
untether doctorwill flag an invalid token - Check
allowed_user_ids— if set, only listed users can interact. An empty list means everyone is allowed. - In a group chat, check trigger mode: if set to
mentions, you must @mention the bot - Make sure you’re messaging the correct bot (not a different one)
Engine CLI not found
Symptoms: “codex: command not found” or similar error after sending a task.
The engine CLI isn’t on your PATH. Install the engine you need:
# Codex
npm install -g @openai/codex
# Claude Code
npm install -g @anthropic-ai/claude-code
# OpenCode
npm install -g opencode-ai@latest
# Pi
npm install -g @mariozechner/pi-coding-agent
# Gemini CLI
npm install -g @google/gemini-cli
# Amp
npm install -g @sourcegraph/amp
Verify with which codex (or which claude, etc.). If installed via npm -g but not found, check that npm’s global bin directory is in your PATH.
Run untether doctor to see which engines are detected.
Permission denied or auth errors
Symptoms: Engine starts but fails with authentication or permission errors.
- Codex: Run
codexin a terminal and sign in with your ChatGPT account - Claude Code: Run
claude loginto authenticate. On macOS, credentials are stored in Keychain; on Linux, in~/.claude/.credentials.json - OpenCode: Run
opencodeand authenticate with your chosen provider - Pi: Run
piand log in with your provider - Gemini CLI: Run
geminiand authenticate with your Google account - Amp: Run
ampand sign in with your Sourcegraph account
Progress stuck on “starting”
Symptoms: The progress message shows “starting” but never updates.
- The engine might be doing a slow first-time setup (repo indexing, dependency install). Wait 30-60 seconds.
- If it persists,
/cancel(reply to the progress message) and try a more specific prompt - Check
debug.log— the engine may have errored silently - Verify the engine works standalone: run
codex "hello"(or equivalent) directly in a terminal
Messages too long or truncated
Symptoms: The bot’s response is cut off or split across multiple messages.
Telegram messages have a 4096-character limit. Untether handles this automatically:
- Split mode (default): Long responses are split across multiple messages (~3500 chars each)
- Trim mode: Single message, truncated to fit
To change:
=== “untether config”
```sh
untether config set transports.telegram.message_overflow "trim"
```
=== “toml”
```toml title="~/.untether/untether.toml"
[transports.telegram]
message_overflow = "trim" # or "split" (default)
```
Voice transcription not working
Symptoms: Sending a voice note doesn’t start a run, or you get a transcription error.
-
Check that voice transcription is enabled:
[transports.telegram] voice_transcription = true -
Make sure you have an OpenAI API key set (voice transcription uses the OpenAI transcription API by default)
-
Check the voice note size — default max is 10 MiB (
voice_max_bytes) -
If using a custom transcription server, verify
voice_transcription_base_urlis reachable
Run untether doctor to validate voice configuration.
File transfer blocked
Symptoms: /file put or /file get fails, or dropped documents aren’t saved.
-
Check that file transfer is enabled:
[transports.telegram.files] enabled = true -
Check
deny_globs— files matching these patterns are blocked (default:.git/**,.env,*.pem,.ssh/**) -
In group chats, file transfer requires admin or creator status (unless
files.allowed_user_idsis set) -
Check the
uploads_dirpath exists relative to the project root
Topics not appearing
Symptoms: /topic doesn’t work, or topics aren’t binding to projects.
-
Topics require a forum-enabled supergroup (not a private chat or regular group)
-
The bot must be admin with “Manage Topics” permission
-
Topics must be enabled in config:
[transports.telegram.topics] enabled = true scope = "auto" # or "main", "projects", "all" -
Run
untether doctor— it checks topic permissions
Webhook not receiving events
Symptoms: Webhooks are configured but never fire.
- Check that triggers are enabled:
[triggers] enabled = true - Verify the server is running:
curl http://127.0.0.1:9876/health(adjust host/port) - Check auth — if using HMAC, the sending service must sign requests with the same secret
- Check
event_filter— if set, only matching event types are processed - Check firewall rules if the webhook server is behind NAT
- Look at
debug.logfor incoming request logs
Session not resuming
Symptoms: Sending a follow-up message starts a new session instead of continuing.
- Chat mode (
session_mode = "chat"): Just send another message — it auto-resumes. Use/newto start fresh. - Stateless mode (
session_mode = "stateless"): You must reply to a message that contains a resume token. Plain messages start new sessions. - If resume fails silently, the previous session may have been corrupted. Untether auto-clears broken resume tokens (0-turn sessions).
Claude Code plugin interference
Symptoms: Agent completes successfully but the response is about “hooks”, “context docs”, or “false positive” instead of the content you actually asked for. The run shows done with a short answer that doesn’t match your request.
This happens when Claude Code plugins with Stop hooks consume the final response. In a terminal, the user can scroll up to see earlier output. In Telegram, only the final message is visible — so if a Stop hook causes Claude to address hook concerns in its last turn, the actual content is replaced.
Affected plugins: Any Claude Code plugin that uses "decision": "block" in a Stop hook. The most common example is PitchDocs context-guard, which nudges Claude to update AI context docs when structural files change.
Fix:
-
Update the plugin — PitchDocs v1.20+ checks for
$UNTETHER_SESSIONand automatically skips blocking Stop hooks in Telegram sessions. Run/pitchdocs:context-guard installin your project to update the hooks. -
Verify
UNTETHER_SESSIONis set — Untether v0.34.4+ setsUNTETHER_SESSION=1in the Claude runner subprocess environment. If you’re on an older version, upgrade:pipx upgrade untether -
For custom plugins — add this to your Stop hook script:
[ -n "${UNTETHER_SESSION:-}" ] && echo '{}' && exit 0
This is not a security concern — UNTETHER_SESSION is a simple signal variable that tells plugins the session is running via Telegram. See the interference audit for a detailed case study.
Cost budget blocking runs
Symptoms: “Budget exceeded” message, or runs are cancelled mid-stream.
-
Check your budget settings:
[cost_budget] enabled = true max_cost_per_run = 2.00 # USD per run max_cost_per_day = 20.00 # USD per day auto_cancel = true # cancels runs exceeding per-run limit -
Daily budgets reset at midnight UTC
-
To temporarily bypass: set
enabled = falseor increase the limits -
Check current spend with
/usage
Group chat: bot ignoring messages
Symptoms: Bot works in private chat but ignores messages in a group.
- Check trigger mode: groups default to
mentionsin many setups. Send/triggerto check, or/trigger allto respond to everything. - Check bot privacy mode in BotFather: send
/setprivacyto @BotFather and select your bot. Set to “Disable” so the bot can see all messages (not just commands and @mentions). - Check
allowed_user_ids— if set, group members not in the list are ignored. - If using topics, make sure the bot has “Manage Topics” permission.
macOS and Linux credential differences
| Platform | Claude Code credentials | Path |
|---|---|---|
| Linux | Plain-text JSON file | ~/.claude/.credentials.json |
| macOS | macOS Keychain | Entry: Claude Code-credentials |
Untether checks both locations automatically. If you’ve recently changed platforms or reinstalled, run claude login to refresh credentials.
Using debug mode
Start Untether with --debug for full diagnostic logging:
untether --debug
This writes to debug.log in the current directory. The log includes:
- Engine JSONL events (every line the subprocess emits)
- Telegram API requests and responses
- Rendered message content
- Error tracebacks
Include debug.log when reporting issues on GitHub.
Using untether doctor
Run untether doctor for a comprehensive preflight check:
untether doctor
It validates:
- Telegram bot token (connects and verifies)
- Chat ID (reachable)
- Topics configuration (permissions, forum group status)
- File transfer settings (deny globs, permissions)
- Voice transcription configuration (API reachability)
- Engine CLI availability (on PATH)
$ untether doctor
✓ bot token valid (@my_untether_bot)
✓ chat 123456789 reachable
✓ engine codex found at /usr/local/bin/codex
✓ engine claude found at /usr/local/bin/claude
✓ engine opencode found at /usr/local/bin/opencode
✓ voice transcription configured
✓ file transfer directory exists
all checks passed
Checking logs
=== “Terminal (all platforms)”
Untether logs to the terminal by default. For detailed logs:
```sh
untether --debug # writes debug.log in current directory
```
=== “Linux (systemd)”
```sh
journalctl --user -u untether -f # live logs
journalctl --user -u untether -n 100 # last 100 lines
journalctl --user -u untether -b # since last boot
```
Look for handle.worker_failed, handle.runner_failed, or config.read.toml_error entries.
Error hints
When an engine fails, Untether scans the error message and shows an actionable recovery hint below the error. These hints cover the most common failure modes across all engines and providers.
Authentication errors
| Error | Hint |
|---|---|
| Access token could not be refreshed | Run codex login --device-auth to re-authenticate |
| Log out and sign in again | Run codex login to re-authenticate |
anthropic_api_key | Check that ANTHROPIC_API_KEY is set in your environment |
openai_api_key | Check that OPENAI_API_KEY is set in your environment |
google_api_key | Check that your Google API key is set in your environment |
Subscription and billing limits
| Error | Hint |
|---|---|
| Out of extra usage / hit your limit | Subscription usage limit reached — wait for the reset window, then resume |
insufficient_quota / exceeded your current quota | OpenAI billing quota exceeded — add credits at platform.openai.com |
billing_hard_limit_reached | OpenAI billing hard limit — increase your spend limit at platform.openai.com |
resource_exhausted | Google API quota exhausted — check quota at console.cloud.google.com |
API overload and server errors
| Error | Hint |
|---|---|
overloaded_error (529) | Anthropic API overloaded — temporary, session saved, try again in a few minutes |
| Server is overloaded | API server overloaded — temporary, try again in a few minutes |
internal_server_error (500) | Internal server error — usually temporary, try again shortly |
| Bad gateway (502) | Bad gateway error — usually temporary, try again shortly |
| Service unavailable (503) | API temporarily unavailable — try again in a few minutes |
| Gateway timeout (504) | Gateway timed out — usually temporary, try again shortly |
Rate limits
| Error | Hint |
|---|---|
| Rate limit / too many requests | Rate limited — the engine will retry automatically |
Network errors
| Error | Hint |
|---|---|
| Connection refused | Check that the target service is running |
| Connect timeout | Connection timed out — check your network, then try again |
| Read timeout | Connection timed out — usually transient, try again |
| Name or service not known | DNS resolution failed — check your network connection |
| Network is unreachable | Network unreachable — check your internet connection |
Process signals
| Error | Hint |
|---|---|
| SIGTERM | Untether was restarted — session saved, resume by sending a new message |
| SIGKILL | Process forcefully terminated (timeout or OOM) — session saved, try resuming |
| SIGABRT | Process aborted unexpectedly — try starting a fresh session with /new |
Session and process errors
| Error | Hint |
|---|---|
| Session not found | Try a fresh session without —session flag |
| Error during execution | Session failed to load (possibly corrupted) — send /new to start fresh |
| Finished without a result event | Engine exited before producing a final answer (crash or timeout) — session saved, try resuming |
| Finished but no session_id | Engine crashed during startup — check that the engine CLI is installed and working |
All hints are case-insensitive and pattern-matched against the full error output. The first matching hint wins. Your session is automatically saved in most cases, so you can resume after resolving the issue.
Related
- Operations and monitoring —
/ping,/restart, hot-reload - Configuration reference — all config options
- Commands & directives — full command reference