/devcontainer-dev
Spin up and interact with ToolHive Studio's containerized dev environment (Xvfb + noVNC + DinD). Use when running, testing, or debugging the app in isolation — locally, in a git worktree, or in GitHub Codespaces; when touching `.devcontainer/*`, `scripts/devcontainer-*.sh`, or
$ npx -y skills add stacklok/toolhive-studio --skill devcontainer-dev --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/devcontainer-dev
Context preview
The summary Claude sees to decide when to auto-load this skill.
Spin up and interact with ToolHive Studio's containerized dev environment (Xvfb + noVNC + DinD). Use when running, testing, or debugging the app in isolation — locally, in a git worktree, or in GitHub Codespaces; when touching `.devcontainer/*`, `scripts/devcontainer-*.sh`, or
SKILL.md
devcontainer-dev.SKILL.mdname: devcontainer-dev
description: Spin up and interact with ToolHive Studio's containerized dev environment (Xvfb + noVNC + DinD). Use when running, testing, or debugging the app in isolation — locally, in a git worktree, or in GitHub Codespaces; when touching `.devcontainer/*`, `scripts/devcontainer-*.sh`, or the `devContainer:dev` npm script; or when debugging "blank white window", "Docker daemon failed to start", or "Missing X server" errors in the devcontainer. The container is fully isolated: no host pnpm install, no host Docker socket, no host X11/GPU — experiment freely without contaminating the host.
allowed-tools: Read, Grep, Glob, Bash
Containerized Dev Environment
An isolated, cross-platform test environment for ToolHive Studio. The whole Electron app — including its backend `thv` binary and the MCP-server containers it spawns — runs inside a single devcontainer. You interact with the UI via a noVNC browser tab.
The entire stack (Node, Electron, display server, window manager, VNC server, Docker-in-Docker, DBus, keyring) lives in the container. **Nothing is installed on the host.** That's the whole point: every worktree can have its own container and its own experiments, with zero risk of contaminating the user's global installs.
---
Entry point
pnpm devContainer:dev
runs `scripts/devcontainer-dev.sh` on the host. The script is "smart":
- If executed on the host: runs `devcontainer up` to build/start the container, then `devcontainer exec` to run the entrypoint inside it.
- If executed inside a container (detected via `/.dockerenv`): skips the build step and just runs the entrypoint directly. This path is used by GitHub Codespaces.
---
The three scripts
| Script | Runs on | Purpose | | ------------------------------------ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `scripts/devcontainer-dev.sh` | host | Picks a host port, kills stale processes, starts readiness poller, opens browser when ready, `devcontainer exec`s the entrypoint. | | `scripts/devcontainer-entrypoint.sh` | in container | Cleans stale X/VNC state, starts Xvfb, fluxbox, x11vnc, websockify/noVNC, dbus, gnome-keyring, then runs `pnpm start`. | | `scripts/devcontainer-post-start.sh` | in container | `postStartCommand` in `devcontainer.json`. In Codespaces (detected via `$CODESPACES`) it `nohup`-launches the entrypoint in the background so the noVNC preview pane opens without user action. |
---
What the container runs
- **Node 24** (matches `.nvmrc`)
- **Electron + runtime deps** — GTK, NSS, X11, etc. (see `.devcontainer/Dockerfile`)
- **Display stack** — Xvfb (virtual framebuffer at 1920×1200), fluxbox (window manager, auto-fullscreens windows with `WM_CLASS=ToolHive`), x11vnc (VNC server), noVNC + websockify (browser client on port 6080)
- **Secret provider stack** — dbus + gnome-keyring. Required by ToolHive's secret API; without them the backend returns 500 on secrets endpoints. Mirrors the setup in `.github/workflows/_e2e.yml`.
- **Docker-in-Docker** via the `docker-in-docker:2` devcontainer feature. Supplies `/var/run/docker.sock` inside the container so the bundled `thv` CLI can spawn MCP-server containers.
---
Finding the URL (the logs are very long)
`pnpm start` + electron-forge + Vite + Electron + HMR produce a **lot** of output. The terminal scrollback often exhausts. Three recovery mechanisms are built in:
1. **Written to a file** by the launcher: `~/.cache/toolhive-studio-url`. Survives any amount of output.
cat ~/.cache/toolhive-studio-url
2. **Set as the terminal tab title** via OSC escape. Visible in the tab bar of most terminals regardless of scrollback state. 3. **Prominent banners** in the output — a green initial block right after `devcontainer up`, plus an inverse-video `✓ ToolHive ready — <URL>` banner that fires only once the app is actually usable.
If you're piping the output:
pnpm devContainer:dev 2>&1 | tee /tmp/dev.log
# later:
grep -E 'ToolHive ready|vnc\.html' /tmp/dev.log
The readiness banner is what you care about. It gates on **three** signals simultaneously:
- noVNC's HTTP endpoint answers (the browser tab will actually load)
- The Electron binary is running (matched via `pgrep -f 'electron/dist/electron'`)
- `thv serve` is running (matched via `pgrep -f 'thv serve'` — _not_ `pgrep -x thv`, because the short-lived version-check invocation also matches on bare name)
Only once all three are true does the banner fire and the host's browser auto-open.
---
Per-worktree isolation
Each git worktree gets its own independent devcontainer:
- **Container identity** — labelled with `devcontainer.local_folder=<absolute-worktree-path>`. The devcontainer CLI uses this to decide which container to reuse vs create fresh.
- **Node modules** — volume named `toolhive-node-modules-<basename>`, scoped to the worktree's basename. No cross-worktree install pollution.
- **Host port** — the primary clone uses `:6080`; additional worktrees try `:6080` first and fall back to a Docker-assigned random port if it's taken. So multiple worktrees can run simultaneously. The actual bound port is queried with `docker port "$CONTAINER_ID" 6080/tcp` and the URL is generated from that.
- **DinD, display state, keyring, etc.** — all container-local. Tearing down a worktree's container removes all of it.
**The host is never touched** — no host-side `pnpm install`, no host-side `/tmp/.X11-unix` mount, n
Read more
name: devcontainer-dev description: Spin up and interact with ToolHive Studio's containerized dev environment (Xvfb + noVNC + DinD). Use when running, testing, or debugging the app in isolation — locally, in a git worktree, or in GitHub Codespaces; when touching `.devcontainer/*`, `scripts/devcontainer-*.sh`, or the `devContainer:dev` npm script; or when debugging "blank white window", "Docker daemon failed to start", or "Missing X server" errors in the devcontainer. The container is fully isolated: no host pnpm install, no host Docker socket, no host X11/GPU — experiment freely without contaminating the host. allowed-tools: Read, Grep, Glob, Bash
Containerized Dev Environment
An isolated, cross-platform test environment for ToolHive Studio. The whole Electron app — including its backend `thv` binary and the MCP-server containers it spawns — runs inside a single devcontainer. You interact with the UI via a noVNC browser tab.
The entire stack (Node, Electron, display server, window manager, VNC server, Docker-in-Docker, DBus, keyring) lives in the container. **Nothing is installed on the host.** That's the whole point: every worktree can have its own container and its own experiments, with zero risk of contaminating the user's global installs.
---
Entry point
pnpm devContainer:dev
runs `scripts/devcontainer-dev.sh` on the host. The script is "smart":
- If executed on the host: runs `devcontainer up` to build/start the container, then `devcontainer exec` to run the entrypoint inside it.
- If executed inside a container (detected via `/.dockerenv`): skips the build step and just runs the entrypoint directly. This path is used by GitHub Codespaces.
---
The three scripts
| Script | Runs on | Purpose | | ------------------------------------ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `scripts/devcontainer-dev.sh` | host | Picks a host port, kills stale processes, starts readiness poller, opens browser when ready, `devcontainer exec`s the entrypoint. | | `scripts/devcontainer-entrypoint.sh` | in container | Cleans stale X/VNC state, starts Xvfb, fluxbox, x11vnc, websockify/noVNC, dbus, gnome-keyring, then runs `pnpm start`. | | `scripts/devcontainer-post-start.sh` | in container | `postStartCommand` in `devcontainer.json`. In Codespaces (detected via `$CODESPACES`) it `nohup`-launches the entrypoint in the background so the noVNC preview pane opens without user action. |
---
What the container runs
- **Node 24** (matches `.nvmrc`)
- **Electron + runtime deps** — GTK, NSS, X11, etc. (see `.devcontainer/Dockerfile`)
- **Display stack** — Xvfb (virtual framebuffer at 1920×1200), fluxbox (window manager, auto-fullscreens windows with `WM_CLASS=ToolHive`), x11vnc (VNC server), noVNC + websockify (browser client on port 6080)
- **Secret provider stack** — dbus + gnome-keyring. Required by ToolHive's secret API; without them the backend returns 500 on secrets endpoints. Mirrors the setup in `.github/workflows/_e2e.yml`.
- **Docker-in-Docker** via the `docker-in-docker:2` devcontainer feature. Supplies `/var/run/docker.sock` inside the container so the bundled `thv` CLI can spawn MCP-server containers.
---
Finding the URL (the logs are very long)
`pnpm start` + electron-forge + Vite + Electron + HMR produce a **lot** of output. The terminal scrollback often exhausts. Three recovery mechanisms are built in:
1. **Written to a file** by the launcher: `~/.cache/toolhive-studio-url`. Survives any amount of output.
cat ~/.cache/toolhive-studio-url
2. **Set as the terminal tab title** via OSC escape. Visible in the tab bar of most terminals regardless of scrollback state. 3. **Prominent banners** in the output — a green initial block right after `devcontainer up`, plus an inverse-video `✓ ToolHive ready — <URL>` banner that fires only once the app is actually usable.
If you're piping the output:
pnpm devContainer:dev 2>&1 | tee /tmp/dev.log # later: grep -E 'ToolHive ready|vnc\.html' /tmp/dev.log
The readiness banner is what you care about. It gates on **three** signals simultaneously:
- noVNC's HTTP endpoint answers (the browser tab will actually load)
- The Electron binary is running (matched via `pgrep -f 'electron/dist/electron'`)
- `thv serve` is running (matched via `pgrep -f 'thv serve'` — _not_ `pgrep -x thv`, because the short-lived version-check invocation also matches on bare name)
Only once all three are true does the banner fire and the host's browser auto-open.
---
Per-worktree isolation
Each git worktree gets its own independent devcontainer:
- **Container identity** — labelled with `devcontainer.local_folder=<absolute-worktree-path>`. The devcontainer CLI uses this to decide which container to reuse vs create fresh.
- **Node modules** — volume named `toolhive-node-modules-<basename>`, scoped to the worktree's basename. No cross-worktree install pollution.
- **Host port** — the primary clone uses `:6080`; additional worktrees try `:6080` first and fall back to a Docker-assigned random port if it's taken. So multiple worktrees can run simultaneously. The actual bound port is queried with `docker port "$CONTAINER_ID" 6080/tcp` and the URL is generated from that.
- **DinD, display state, keyring, etc.** — all container-local. Tearing down a worktree's container removes all of it.
**The host is never touched** — no host-side `pnpm install`, no host-side `/tmp/.X11-unix` mount, n
Run any Model Context Protocol (MCP) server — securely, instantly, anywhere. ToolHive is the easiest way to discover, deploy, and manage MCP servers. Launch any MCP server in a locked-down container with just a few clicks.
Repo: stacklok/toolhive-studio
Other skills on toolhive-studio.
- /bug-fix-tdd
Reproduce and fix bugs using TDD. Use when analyzing a bug report, writing a regression test, or applying a minimal fix. Covers test placement, mock patterns, and the red-green-refactor workflow for automated bug fixing.
Open skill - /deep-links
Deep links in ToolHive Studio. Use when implementing, debugging, or asking about deep link features (toolhive-gui:// protocol), adding new deep link intents, understanding the deep link architecture, IPC model, or platform/packaging support.
Open skill - /security-vuln-remediation
Remediate security vulnerabilities found by Grype or pnpm audit. Use when a security scan fails, a CVE needs fixing, or you need to analyze, upgrade, override, or ignore a vulnerable dependency.
Open skill - /skill-creator
Create new AI agent skills for Claude Code, Codex, and Cursor. Use when asked to create a skill, add a new agent capability, or set up a slash command.
Open skill - /skill-editor
REQUIRED for editing any skill file. Ensures changes sync to Claude, Codex, and Cursor. Never edit .claude/skills/ files directly - always use this skill.
Open skill - /testing-api-assertions
Verify API requests in tests. Use when testing that correct API calls are made for create, update, or delete operations. Use when testing mutations, form submissions, or actions with backend side effects.
Open skill

