Skip to main content

Troubleshooting Guide

Setup Wizard Issues

Wizard shows “No TTY detected” when using curl pipe

Problem: Running curl ... | bash without a terminal redirects stdin, so the interactive wizard cannot receive keyboard input. Solution:

API key validation fails but key is correct

Problem: The wizard rejects an API key during live validation even though the key works. Solutions:
  1. Check network connectivity — the wizard makes outbound HTTPS requests to api.anthropic.com, api.openai.com, api.telegram.org
  2. Use --skip-validation to bypass live checks: pnpm wizard --skip-validation
  3. Corporate proxies or firewalls may block API calls — configure HTTPS_PROXY env var before running wizard

”I have Claude Pro/Max subscription - can I use it for EchOS?”

Problem: Confusion about Anthropic subscription plans vs API access. Answer: ❌ No, subscription plans cannot be used for EchOS.
  • Claude Pro/Max subscriptions (2020-200/month) are for using Claude through Anthropic’s web, desktop, and mobile apps only
  • They do NOT provide API access for programmatic integration
  • EchOS requires a separate Anthropic API account with pay-as-you-go billing
  • API costs are typically much lower than subscriptions for automated use cases (often ~$5/month for typical personal use)
What you need:
  1. Sign up for Anthropic API access at https://console.anthropic.com/
  2. Add credits or set up billing
  3. Generate an API key under Settings → API Keys
  4. Use that key in EchOS .env file
See: the Anthropic pricing page for a comparison of subscription vs API plans.

Wizard exits immediately in --non-interactive mode

Problem: Missing required env vars. Required variables for non-interactive mode:
  • ALLOWED_USER_IDS
  • At least one LLM key: ANTHROPIC_API_KEY or LLM_API_KEY

.env file has wrong permissions

Problem: Other users on the system can read your API keys. Fix:
The wizard sets 0600 automatically, but if you created .env manually, set it yourself.

”No .env file found. Run: pnpm wizard” on startup

Problem: pnpm start exits immediately with this message. Solution: Run the setup wizard:
Or for CI environments:

Build and Installation Issues

Cannot find package ‘@echos/shared’ (or other @echos/* packages)

Problem: Workspace packages aren’t built or aren’t being resolved by tsx. Symptoms:
Solutions:
  1. Build all packages first:
  2. If build fails, clean and rebuild:
  3. Verify path mappings: The root tsconfig.json includes these mappings for tsx:

LanceDB Native Module Errors

Problem: LanceDB native bindings missing for your platform. Symptoms:
Solutions:
  1. Intel Macs (darwin-x64):
    • The project is configured to use LanceDB 0.22.3 (last version with Intel Mac support)
    • This is set in packages/core/package.json
    • If you see this error, run: pnpm install --force
  2. Apple Silicon Macs (darwin-arm64):
    • Should work with LanceDB 0.26.2+
    • If issues persist, try: pnpm install --force
  3. Linux/Windows:
    • LanceDB should auto-install the correct native binding
    • Run: pnpm install --force if needed
  4. Check what’s installed:

Configuration Errors

Problem: Missing required environment variables. Symptoms:
Solution:
  1. Copy the example file:
  2. Edit .env and fill in required values:
  3. Get your Telegram user ID:
    • Message @userinfobot on Telegram
    • Add your ID to ALLOWED_USER_IDS
  4. Verify the file loads (Node 20.6+):

Runtime Issues

Telegram Bot Conflicts

Problem: Multiple bot instances trying to poll Telegram simultaneously. Symptoms:
Solution: Only one instance can poll Telegram updates at a time. The conflict can come from:
  • Local processes on your machine
  • Remote deployments (VPS, cloud instances, etc.)
  • Docker containers
  • Another developer’s machine using the same bot token

Step 1: Check local processes

Step 2: Check Docker containers

Step 3: Check bot webhook status

Step 4: Clear webhook and pending updates

Step 5: Check remote deployments

If you deployed to a remote server, check there:

Step 6: Wait for timeout

If another instance was recently stopped, Telegram may still have an active long-polling connection (30 second timeout). Wait 30-60 seconds then try again.

Step 7: Stop all instances

If the conflict persists:
For Production: Use webhooks instead of long-polling to avoid conflicts:
Configure in .env:
Note: Implementing webhook support requires code changes in packages/telegram/src/index.ts.

Redis Connection Errors

Problem: Cannot connect to Redis. Symptoms:
Solutions:
  1. Use the Redis management script (recommended):
  2. Manual platform-specific start (if script fails):
  3. Check Redis URL in .env:
  4. Verify Redis is running:
Note: Redis is required for EchOS. If Redis is not running at startup, EchOS will exit with an error.

Database/Storage Errors

Problem: SQLite or LanceDB initialization failures. Symptoms:
Solutions:
  1. Create data directories:
  2. Check permissions:
  3. Check paths in .env:
  4. Clean state (CAUTION: deletes all data):

docker compose ps shows echos as unhealthy even though it’s working

Problem: Prior to this fix, the Docker healthcheck did fetch('http://localhost:3000/health'), which is served by the web interface (packages/web). If ENABLE_WEB=false (e.g. Telegram-only deployments), nothing ever listens on port 3000, so the healthcheck failed permanently — even on a perfectly healthy container. This made docker compose ps useless for spotting real problems, since “unhealthy” was the permanent baseline rather than a meaningful signal. Fix: The healthcheck now checks a liveness heartbeat file (/tmp/echos-heartbeat, written every 15s by src/heartbeat.ts) instead of the web interface’s /health endpoint. This works regardless of ENABLE_WEB/ENABLE_TELEGRAM/ENABLE_MCP and still catches a wedged event loop (a hung process stops writing the heartbeat, same as it would stop responding to HTTP). If you’re still seeing “unhealthy” after upgrading:
On low-memory hosts (e.g. Oracle Cloud’s free-tier VM.Standard.E2.1.Micro, 1GB RAM / no swap), a wedged-but-still-”Running” container after an OOM kill is the most common root cause. Check dmesg -T | grep -i oom on the host for confirmation, and add swap so transient spikes (e.g. the backup job’s tar/cp work) don’t trigger the OOM-killer:

Development Issues

TypeScript Errors

Problem: Type errors when editing code. Solutions:
  1. Check all packages:
  2. Rebuild after package changes:
  3. Use watch mode during development:

Vitest Test Failures

Problem: Tests failing or hanging. Solutions:
  1. Run tests:
  2. Run specific test file:
  3. Watch mode:

Storage Sync Issues

Problem: You added a .md file directly to the knowledge/ directory (or edited one in an external editor), but the agent can’t find it via search. How sync works:
  • On startup, EchOS reconciles all markdown files with SQLite and LanceDB automatically
  • While running, a file watcher picks up any add, change, or unlink events in real time (debounced 500 ms)
If a file isn’t being found:
  1. Check the file has a valid id in frontmatter — files without an id field are silently skipped:
  2. If you added the file while the app was stopped — just restart. The startup reconciler will pick it up:
  3. If you added the file while the app is running — the file watcher should index it within ~1 second. If it doesn’t appear after a few seconds, check the logs:
  4. Enable debug logging to see all reconciler/watcher events:

Search results show stale content after editing a note externally

Problem: You edited a markdown file in VS Code or Obsidian, but the agent still returns the old content. The content hash prevents unnecessary re-indexing, so re-indexing only happens when the body text of the note changes (not just frontmatter). If the watcher picked up the change but content looks stale, wait a moment for the debounce window (500 ms) then retry. If the issue persists after restarting the app, check that the file has a valid id in its frontmatter — files without an ID are skipped by both the reconciler and the watcher.

Memory Issues

Memory stored but not recalled after /reset

Problem: You told the agent to remember something, then after /reset it doesn’t know the fact. How memory works:
  • remember_about_me stores facts permanently in SQLite (survives /reset)
  • On every new session, the top 15 memories by confidence are injected into the system prompt automatically
  • Additional memories beyond the top 15 are searchable via recall_knowledge
If a memory isn’t being recalled:
  1. It may be beyond position 15 — ask explicitly: “recall what you know about X” to trigger recall_knowledge
  2. The search uses keyword matching — use related terms: “recall what you know about my birthday” or “recall birth year”
  3. Increase confidence when storing important facts: the agent can be told “remember this with high confidence”
To see all stored memories: ask “list everything you remember about me” — the agent will use recall_knowledge to retrieve all entries.

Content Status Issues

Article shows up in knowledge search even though I haven’t read it:
  • Articles saved via save_article start with status: saved (reading list), not status: read (knowledge)
  • If the agent is mixing them, remind it: “distinguish between saved articles and things I’ve actually read”
  • You can filter: “show only what I’ve actually read about X”
Reading list shows nothing:
  • Ask: “show my reading list” — the agent calls list_notes(status="saved")
  • Articles saved before this feature was introduced may have status: null — they won’t appear in filtered lists; use update_note or mark_content to set their status
Agent marks article as read when I didn’t ask:
  • This is intentional: when you begin actively discussing a saved article, the agent auto-marks it read
  • To prevent this, tell the agent you’re just asking about the topic in general, not discussing that specific article
save_conversation creates too much noise:
  • save_conversation is only called when you explicitly ask (“save this conversation” or “save what we discussed about X”)
  • It is never called automatically

CLI Issues (pnpm echos)

Empty response — agent runs but prints nothing

Most likely cause: The configured model is deprecated and the API returns an empty response. Check: Look for a deprecation warning in the output. If you see The model '...' is deprecated, update DEFAULT_MODEL in .env to a current model (e.g. claude-haiku-4-5-20251001). Debug: Run with logging enabled to see what happens:

Startup logs cluttering the output

By default the CLI suppresses all logs below warn. If you’re seeing INFO logs:
  1. Check whether LOG_LEVEL is exported in your shell: echo $LOG_LEVEL
  2. Unset it or set it to warn: unset LOG_LEVEL

Shell parse errors (zsh: parse error near '\n')

Special characters (?, >, *, !, &) in arguments are interpreted by the shell before echos sees them. Always quote arguments:

History not persisting between sessions

The history file is ~/.echos_history. If it doesn’t persist:
  • Check write permissions: ls -la ~/.echos_history
  • The file is created on the first clean exit (exit / quit / Ctrl+D). Kills via kill -9 skip the save.

YouTube Transcript Issues

”Unable to save this video” / YouTube transcript fails

Problem: The YouTube plugin uses youtube-transcript-api-js (pure JS) to fetch transcripts. Failures are usually caused by the server IP being blocked by YouTube. Symptoms:
  • Bot responds with “I was unable to save this video”
  • Logs show: YouTube transcript unavailable [IpBlocked] or YouTube transcript unavailable [RequestBlocked]
Fixes:
  1. Local machine: No setup required — home IPs are not blocked by YouTube.
  2. Docker / VPS: The Docker image includes all required JS dependencies. No extra steps needed.
Proxy for cloud deployments: YouTube blocks most cloud/datacenter IPs. Configure a Webshare rotating proxy in .env:
Use your base Webshare username — without the -rotate suffix. The app appends -rotate automatically to enable IP rotation. If you copy the username directly from the Webshare dashboard and it already ends in -rotate, remove that suffix before pasting it here.
When set, transcript requests and audio downloads route through p.webshare.io:80.

Whisper transcribes audio in the wrong language

Problem: When a YouTube video has no captions and falls back to Whisper transcription, or when you send a Telegram voice message, Whisper may misidentify the language — e.g. transcribing English audio as Welsh or another language. Symptoms:
  • Transcript output is garbled or in the wrong language
  • Voice message responses don’t match what you said
Fix: Pin the transcription language by setting WHISPER_LANGUAGE in your .env:
This accepts an ISO-639-1 two-letter code (e.g. en, fr, de, es, it, ja). When set, Whisper skips auto-detection and transcribes in the specified language. If unset, Whisper auto-detects (which can fail on short clips or accented speech).

Twitter / X Issues

”Unable to save this tweet” / Tweet fetch fails

Problem: The Twitter plugin uses the free FxTwitter API (api.fxtwitter.com). If the tweet is unavailable or the API is temporarily down, the save will fail. Symptoms:
  • Agent responds with “I wasn’t able to save that tweet” or similar
  • Logs show: save_tweet failed with an error message
Fixes:
  1. Check the URL format — the plugin accepts these formats:
    • https://twitter.com/<user>/status/<id>
    • https://x.com/<user>/status/<id>
    • https://mobile.twitter.com/<user>/status/<id>
    • https://fxtwitter.com/<user>/status/<id> / https://vxtwitter.com/<user>/status/<id>
  2. Check that the tweet exists and is public — private/protected accounts and deleted tweets cannot be fetched.
  3. Enable debug logging to see the full error from the FxTwitter API:
  4. FxTwitter rate limiting — the FxTwitter API is a free public service and may occasionally rate-limit requests. Wait a moment and try again.

Agent uses create_note instead of save_tweet

Problem: The agent creates a generic note when given a Twitter URL instead of calling save_tweet. Fix: The system prompt includes explicit URL routing. If this happens, it may indicate the system prompt wasn’t applied to the session. Try resetting the session:
  • Telegram: Send /reset
  • CLI: Exit and restart (exit or Ctrl+D)
The routing rules in the system prompt direct twitter.com and x.com URLs to save_tweet, not create_note.

MCP Server Issues

MCP server not starting

Problem: ENABLE_MCP=true is set but no “MCP server started” log line appears. Solution: Check for a port conflict on MCP_PORT (default 3939):
Kill the conflicting process or change MCP_PORT in .env, then restart the daemon.

Claude Code shows the MCP server as disconnected

Problem: The echos server appears in Claude Code but shows as disconnected or errors on use. Solution:
  1. Confirm the daemon is running (pnpm start or check your service manager).
  2. Verify the URL in .claude.json matches MCP_PORT in .env.
  3. If MCP_API_KEY is set, confirm the Authorization header value matches exactly.

Unauthorized errors from the MCP server

Problem: Requests return 401 Unauthorized. Solution: The bearer token in your client config does not match MCP_API_KEY. Regenerate and update both:

Getting Help

If you’re still stuck:
  1. Check the logs: EchOS uses Pino for structured logging
  2. Enable debug logging: Set LOG_LEVEL=debug in .env
  3. Check system requirements:
    • Node.js 20+ (node --version)
    • pnpm 9+ (pnpm --version)
    • Redis running (redis-cli ping)
    • Enough disk space for embeddings/vectors
  4. Review security docs: See Security
  5. Architecture overview: See Architecture