Fix 9Router Docker /dashboard/cli-tools detection when tools installed on the host show as not installed inside the dashboard.
This repo handles two Docker migration issues:
-
/dashboard/providersdoes not show old npm/host setup. Fix: start Docker with the 9Router data directory mounted:-v "$HOME/.9router:/app/data" -e DATA_DIR=/app/data -
/dashboard/cli-toolsdoes not detect host CLI tools or connected setup. Fix: start Docker with host home mounted, then run the sync script to create/home/nodesymlinks to host config folders:-v "$HOME:/home/user" bash scripts/sync-9router-cli-symlinks.sh 9router
9Router works differently depending on how it is started:
npm install -g 9router && 9routerruns on the host machine.docker run decolua/9routerruns inside a container.
When 9Router runs from npm, it can see host CLI binaries and host config files like:
/home/user/.codex/config.toml
/home/user/.claude/settings.json
/home/user/.config/opencode/opencode.json
/home/user/.local/share/kilo/auth.json
When 9Router runs in Docker, its app home is usually:
/home/node
So the dashboard checks paths like:
/home/node/.codex/config.toml
/home/node/.claude/settings.json
/home/node/.config/opencode/opencode.json
/home/node/.local/share/kilo/auth.json
Those paths do not exist unless you mount or link host config into the container. Result: /dashboard/cli-tools shows not installed, even though the CLI tools work on the host.
9Router CLI detection is path-based. It does not scan the host machine from inside Docker.
From the 9Router source, /api/cli-tools/all-statuses checks these tools:
claude, codex, opencode, droid, openclaw, hermes, cowork, copilot, cline, kilo, deepseek-tui, jcode
Each tool route usually checks which <tool> first, then falls back to a config file under os.homedir(). In Docker, os.homedir() resolves to /home/node, not /home/user.
Important paths:
Claude /home/node/.claude/settings.json
Codex /home/node/.codex/config.toml
OpenCode /home/node/.config/opencode/opencode.json
Kilo /home/node/.local/share/kilo/auth.json
Cline /home/node/.cline/data/globalState.json
Droid /home/node/.factory/settings.json
OpenClaw /home/node/.openclaw/openclaw.json
Hermes /home/node/.hermes/config.yaml
DeepSeek TUI /home/node/.deepseek/config.toml
JCode /home/node/.jcode/config.toml
JCode env /home/node/.config/jcode/provider-9router.env
Copilot /home/node/.config/Code/User/chatLanguageModels.json
Cowork /home/node/.config/Claude-3p or /home/node/.config/Claude
We mounted the real host home into the 9Router container:
-v "$HOME:/home/user"Then we created symlinks from /home/node paths to the real /home/user config folders.
Example:
/home/node/.codex -> /home/user/.codex
/home/node/.claude -> /home/user/.claude
/home/node/.config/opencode -> /home/user/.config/opencode
/home/node/.local/share/kilo -> /home/user/.local/share/kilo
This makes the dashboard real, not fake. When 9Router writes:
/home/node/.codex/config.toml
it actually changes:
/home/user/.codex/config.toml
Prerequisites:
- Check Docker works first:
docker --version
docker ps- If
docker psfails with a permission error, usesudo dockerfor the Docker commands below.
Clone this repo and enter it:
git clone https://github.com/afu-it/9router-docker-cli-sync.git
cd 9router-docker-cli-syncStart 9Router with host home mounted. This mount is required; without it, the symlinks point to missing files:
sudo docker stop 9router 2>/dev/null || true
sudo docker rm 9router 2>/dev/null || true
sudo docker run -d \
--name 9router \
-p 20128:20128 \
-v "$HOME:/home/user" \
-v "$HOME/.9router:/app/data" \
-e DATA_DIR=/app/data \
decolua/9router:latestConfirm the required mount exists:
sudo docker inspect 9router --format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}'Expected output must include:
/home/your-user -> /home/user
Run sync. Use sudo bash if Docker requires sudo on your system:
bash scripts/sync-9router-cli-symlinks.sh 9routerOr:
sudo bash scripts/sync-9router-cli-symlinks.sh 9routerVerify symlinks:
sudo docker exec 9router sh -lc 'ls -la /home/node/.codex /home/node/.claude /home/node/.config/opencode /home/node/.local/share/kilo'Expected output should show symlinks like:
/home/node/.codex -> /home/user/.codex
/home/node/.claude -> /home/user/.claude
/home/node/.config/opencode -> /home/user/.config/opencode
/home/node/.local/share/kilo -> /home/user/.local/share/kilo
Verify dashboard API:
curl http://127.0.0.1:20128/api/cli-tools/all-statusesOpen dashboard:
http://127.0.0.1:20128/dashboard/cli-tools
If the dashboard still shows old status, hard refresh the browser or restart the container:
sudo docker restart 9routerTools only show as installed when their expected host config exists or the binary exists inside the container. For example, jcode, openclaw, and deepseek-tui will still show not installed if they are not installed on the host.
Give the agent this repo and this prompt:
Use the 9Router Docker CLI Sync skill from:
https://github.com/afu-it/9router-docker-cli-sync
Problem: 9Router runs in Docker, and /dashboard/cli-tools shows CLI tools as not installed even though they are installed on the host. Fix it by making the Docker container read and write the real host CLI config files, not copied container-only files.
Requirements:
1. Confirm the 9Router container name, default is 9router.
2. Confirm the container was started with host home mounted as /home/user.
3. If not mounted, tell me the exact docker run command to recreate it safely.
4. Run scripts/sync-9router-cli-symlinks.sh against the container.
5. Verify symlinks under /home/node point to /home/user.
6. Verify http://127.0.0.1:20128/api/cli-tools/all-statuses shows installed/has9Router for available host tools.
7. Do not copy configs into the container. Use real symlinks only.
If the agent supports skills directly, use:
Use $9router-docker-cli-sync to make 9Router Docker detect and edit host CLI tool configs.
Mounting $HOME:/home/user exposes the full host home to the 9Router container. This is intentional for the full symlink approach. If you prefer least privilege, mount only the needed config folders instead of the whole home directory.