# Dev Start

> Start the development server in background mode

- Skill: `vm0-ai/dev-start` (Agent Skill)
- Install (CLI): `npx skillmds@latest add vm0-ai/dev-start`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vm0-ai/dev-start/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: vm0-ai (https://skillmd.com/u/vm0-ai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vm0-ai/dev-start

---


Start the Turbo development server in background mode.

## Arguments

Your args are: `$ARGUMENTS`

Supports `--tunnel-hostname=<fqdn>` to use a fixed tunnel domain instead of the auto-generated one.

**Note**: The web app automatically starts a Cloudflare tunnel during dev startup (issue #1726). This means `VM0_API_URL` is set automatically and webhooks work out of the box. The web app takes ~15 seconds longer to start than other packages due to tunnel setup.

## Workflow

### Step 1: Ensure Local Hosts Mapping

Ensure every local `vm7.ai` development domain resolves to `127.0.0.1` before running status checks or opening the app. The platform app calls `https://api.vm7.ai:8443`; if `api.vm7.ai` resolves outside the container while `www.vm7.ai` and `app.vm7.ai` resolve locally, the web/app services can look healthy while platform API calls hang.

Use `sudo tee` to update `/etc/hosts`; do not use `sed -i` because `/etc/hosts` is often a mounted file in devcontainers and atomic rename can fail with `Device or resource busy`.

```bash
VM7_DOMAINS="vm7.ai www.vm7.ai app.vm7.ai api.vm7.ai docs.vm7.ai platform.vm7.ai"

needs_hosts_update=0
for domain in $VM7_DOMAINS; do
  if ! getent hosts "$domain" | awk '{print $1}' | grep -qx "127.0.0.1"; then
    needs_hosts_update=1
  fi
done

if [ "$needs_hosts_update" -eq 1 ]; then
  tmp_hosts=$(mktemp)
  awk '
    /(^|[[:space:]])(vm7|www[.]vm7|app[.]vm7|api[.]vm7|docs[.]vm7|platform[.]vm7)[.]ai([[:space:]]|$)/ { next }
    { print }
  ' /etc/hosts > "$tmp_hosts"
  printf "127.0.0.1 %s\n" "$VM7_DOMAINS" >> "$tmp_hosts"
  sudo tee /etc/hosts < "$tmp_hosts" >/dev/null
  rm -f "$tmp_hosts"
fi

getent hosts api.vm7.ai www.vm7.ai app.vm7.ai docs.vm7.ai
```

If this command cannot update `/etc/hosts` because `sudo` is unavailable or prompts for credentials, report that blocker before starting browser work; otherwise API calls from the platform can time out silently.

### Step 2: Pre-flight Check

Run the dev server status check. This verifies SSL certificates (regenerating if missing) and checks port accessibility:

```bash
PROJECT_ROOT=$(git rev-parse --show-toplevel)
cd "$PROJECT_ROOT/turbo" && pnpm dev:status
```

If all three services show `running`, the vm0 dev server is already up — display the output, skip the runner/prepare/vm0 dev steps, and still run the marketing startup step below so `www.vm7.ai` has a local backend. Otherwise, proceed to start the server.

### Step 3: Start Runner in Background

Start the runner first using Bash tool with `run_in_background: true` parameter. The runner takes several minutes to initialize, so we start it early to overlap with the prepare step.

```bash
PROJECT_ROOT=$(git rev-parse --show-toplevel)
cd "$PROJECT_ROOT/turbo" && pnpm runner
```

This returns a task_id for monitoring.

**Note on runner**: The runner takes several minutes to initialize (cross-compile, upload, build rootfs/snapshots). The app works without it — only chat/agent interaction features require the runner. You will be notified when the runner background task completes.

### Step 4: Start Marketing in Background

Start the independent `vm0-marketing` project using Bash tool with `run_in_background: true` parameter. This runs independently from runner, `prepare.sh`, and `pnpm dev`, so start it early and let it overlap with the rest of the workflow.

Use the sibling checkout if it exists at `../vm0-marketing`; otherwise clone `vm0-ai/vm0-marketing` into `/tmp/vm0-marketing`. Find the first `package.json` directory, install dependencies there, sync env from the marketing repo root, and start its `dev` script on port `3042`:

```bash
PROJECT_ROOT=$(git rev-parse --show-toplevel)
SIBLING_MARKETING_ROOT="$(cd "$PROJECT_ROOT/.." && pwd)/vm0-marketing"

if [ -d "$SIBLING_MARKETING_ROOT" ]; then
  MARKETING_ROOT="$SIBLING_MARKETING_ROOT"
else
  MARKETING_ROOT="/tmp/vm0-marketing"
  if [ ! -d "$MARKETING_ROOT/.git" ]; then
    rm -rf "$MARKETING_ROOT"
    gh repo clone vm0-ai/vm0-marketing "$MARKETING_ROOT"
  fi
fi

MARKETING_PACKAGE_JSON="$(find "$MARKETING_ROOT" -name package.json -not -path "*/node_modules/*" -not -path "*/.next/*" -not -path "*/dist/*" -print -quit)"
if [ -z "$MARKETING_PACKAGE_JSON" ]; then
  echo "No package.json found in $MARKETING_ROOT"
  exit 1
fi

MARKETING_DEV_DIR="$(dirname "$MARKETING_PACKAGE_JSON")"
cd "$MARKETING_DEV_DIR"
pnpm install
bash "$MARKETING_ROOT/scripts/sync-env.sh"
PORT=3042 pnpm dev 2>&1 | tee "$MARKETING_ROOT/.dev-server.log"
```

This returns a task_id for monitoring.

### Step 5: Run prepare.sh

While the runner is initializing in the background, run `prepare.sh` to set up the environment (sync .env.local, install dependencies, run database migrations). This may take a few minutes — wait for it to complete:

```bash
PROJECT_ROOT=$(git rev-parse --show-toplevel)
cd "$PROJECT_ROOT" && bash -c 'set -o pipefail; bash scripts/prepare.sh 2>&1 | tee /tmp/prepare-output.log'
```

#### If prepare.sh fails

If the command above exits with a non-zero code, do the following **before** reporting the failure to the user:

1. **Gather diagnostic info** by running:

```bash
echo "HOSTNAME: $(bash "$(git rev-parse --show-toplevel)/scripts/cn.sh")"
echo "BRANCH: $(git branch --show-current)"
echo "--- LAST 20 LINES ---"
tail -20 /tmp/prepare-output.log
```

2. **Determine the failed step**: inspect the output for `db:migrate` or `Database migrations failed`. If found, the failed step is `db:migrate`; otherwise, report it as `prepare.sh`.

3. **Send a Slack notification** to `#flaky-test` using the Slack MCP tool (`slack_send_message`) with the following message format:

```
🔴 Dev server prepare failed on `<hostname>` (branch: `<branch>`)

**Failed step:** <prepare.sh or db:migrate>
**Error snippet:**
\`\`\`
<last ~20 lines of /tmp/prepare-output.log>
\`\`\`

> ℹ️ This may be caused by FK constraints preventing migration on databases with real data.
```

4. **Report the failure to the user** as normal — do NOT silently swallow the error, and do NOT proceed to Step 4.

#### If prepare.sh succeeds

Proceed to Step 6 as normal. No Slack notification is sent.

### Step 6: Start Dev Server in Background

After `prepare.sh` completes successfully, start the dev server using Bash tool with `run_in_background: true` parameter.

**Important**: Use `tee` to write output to a persistent log file so `/dev-logs` works even after context compaction. The log file is the primary way `/dev-logs` reads output.

If `--tunnel-hostname=<fqdn>` was provided in args, pass it as `TUNNEL_HOSTNAME` env var:

```bash
PROJECT_ROOT=$(git rev-parse --show-toplevel)
cd "$PROJECT_ROOT/turbo" && TUNNEL_HOSTNAME=<fqdn> pnpm dev 2>&1 | tee "$PROJECT_ROOT/turbo/.dev-server.log"
```

Otherwise (default):

```bash
PROJECT_ROOT=$(git rev-parse --show-toplevel)
cd "$PROJECT_ROOT/turbo" && pnpm dev 2>&1 | tee "$PROJECT_ROOT/turbo/.dev-server.log"
```

**Save the dev server task_id** to a local file for TaskOutput fallback:

```bash
PROJECT_ROOT=$(git rev-parse --show-toplevel)
echo "<dev-task_id>" > "$PROJECT_ROOT/turbo/.dev-task-id"
```

### Step 7: Display Results

Once the server is confirmed running, display the URLs:

```
✅ Dev server started in background
🔧 Runner deployment started in background (takes several minutes)
🌐 Marketing dev server started in background

- Web:      https://www.vm7.ai:8443
- App:      https://app.vm7.ai:8443
- Docs:     https://docs.vm7.ai:8443
- Marketing backend: http://localhost:3042

The app is usable now. Chat/agent features will become available once the runner finishes initializing.

Next steps:
- Use `/dev-logs` to view server output
- Use `/dev-logs [pattern]` to filter logs (e.g., `/dev-logs error`)
- Use `/dev-stop` to stop the server
```

## Notes

- Use TaskOutput with the task_ids from `run_in_background` to check runner, marketing, or dev server output
- This operation runs in main context so the background task persists throughout the conversation
- **NEVER use `nohup` to start the server** (e.g., `nohup pnpm dev > /tmp/dev-server.log 2>&1 &`). Always use the Bash tool's `run_in_background: true` parameter instead.

