> ## Documentation Index
> Fetch the complete documentation index at: https://docs.echos.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting

> Common setup, runtime, and development issues with solutions.

# 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**:

```bash theme={null}
# Option A: Run install.sh first, then the wizard manually
curl -sSL https://raw.githubusercontent.com/albinotonnina/echos/main/install.sh | bash
# Follow the printed instructions to run: cd ~/echos && pnpm wizard

# Option B: Download first, then run (preserves TTY)
curl -sSL https://raw.githubusercontent.com/albinotonnina/echos/main/install.sh -o /tmp/install-echos.sh
bash /tmp/install-echos.sh
```

### 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 ($20-$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/](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](https://www.anthropic.com/pricing) 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`

```bash theme={null}
# Anthropic
ANTHROPIC_API_KEY=sk-ant-... ALLOWED_USER_IDS=123456789 pnpm wizard --non-interactive
# Other provider (e.g. Groq)
LLM_API_KEY=gsk_... DEFAULT_MODEL=groq/llama-3.3-70b-versatile ALLOWED_USER_IDS=123456789 pnpm wizard --non-interactive
```

### .env file has wrong permissions

**Problem**: Other users on the system can read your API keys.

**Fix**:

```bash theme={null}
chmod 0600 .env
ls -la .env  # should show: -rw------- 1 you ...
```

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:

```bash theme={null}
pnpm wizard
```

Or for CI environments:

```bash theme={null}
ANTHROPIC_API_KEY=... ALLOWED_USER_IDS=... pnpm wizard --non-interactive --skip-validation
# or with a non-Anthropic provider:
LLM_API_KEY=... DEFAULT_MODEL=groq/llama-3.3-70b-versatile ALLOWED_USER_IDS=... pnpm wizard --non-interactive --skip-validation
```

***

## 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**:

```
Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@echos/shared'
```

**Solutions**:

1. **Build all packages first**:

   ```bash theme={null}
   pnpm build
   ```

2. **If build fails, clean and rebuild**:

   ```bash theme={null}
   pnpm clean
   pnpm install
   pnpm build
   ```

3. **Verify path mappings**: The root `tsconfig.json` includes these mappings for tsx:

   ```json theme={null}
   "paths": {
     "@echos/shared": ["./packages/shared/src/index.ts"],
     "@echos/core": ["./packages/core/src/index.ts"],
     // ... etc
   }
   ```

### LanceDB Native Module Errors

**Problem**: LanceDB native bindings missing for your platform.

**Symptoms**:

```
Error: Cannot find module '@lancedb/lancedb-darwin-x64'
Error: Cannot find module '@lancedb/lancedb-darwin-arm64'
```

**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**:

   ```bash theme={null}
   ls node_modules/.pnpm/@lancedb*/
   ```

### Configuration Errors

**Problem**: Missing required environment variables.

**Symptoms**:

```
Error: Invalid configuration:
  telegramBotToken: Required
  allowedUserIds: Required
  anthropicApiKey: Required
```

**Solution**:

1. Copy the example file:

   ```bash theme={null}
   cp .env.example .env
   ```

2. Edit `.env` and fill in required values:

   ```bash theme={null}
   TELEGRAM_BOT_TOKEN=your_token_from_botfather
   ALLOWED_USER_IDS=123456789,987654321
   # Anthropic:
   ANTHROPIC_API_KEY=sk-ant-your-key-here
   # or another provider (e.g. Groq):
   # LLM_API_KEY=gsk_...
   # DEFAULT_MODEL=groq/llama-3.3-70b-versatile
   ```

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+):

   ```bash theme={null}
   # The start script uses --env-file flag
   pnpm start
   ```

## Runtime Issues

### Telegram Bot Conflicts

**Problem**: Multiple bot instances trying to poll Telegram simultaneously.

**Symptoms**:

```
GrammyError: Call to 'getUpdates' failed! (409: Conflict: 
terminated by other getUpdates request; make sure that only 
one bot instance is running)
```

**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

```bash theme={null}
ps aux | grep "tsx.*index.ts"
# Or more broadly
ps aux | grep echos
# If only the grep command appears, no local instances are running
```

#### Step 2: Check Docker containers

```bash theme={null}
docker ps | grep echos
# If no output, no containers running
```

#### Step 3: Check bot webhook status

```bash theme={null}
./scripts/check-telegram-bot.sh status
# Look for "url" field - should be empty "" for polling
```

#### Step 4: Clear webhook and pending updates

```bash theme={null}
./scripts/check-telegram-bot.sh delete-webhook
# This drops pending updates and allows polling
```

#### Step 5: Check remote deployments

**If you deployed to a remote server**, check there:

```bash theme={null}
# SSH to your server
ssh user@your-server

# Check for running processes
ps aux | grep echos

# Check Docker containers
docker ps

# Stop if found
docker compose down
# Or kill the process
pkill -f "tsx.*index.ts"
```

#### 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:

```bash theme={null}
# Stop local processes
pkill -f "tsx.*index.ts"

# Stop Docker
docker compose down

# Clear webhook
./scripts/check-telegram-bot.sh delete-webhook

# Wait 60 seconds
sleep 60

# Restart
pnpm start
```

**For Production**: Use webhooks instead of long-polling to avoid conflicts:

```bash theme={null}
# Set webhook URL (HTTPS required)
curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/setWebhook" \
  -d "url=https://your-domain.com/telegram/webhook" \
  -d "secret_token=your_random_secret_here"

# Verify webhook
curl "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getWebhookInfo"
```

Configure in `.env`:

```bash theme={null}
TELEGRAM_WEBHOOK_URL=https://your-domain.com/telegram/webhook
TELEGRAM_WEBHOOK_SECRET=your_random_secret_here
```

**Note**: Implementing webhook support requires code changes in `packages/telegram/src/index.ts`.

### Redis Connection Errors

**Problem**: Cannot connect to Redis.

**Symptoms**:

```
Error: connect ECONNREFUSED 127.0.0.1:6379
```

**Solutions**:

1. **Use the Redis management script** (recommended):

   ```bash theme={null}
   # Check status
   pnpm redis:status

   # Start Redis (auto-detects platform)
   pnpm redis:start

   # Verify connection
   pnpm redis:health
   ```

2. **Manual platform-specific start** (if script fails):

   ```bash theme={null}
   # macOS
   brew services start redis

   # Linux (systemd)
   sudo systemctl start redis

   # Docker
   docker run -d -p 6379:6379 --name echos-redis redis:7-alpine
   ```

3. **Check Redis URL** in `.env`:

   ```bash theme={null}
   REDIS_URL=redis://localhost:6379
   ```

4. **Verify Redis is running**:

   ```bash theme={null}
   redis-cli ping
   # Should return: PONG
   ```

**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**:

```
Error: unable to open database file
Error: ENOENT: no such file or directory
```

**Solutions**:

1. **Create data directories**:

   ```bash theme={null}
   mkdir -p data/db data/knowledge data/sessions
   ```

2. **Check permissions**:

   ```bash theme={null}
   ls -la data/
   # Ensure directories are writable
   ```

3. **Check paths in `.env`**:

   ```bash theme={null}
   KNOWLEDGE_DIR=./data/knowledge
   DB_PATH=./data/db
   SESSION_DIR=./data/sessions
   ```

4. **Clean state (CAUTION: deletes all data)**:

   ```bash theme={null}
   rm -rf data/
   mkdir -p data/db data/knowledge data/sessions
   pnpm start
   ```

### `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:

```bash theme={null}
# Confirm the container's own process can see a recent heartbeat
docker compose exec echos node -e "const fs=require('fs');console.log(Date.now()-fs.statSync('/tmp/echos-heartbeat').mtimeMs, 'ms old')"

# If docker exec itself fails ("cannot exec in a stopped state") while docker inspect
# still reports Running=true, the container is likely wedged (e.g. after an OOM kill
# on a memory-constrained host) and Docker hasn't detected the exit. Force-recreate it:
docker compose up -d --force-recreate echos
```

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:

```bash theme={null}
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf
sudo sysctl --system
```

## Development Issues

### TypeScript Errors

**Problem**: Type errors when editing code.

**Solutions**:

1. **Check all packages**:

   ```bash theme={null}
   pnpm typecheck
   ```

2. **Rebuild after package changes**:

   ```bash theme={null}
   pnpm build
   ```

3. **Use watch mode during development**:

   ```bash theme={null}
   pnpm dev
   ```

### Vitest Test Failures

**Problem**: Tests failing or hanging.

**Solutions**:

1. **Run tests**:

   ```bash theme={null}
   pnpm test
   ```

2. **Run specific test file**:

   ```bash theme={null}
   pnpm vitest packages/shared/src/security/url-validator.test.ts
   ```

3. **Watch mode**:

   ```bash theme={null}
   pnpm test:watch
   ```

## Storage Sync Issues

### Manually added/edited markdown file not appearing in search

**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:

   ```yaml theme={null}
   ---
   id: some-unique-id
   type: note
   title: My Note
   created: 2026-02-17T12:00:00.000Z
   updated: 2026-02-17T12:00:00.000Z
   tags: []
   links: []
   category: uncategorized
   ---
   ```

2. **If you added the file while the app was stopped** — just restart. The startup reconciler will pick it up:

   ```bash theme={null}
   pnpm start
   ```

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:

   ```bash theme={null}
   pnpm start | pnpm exec pino-pretty
   # Look for "File watcher: upserted" or "Reconciler:" log lines
   ```

4. **Enable debug logging** to see all reconciler/watcher events:

   ```bash theme={null}
   LOG_LEVEL=debug pnpm start
   ```

### 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:

```bash theme={null}
LOG_LEVEL=info pnpm echos "hello"
```

### 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:

```bash theme={null}
# Wrong — zsh interprets `?` and `>`
pnpm echos what notes do I have about TypeScript?

# Correct
pnpm echos "what notes do I have about TypeScript?"
```

### 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](https://webshare.io) rotating proxy in `.env`:

```bash theme={null}
WEBSHARE_PROXY_USERNAME=your_username
WEBSHARE_PROXY_PASSWORD=your_password
```

<Warning>
  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.
</Warning>

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`:

```bash theme={null}
WHISPER_LANGUAGE=en
```

This accepts an [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) 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:

   ```bash theme={null}
   LOG_LEVEL=debug pnpm echos "save this tweet: https://x.com/..."
   ```

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):

```bash theme={null}
lsof -ti: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:

```bash theme={null}
openssl rand -hex 32   # copy this value
# Update MCP_API_KEY in .env and the Authorization header in your client config
# Restart the daemon
```

## Getting Help

If you're still stuck:

1. **Check the logs**: EchOS uses Pino for structured logging

   ```bash theme={null}
   pnpm start | pnpm exec pino-pretty
   ```

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](/security)

5. **Architecture overview**: See [Architecture](/architecture)
