Skip to content

Troubleshooting ​

Common issues and solutions for Nerve.


Authentication ​

Login page won't accept password ​

Symptom: Entering the correct password returns "Invalid password".

Causes:

  1. The password hash in .env doesn't match the password you're entering
  2. You're trying the gateway token but GATEWAY_TOKEN isn't set in .env

Fix:

  • Re-run npm run setup to set a new password
  • Or set NERVE_AUTH=true with a valid GATEWAY_TOKEN — the gateway token works as a fallback password without needing a password hash

Session expired / redirected to login ​

Symptom: You were logged in but got redirected to the login page.

Cause: The session cookie expired (default TTL: 30 days) or the NERVE_SESSION_SECRET changed (e.g., server restart without a persisted secret).

Fix:

  • Log in again with your password
  • If sessions don't survive restarts, ensure NERVE_SESSION_SECRET is set in .env (the setup wizard generates one). Without it, an ephemeral secret is created on each startup

API returns 401 but auth is disabled ​

Symptom: API calls fail with "Authentication required" even though NERVE_AUTH isn't set.

Cause: Check that NERVE_AUTH isn't set to true somewhere unexpected (e.g., exported in your shell profile).

Fix:

bash
# Check the env var
grep NERVE_AUTH .env
echo $NERVE_AUTH

# Explicitly disable
echo "NERVE_AUTH=false" >> .env

WebSocket connects but immediately closes with 401 ​

Symptom: Browser console shows WebSocket upgrade failed with 401.

Cause: When auth is enabled, WebSocket upgrade requests are also authenticated via the session cookie. If the cookie is missing or expired, the upgrade is rejected.

Fix: Refresh the page and log in again. The session cookie is sent automatically with WebSocket upgrade requests.


Build Errors ​

tsc -b fails with path alias errors ​

Symptom: Cannot find module '@/...' during TypeScript compilation.

Cause: The @/ alias must be configured in both tsconfig.json (for editor) and the relevant project reference config.

Fix: Ensure the root tsconfig.json has:

json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": { "@/*": ["./src/*"] }
  }
}

npm run build:server produces nothing ​

Symptom: server-dist/ is empty or npm start fails with "Cannot find module".

Cause: Server TypeScript is compiled separately via tsc -p config/tsconfig.server.json.

Fix:

bash
npm run build:server   # Compiles server/ → server-dist/
npm start              # Then runs node server-dist/index.js

Chunk size warnings during vite build ​

Symptom: Vite warns about chunks exceeding 500 kB.

Cause: Heavy dependencies (highlight.js, react-markdown) are bundled.

Info: This is expected. The build uses manual chunks to split: react-vendor, markdown, ui-vendor, utils. The warning limit is set to 600 kB in vite.config.ts. If a chunk exceeds this, check for accidental imports pulling in large libraries.

Port already in use ​

Symptom: Port 3080 is already in use. Is another instance running?

Fix:

bash
# Find what's using the port
lsof -i :3080
# Kill it, or use a different port:
PORT=3090 npm start

The server detects EADDRINUSE and exits with a clear error (see server/index.ts).


Gateway Connection ​

"Auth failed" in ConnectDialog ​

Symptom: Connection dialog shows "Auth failed: unknown" or similar.

Causes:

  1. Wrong gateway token
  2. Gateway not running
  3. Token mismatch between Nerve server config and gateway

Fix:

  • Verify the gateway is running: openclaw gateway status
  • Check token: the server reads GATEWAY_TOKEN or OPENCLAW_GATEWAY_TOKEN env var
  • For trusted official-gateway access, /api/connect-defaults advertises serverSideAuth=true and Nerve connects with an empty browser-side token
  • For custom gateway URLs or untrusted access, enter the token manually in the connection dialog

Connection drops and "SIGNAL LOST" banner ​

Symptom: Red reconnecting banner appears periodically.

Cause: WebSocket connection to gateway dropped. Nerve auto-reconnects with exponential backoff (1s base, 30s max, up to 50 attempts).

Diagnosis:

bash
# Check gateway health
curl http://127.0.0.1:18789/health

# Check Nerve health (includes gateway probe)
curl http://127.0.0.1:3080/health
# Returns: { "status": "ok", "uptime": ..., "gateway": "ok"|"unreachable" }

Fix:

  • If gateway is unreachable, restart it: openclaw gateway restart
  • If persistent, check firewall rules or network configuration
  • If a stale manual token is saved in localStorage, clear oc-config and reload before reconnecting

Auto-connect doesn't work ​

Symptom: ConnectDialog appears even though the gateway is running.

Cause: The frontend fetches /api/connect-defaults on mount, but auto-connect only happens when:

  • serverSideAuth=true
  • the saved gateway URL is empty or matches the server's official gateway URL
  • the initial connect attempt succeeds

Fix:

  • If you want the managed path, clear stale browser config (localStorage.oc-config) and reload
  • If you are using a custom gateway URL, manual token entry is expected
  • If you are remote but authenticated and using the official gateway URL, the dialog should not be required after stale config is cleared

WebSocket Proxy ​

WebSocket connects but no events arrive ​

Symptom: UI shows "connected" but sessions/messages don't update.

Cause: The WS proxy (server/lib/ws-proxy.ts) might not be injecting device identity correctly, so the gateway doesn't grant operator.read/operator.write scopes.

Diagnosis:

  • Check server logs for [ws-proxy] Injected device identity: ...
  • If missing, the device identity file may be corrupted

Fix:

bash
# Remove and regenerate device identity
rm ~/.nerve/device-identity.json
# Restart Nerve — a new keypair will be generated

"Target not allowed" WebSocket error ​

Symptom: Browser console shows WebSocket close code 1008 with "Target not allowed".

Cause: The gateway URL hostname is not in the WS_ALLOWED_HOSTS allowlist (configured in server/lib/config.ts).

Fix: By default, only 127.0.0.1, localhost, and ::1 are allowed. To add a custom host:

bash
WS_ALLOWED_HOSTS=mygateway.local npm start

"device token mismatch" on WebSocket connect ​

Symptom: Server logs show [ws-proxy] Gateway closed: code=1008, reason=unauthorized: device token mismatch.

Causes:

  1. Stale browser config. The browser may still have an old manually-entered token saved in localStorage (oc-config), often from an older build or a custom gateway connection.
  2. Token mismatch across config files. OpenClaw 2026.2.19 has a known bug where openclaw onboard writes different tokens to the systemd service file and openclaw.json. The gateway uses the systemd env var; Nerve reads from .env.

Fix (stale browser config): Clear site data or remove localStorage.oc-config, then reload so the official managed gateway path can reconnect with an empty token.

Fix (token mismatch): Re-run the setup wizard — it reads the real token from the systemd service file and aligns everything:

bash
npm run setup

If you need to check manually:

bash
# The gateway's actual token (source of truth)
grep OPENCLAW_GATEWAY_TOKEN ~/.config/systemd/user/openclaw-gateway.service

# These must all match:
grep gateway.auth.token ~/.openclaw/openclaw.json     # CLI config
grep GATEWAY_TOKEN .env                                 # Nerve config

"Missing scope" errors after connecting ​

Symptom: Chat sends but responses fail with "missing scope" or tool calls are rejected.

Cause: The gateway didn't grant operator.read/operator.write scopes. This happens when:

  1. The device hasn't been approved yet (first connection)
  2. The device was rejected or the gateway was reset

Fix: Re-run npm run setup — it bootstraps device scopes automatically. If that doesn't work:

bash
# Check pending devices
openclaw devices list

# Approve the Nerve device
openclaw devices approve <requestId>

After approval, reconnect from the browser (refresh the page or click reconnect).

Messages buffered indefinitely ​

Symptom: Messages sent immediately after connecting are lost.

Info: The proxy buffers up to 100 messages (1 MB) while the upstream gateway connection opens. If the buffer overflows, the client is disconnected with "Too many pending messages". This is a safety limit — reduce message burst rate.


TTS Issues ​

No audio plays ​

Symptom: TTS is enabled but no sound on responses.

Diagnosis tree:

  1. Sound enabled? Check Settings → Audio → Sound toggle is on
  2. TTS provider configured? Check Settings → Audio → TTS Provider
  3. API key present?
    • OpenAI: requires OPENAI_API_KEY env var
    • Replicate: requires REPLICATE_API_TOKEN env var
    • Edge: no key needed (free)
  4. Server-side check:
    bash
    curl -X POST http://127.0.0.1:3080/api/tts \
      -H "Content-Type: application/json" \
      -d '{"text": "hello", "provider": "edge"}'
    Should return audio/mpeg binary.

Provider auto-fallback: If no explicit provider is selected, the server tries: OpenAI (if key) → Replicate (if key) → Edge (always available).

TTS plays old/wrong responses ​

Symptom: Audio doesn't match the displayed message.

Cause: TTS cache serving stale entries. The cache is an LRU with TTL expiry (configurable via config.ttsCacheTtlMs), 100 MB memory budget.

Fix: Restart the Nerve server to clear the in-memory TTS cache.

Edge TTS fails silently ​

Symptom: Edge TTS selected but no audio. No error in UI.

Cause: Edge TTS uses Microsoft's speech service WebSocket. The Sec-MS-GEC token generation or the WebSocket connection may fail.

Diagnosis: Check server logs for [edge-tts] errors.

Fix: Edge TTS has no API key dependency, but requires outbound WebSocket access to speech.platform.bing.com. Ensure your network allows this.


Voice Input / Wake Word ​

Microphone not working ​

Symptom: Voice input button does nothing or permission denied.

Cause: Microphone requires a secure context (HTTPS or localhost).

Fix:

  • If accessing via http://127.0.0.1:3080 — should work (localhost is secure)
  • If accessing remotely, use HTTPS:
    bash
    # Generate self-signed cert
    mkdir -p certs
    openssl req -x509 -newkey rsa:2048 -nodes \
      -keyout certs/key.pem -out certs/cert.pem -days 365 \
      -subj "/CN=localhost"
    # Nerve auto-detects certs and starts HTTPS on port 3443

Whisper transcription fails ​

Symptom: Voice input records but transcription returns error.

Causes:

  • Local STT (default): The whisper model hasn't been downloaded yet, or ffmpeg is missing
  • OpenAI STT: OPENAI_API_KEY not set

Fix (local STT):

  • Models auto-download on first use. Check server logs for download progress or errors
  • Ensure ffmpeg is installed (the installer handles this): ffmpeg -version
  • Check model file exists: ls ~/.nerve/models/ggml-tiny.bin

Fix (OpenAI STT):

  • Set STT_PROVIDER=openai and OPENAI_API_KEY in .env

Both providers:

  • Max file size: 12 MB
  • Accepted formats: audio/webm, audio/mp3, audio/mpeg, audio/mp4, audio/m4a, audio/wav, audio/ogg, audio/flac

Non-English transcription is inaccurate ​

Symptom: STT works, but non-English speech is mis-transcribed or stop/cancel commands are unreliable.

Causes:

  • Language is set incorrectly
  • Local model is tiny (fast, but less accurate for conversational non-English)
  • English-only model (*.en) selected for non-English speech

Fix:

  1. Set language explicitly in Settings → Audio → Language (no auto-detect mode)
  2. For local STT, switch model from tiny to base
  3. Ensure WHISPER_MODEL is multilingual (tiny, base, small) for non-English usage
  4. Persist language in .env as NERVE_LANGUAGE=<code> (legacy LANGUAGE is still read, but deprecated)

Voice phrase changes don't persist ​

Symptom: Custom stop/cancel/wake phrases disappear after refresh or restart.

Cause: Phrase overrides are stored on disk at ~/.nerve/voice-phrases.json (or NERVE_VOICE_PHRASES_PATH if set). Write failures usually come from path/permission issues.

Fix:

  • Verify file location and permissions:
    bash
    ls -l ~/.nerve/voice-phrases.json
  • If using a custom path, ensure NERVE_VOICE_PHRASES_PATH points to a writable location
  • Re-save phrases via Settings and watch server logs for /api/voice-phrases errors

Wake word doesn't trigger ​

Symptom: Wake word toggle is on but voice detection never activates.

Cause: Wake word state is managed collaboratively — the InputBar component reports wake word state to SettingsContext via handleWakeWordState(enabled, toggleFn).

Fix:

  • Ensure microphone permissions are granted
  • Try toggling wake word off and on via Settings or Cmd+K command palette
  • Check browser console for speech recognition errors

Memory Editing ​

Memory changes don't appear ​

Symptom: Added/deleted memories don't reflect in the UI.

Cause: Memory operations go through the gateway tool invocation (memory_store/memory_delete), then the file watcher detects changes and broadcasts an SSE event.

Diagnosis:

  1. Check POST/DELETE to /api/memories returns { ok: true }
  2. Check server logs for [file-watcher] events
  3. Check SSE stream: curl -N http://127.0.0.1:3080/api/events

Fix:

  • If the gateway tool call fails, check gateway connectivity
  • If file watcher isn't firing, the memory file path may be wrong — check config.memoryPath
  • Manual refresh: click the refresh button in the Memory tab, or use Cmd+K → "Refresh Memory"

Memory file path is wrong ​

Symptom: Memories show as empty even though MEMORY.md exists.

Cause: The server resolves memory path from config (config.memoryPath). The workspace path is the parent of the memory path.

Fix: Check and set the correct path:

bash
# In .env
MEMORY_PATH=/path/to/.openclaw/workspace/MEMORY.md

Session Management ​

Sessions don't appear in sidebar ​

Symptom: Session list is empty or shows only the main session.

Cause: Sessions are fetched via gateway RPC sessions.list with activeMinutes: 120 filter.

Fix:

  • Sessions inactive for >2 hours won't appear — this is by design
  • Check gateway connectivity (sessions come from the gateway, not local state)
  • Force refresh: click refresh button or Cmd+K → "Refresh Sessions"

Sub-agent spawn times out ​

Symptom: "Timed out waiting for subagent to spawn" error.

Cause: Spawning uses a polling approach — sends a [spawn-subagent] chat message to the main session, then polls sessions.list every 2s for up to 30s waiting for a new subagent session to appear.

Fix:

  • The main agent must be running and able to process the spawn request
  • Check that the main session isn't busy with another task
  • Check gateway logs for spawn errors

Session status stuck on "THINKING" ​

Symptom: Session shows thinking/spinning indefinitely.

Cause: The agent state machine transitions THINKING → STREAMING → DONE → IDLE. If a lifecycle event was missed, the status can get stuck.

Fix:

  • Use the abort button (or Ctrl+C when generating) to reset the state
  • The DONE → IDLE auto-transition happens after 3 seconds (see doneTimeoutsRef in SessionContext)
  • Force refresh sessions to re-sync from gateway

Model Switching ​

Model dropdown doesn't show available models ​

Symptom: Model selector is empty or shows only the current model.

Cause: Models are fetched via GET /api/gateway/models, which runs openclaw models list --json.

Fix:

  • Ensure the openclaw binary is in PATH (the server searches multiple locations — see lib/openclaw-bin.ts)
  • Set OPENCLAW_BIN env var to the explicit path
  • Check server logs for model list errors
  • An allowlist can restrict visible models (configured server-side)

Model change doesn't take effect ​

Symptom: Switched model in UI but responses still come from the old model.

Cause: Model/thinking changes go through POST /api/gateway/session-patch, which invokes the gateway's session patch API.

Fix:

  • The change applies per-session — switching sessions will show that session's model
  • Verify the patch succeeded: check for { ok: true } response
  • Some models may not be available for the current session type

Rate Limiting ​

"Too many requests" errors ​

Symptom: API returns 429 status.

Cause: Per-IP sliding window rate limiter. Different limits for:

  • General API endpoints
  • TTS synthesis (more restrictive)
  • Transcription (more restrictive)

Fix:

  • Wait for the rate limit window to reset (check X-RateLimit-Reset header)
  • If behind a reverse proxy, ensure X-Forwarded-For is set correctly (the server only trusts forwarded headers from trusted proxy IPs)

HTTPS / SSL ​

Certificate errors ​

Symptom: Browser shows SSL warnings or refuses to connect on port 3443.

Fix: For development, generate a self-signed cert:

bash
mkdir -p certs
openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout certs/key.pem -out certs/cert.pem -days 365 \
  -subj "/CN=localhost"

The server auto-detects cert files at certs/cert.pem and certs/key.pem. No configuration needed — if the files exist, HTTPS starts on config.sslPort (default 3443).

SSE not working over HTTPS ​

Symptom: Real-time updates (memory changes, token updates) don't arrive over HTTPS.

Cause: The HTTPS server has special handling for SSE responses — it streams them instead of buffering (see server/index.ts SSE streaming fix).

Fix: This should work automatically. If it doesn't, check that:

  • The response content-type includes text/event-stream
  • No intermediate reverse proxy is buffering the response
  • The compression middleware correctly skips /api/events

Development ​

npm run dev — proxy errors ​

Symptom: API requests fail with 502 during development.

Cause: Vite proxies /api and /ws to the backend server. If the backend isn't running, all proxied requests fail.

Fix: Run both servers:

bash
# Terminal 1
npm run dev:server   # Backend on port 3081

# Terminal 2
npm run dev          # Frontend on port 3080 (proxies to 3081)

Tests fail with "Cannot find module" ​

Symptom: Vitest can't resolve @/ imports.

Fix: The test config (vitest.config.ts) must have the same path alias:

ts
resolve: {
  alias: {
    '@': path.resolve(__dirname, './src'),
  },
},

Also ensure server-dist/ is excluded from test discovery (it contains compiled .test.js duplicates):

ts
test: {
  exclude: ['node_modules/**', 'server-dist/**'],
}

Update & Rollback ​

See docs/UPDATING.md for the full updater reference.

"No semver tags found on remote" ​

Symptom: npm run update fails at version resolution (exit 20).

Cause: No tags on the remote, or git can't reach origin.

Fix:

bash
git remote -v              # Verify origin
git fetch --tags origin    # Pull tags
git tag -l                 # Confirm tags exist locally

Update stuck or "Lock acquisition failure" (exit 80) ​

Symptom: Update refuses to start, says another update is running.

Cause: A previous update crashed without releasing the lock.

Fix:

bash
cat ~/.nerve/updater/nerve-update.lock   # Shows the PID
ps -p <pid>                               # Check if alive
rm ~/.nerve/updater/nerve-update.lock     # Remove if stale

Health check version mismatch (exit 60) ​

Symptom: Update completes build and restart, but health check says wrong version.

Cause: The old server process didn't release the port, or systemd hasn't fully restarted.

Fix:

bash
systemctl restart nerve
curl http://127.0.0.1:3080/api/version   # Verify version

Rollback failed (exit 70) ​

Symptom: Both update and automatic rollback failed. Critical state.

Fix: Manual recovery:

bash
cat ~/.nerve/updater/last-good.json       # Get snapshot ref
git checkout --force <ref>
npm install && npm run build && npm run build:server
systemctl restart nerve

Known Limitations ​

None currently tracked.

Released under the MIT License.