| name | debug |
| description | Debug container agent issues. Use when things aren't working, container fails, authentication problems, or to understand how the container system works. Covers logs, environment variables, mounts, and common issues. |
OmniClaw Container Debugging
This guide covers debugging the containerized agent execution system.
Architecture Overview
Host (macOS / Linux) Container (Docker / OrbStack)
─────────────────────────────────────────────────────────────────
src/backends/local-backend.ts container/agent-runner/
│ │
│ spawns container via backend │ runs Claude Agent SDK
│ with volume mounts │ with MCP servers
│ │
├── data/env/env ──────────────> /workspace/env-dir/env
├── groups/{folder} ───────────> /workspace/group
├── data/ipc/{folder} ────────> /workspace/ipc
├── data/sessions/{folder}/.claude/ ──> /home/node/.claude/ (isolated per-group)
└── (main only) project root ──> /workspace/project
Important: The container runs as user node with HOME=/home/node. Session files must be mounted to /home/node/.claude/ (not /root/.claude/) for session resumption to work.
Log Locations
| Log | Location | Content |
|---|
| Main app logs | logs/omniclaw.log | Host-side messaging, routing, container spawning |
| Main app errors | logs/omniclaw.error.log | Host-side errors |
| Container run logs | groups/{folder}/logs/container-*.log | Per-run: input, mounts, stderr, stdout |
| Claude sessions | ~/.claude/projects/ | Claude Code session history |
Enabling Debug Logging
Set LOG_LEVEL=debug for verbose output:
LOG_LEVEL=debug bun run dev
systemctl --user edit omniclaw
Debug level shows:
- Full mount configurations
- Container command arguments
- Real-time container stderr
Common Issues
1. "Claude Code process exited with code 1"
Check the container log file in groups/{folder}/logs/container-*.log
Common causes:
Missing Authentication
Invalid API key · Please run /login
Fix: Ensure .env file exists with either OAuth token or API key:
Root User Restriction
--dangerously-skip-permissions cannot be used with root/sudo privileges
Fix: Container must run as non-root user. Check Dockerfile has USER node.
2. Environment Variables Not Passing
The system extracts only authentication variables (CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY) from .env and mounts them for sourcing inside the container.
To verify env vars are reaching the container:
docker run --rm \
-v $(pwd)/data/env:/workspace/env-dir:ro \
--entrypoint /bin/bash omniclaw-agent:latest \
-c 'export $(cat /workspace/env-dir/env | xargs); echo "OAuth: ${#CLAUDE_CODE_OAUTH_TOKEN} chars, API: ${#ANTHROPIC_API_KEY} chars"'
3. Mount Issues
To check what's mounted inside a container:
docker run --rm --entrypoint /bin/bash omniclaw-agent:latest -c 'ls -la /workspace/'
Expected structure:
/workspace/
├── env-dir/env # Environment file (CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY)
├── group/ # Current group folder (cwd)
├── project/ # Project root (main channel only)
├── global/ # Global CLAUDE.md (non-main only)
├── ipc/ # Inter-process communication
│ ├── messages/ # Outgoing messages
│ ├── tasks/ # Scheduled task commands
│ ├── current_tasks.json # Read-only: scheduled tasks visible to this group
│ └── available_groups.json # Read-only: groups for activation (main only)
└── extra/ # Additional custom mounts
4. Permission Issues
The container runs as user node (uid 1000). All of /workspace/ and /app/ should be owned by node.
5. Session Not Resuming / "Claude Code process exited with code 1"
Root cause: The SDK looks for sessions at $HOME/.claude/projects/. Inside the container, HOME=/home/node, so it looks at /home/node/.claude/projects/.
Fix: Ensure local-backend.ts mounts to /home/node/.claude/:
mounts.push({
hostPath: claudeDir,
containerPath: '/home/node/.claude',
readonly: false,
});
6. Service Stops After SSH Disconnect (Linux)
Systemd user services are killed when the last login session ends unless lingering is enabled.
Diagnose:
loginctl show-user $(whoami) | grep Linger
Fix:
loginctl enable-linger $(whoami)
This persists across reboots. The setup script (step 10) enables this automatically. If it fails with a permissions error, sudo or a polkit rule may be needed.
7. MCP Server Failures
If an MCP server fails to start, the agent may exit. Check the container logs for MCP initialization errors.
8. OpenCode Session Looks Healthy But Replies Are Empty
Symptom pattern in logs/omniclaw.log:
[opencode-runtime] Created new session: ...
[opencode-runtime] Injected system context
[opencode-runtime] Sending prompt ...
[opencode-runtime] extractResponseText: 0 parts, types:
[opencode-runtime] waitForAssistantText: 3 messages, last role: assistant, last parts: 0, types:
Important: if this happens on a brand new session, the root cause is not stale session resume alone.
Known real-world case:
- Remote Linux OpenCode agent worked normally
- Local macOS OpenCode agent created fresh sessions but still returned empty assistant parts
- The local base OpenCode auth store contained mixed providers (
anthropic + openai)
- The working remote auth store contained only
openai
What to check:
sqlite3 store/messages.db "SELECT group_folder, session_id, created_at FROM sessions WHERE group_folder LIKE 'ocpeyton-discord__dispatch__%';"
python3 - <<'PY'
from pathlib import Path
import json
p = Path('data/opencode-data/ocpeyton-discord')
for name in ['auth.json','mcp-auth.json','opencode.db']:
fp = p / name
print(name, fp.exists())
if fp.exists() and name.endswith('.json'):
print(sorted(json.loads(fp.read_text()).keys()))
PY
Recovery order:
launchctl bootout gui/$(id -u)/com.omniclaw
systemctl --user stop omniclaw
sqlite3 store/messages.db "DELETE FROM sessions WHERE group_folder LIKE 'ocpeyton-discord__dispatch__%';"
find data/opencode-data -maxdepth 1 -type d -name 'ocpeyton-discord__dispatch__*' -print -exec rm -rf {} +
rm -f data/opencode-data/ocpeyton-discord/auth.json \
data/opencode-data/ocpeyton-discord/mcp-auth.json \
data/opencode-data/ocpeyton-discord/opencode.db \
data/opencode-data/ocpeyton-discord/opencode.db-shm \
data/opencode-data/ocpeyton-discord/opencode.db-wal
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.omniclaw.plist
systemctl --user start omniclaw
Notes:
- If the remote agent works and the local one does not, compare
auth.json provider keys first.
- Do not assume session corruption if the failure reproduces on a fresh session.
- OpenCode reasoning should not be sent to users; only user-facing text parts should be forwarded.
Service Management
launchctl list | grep omniclaw
systemctl --user status omniclaw
launchctl kickstart -k gui/$(id -u)/com.omniclaw
systemctl --user restart omniclaw
launchctl bootout gui/$(id -u)/com.omniclaw
systemctl --user stop omniclaw
tail -f logs/omniclaw.log
docker ps --filter name=omniclaw
bun run build
Manual Container Testing
Test the full agent flow:
mkdir -p data/env groups/test
echo '{"prompt":"What is 2+2?","groupFolder":"test","chatJid":"test@g.us","isMain":false}' | \
docker run -i --rm \
-v $(pwd)/data/env:/workspace/env-dir:ro \
-v $(pwd)/groups/test:/workspace/group \
-v $(pwd)/data/ipc:/workspace/ipc \
omniclaw-agent:latest
Interactive shell in container:
docker run --rm -it --entrypoint /bin/bash omniclaw-agent:latest
SDK Options Reference
The agent-runner uses these Claude Agent SDK options:
query({
prompt: input.prompt,
options: {
cwd: '/workspace/group',
allowedTools: ['Bash', 'Read', 'Write', ...],
permissionMode: 'bypassPermissions',
allowDangerouslySkipPermissions: true,
settingSources: ['project'],
mcpServers: { ... }
}
})
Important: allowDangerouslySkipPermissions: true is required when using permissionMode: 'bypassPermissions'. Without it, Claude Code exits with code 1.
Rebuilding After Changes
bun run build
./container/build.sh
docker builder prune -f && ./container/build.sh
Session Persistence
Claude sessions are stored per-group in data/sessions/{group}/.claude/ for security isolation.
Critical: The mount path must match the container user's HOME directory:
- Container user:
node
- Container HOME:
/home/node
- Mount target:
/home/node/.claude/ (NOT /root/.claude/)
To clear sessions:
rm -rf data/sessions/
rm -rf data/sessions/{groupFolder}/.claude/
sqlite3 store/messages.db "DELETE FROM sessions WHERE group_folder = '{groupFolder}'"
Discord Multi-Bot Routing
Internal bot key vs Discord snowflake ID
OmniClaw uses two completely different identifiers for Discord bots — they must not be confused:
| Identifier | Where it lives | Example | Used for |
|---|
| Internal bot key | DISCORD_BOT_IDS in .env | PRIMARY, OCPEYTON | OmniClaw routing |
| Discord snowflake ID | Discord Developer Portal → App → General | 1476396931709276191 | Discord's own API |
The internal bot key must match exactly across:
DISCORD_BOT_IDS=PRIMARY,OCPEYTON
DISCORD_BOT_<KEY>_TOKEN=<token> (e.g., DISCORD_BOT_OCPEYTON_TOKEN)
channel_subscriptions.discord_bot_id in SQLite
Common mistake: Using the numeric Discord snowflake ID in channel_subscriptions.discord_bot_id instead of the human-readable key. This breaks routing silently.
Diagnosing wrong-bot-sending issues
grep DISCORD_BOT_IDS .env
sqlite3 store/messages.db "SELECT DISTINCT discord_bot_id FROM channel_subscriptions WHERE discord_bot_id IS NOT NULL"
sqlite3 store/messages.db "UPDATE channel_subscriptions SET discord_bot_id = 'OCPEYTON' WHERE discord_bot_id = '1476396931709276191'"
IPC Debugging
ls -la data/ipc/messages/
ls -la data/ipc/tasks/
cat data/ipc/main/available_groups.json
cat data/ipc/{groupFolder}/current_tasks.json
Quick Diagnostic Script
echo "=== OmniClaw Diagnostic ==="
echo -e "\n1. Service running?"
if command -v systemctl &>/dev/null; then
systemctl --user is-active omniclaw 2>/dev/null && echo "OK (systemd)" || echo "NOT RUNNING"
echo -n " Linger: "; loginctl show-user $(whoami) 2>/dev/null | grep Linger || echo "unknown"
elif command -v launchctl &>/dev/null; then
launchctl list 2>/dev/null | grep -q com.omniclaw && echo "OK (launchd)" || echo "NOT RUNNING"
fi
echo -e "\n2. Authentication configured?"
[ -f .env ] && (grep -q "CLAUDE_CODE_OAUTH_TOKEN=sk-" .env || grep -q "ANTHROPIC_API_KEY=sk-" .env) && echo "OK" || echo "MISSING"
echo -e "\n3. Container runtime?"
if command -v docker &>/dev/null; then
docker info &>/dev/null && echo "OK (Docker)" || echo "Docker installed but not running — open OrbStack/Docker Desktop on macOS, sudo systemctl start docker on Linux"
else
echo "No docker runtime found. On macOS: brew install --cask orbstack, then open OrbStack.app once."
fi
echo -e "\n4. Container image?"
docker images omniclaw-agent:latest --format "OK ({{.Size}})" 2>/dev/null || \
echo "MISSING — run ./container/build.sh"
echo -e "\n5. Recent errors?"
grep -E '"level":"error"' logs/omniclaw.log 2>/dev/null | tail -3 || echo "No error log"
echo -e "\n6. Groups loaded?"
grep 'groupCount' logs/omniclaw.log 2>/dev/null | tail -1 || echo "No log data"