# How to Run OpenWork in Headless Mode: Complete Automation and CI Guide

> Run OpenWork in headless mode using pnpm dev headless-web. Enable browser automation, CI pipelines, and agent-driven workflows without the Electron shell. Automate OpenWork easily.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: how-to-guide
- Published: 2026-08-21

---

**To run OpenWork in headless mode, execute `pnpm dev:headless-web` to launch a Vite-served web UI and local API server without the Electron desktop shell, enabling browser-based automation, CI pipelines, and agent-driven workflows.**

OpenWork is an open-source workspace platform that typically operates within an Electron desktop environment. When you need to run OpenWork in headless mode for automated testing, continuous integration, or background agent operations, the repository provides a specialized headless web stack coordinated by [`scripts/dev-headless-web.ts`](https://github.com/different-ai/openwork/blob/main/scripts/dev-headless-web.ts) that eliminates the desktop dependency while preserving full workspace functionality and authentication state.

## Architecture of the Headless Web Stack

The headless implementation consists of three coordinated components that replace the traditional desktop shell:

- **Vite Dev Server**: Serves the OpenWork web UI (`@openwork/app`) on a local port (default 5178), making the interface accessible via standard browsers instead of the Electron window
- **OpenWork Server**: The core API and storage layer ([`apps/server/src/cli.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/cli.ts)) running on port 8778, handling workspace persistence, token management, and file operations
- **Launcher/Runtime Manifest**: Orchestrates process management, port allocation, and authentication tokens through functions like `buildHeadlessRuntimeManifest`, writing operational state to [`tmp/dev-headless-web.json`](https://github.com/different-ai/openwork/blob/main/tmp/dev-headless-web.json) with permissions `0600` for credential security

### Single-Instance Management and Workspace Persistence

The launcher enforces a single-instance-per-worktree policy through the `readExistingManifest` and `probeStack` functions. If a healthy instance already exists, the script reuses it and prints the existing URLs unless you specify the `--replace` flag.

Workspace data persists across restarts via `mergeHeadlessServerConfig` in [`scripts/dev-headless-web-lib.ts`](https://github.com/different-ai/openwork/blob/main/scripts/dev-headless-web-lib.ts), which merges the isolated configuration at [`tmp/headless-server.json`](https://github.com/different-ai/openwork/blob/main/tmp/headless-server.json) with user-created workspaces to prevent data loss during relaunch.

## Starting the Headless Stack

### Basic Execution

Run the headless web stack with the predefined script command:

```bash
pnpm dev:headless-web

```

This command spawns the Vite server and OpenWork server via `buildHeadlessServerLaunch`, then prints accessible URLs to the terminal. The launcher handles the initialization sequence and health checks automatically.

### Detached Execution for Long-Running Processes

For automation scenarios where the stack must survive terminal closure, use the `--detach` flag:

```bash
pnpm dev:headless-web --detach

```

The script respawns itself using `spawnLogged` with `detached: true`, placing the process in its own process group. This ensures the headless stack continues running after the launching terminal exits, making it ideal for CI environments and background services.

### Port and Token Configuration

The launcher automatically handles port conflicts through `resolvePort` and `getFreePort`, defaulting to 5178 for the web UI and 8778 for the API server. If these ports are busy, the script discovers free alternatives and updates the runtime manifest accordingly.

Control authentication token behavior during restarts using these flags:

```bash

# Force fresh tokens and new ports (useful for complete reset)

pnpm dev:headless-web --replace

# Restart server but preserve existing tokens (keeps browser tabs authenticated)

pnpm dev:headless-web --replace --keep-tokens

```

Token management occurs through `resolveHeadlessTokens` in [`scripts/dev-headless-web-lib.ts`](https://github.com/different-ai/openwork/blob/main/scripts/dev-headless-web-lib.ts), ensuring authentication survives crash-restarts while allowing explicit token refresh when required.

## Understanding the Runtime Manifest

The launcher writes critical operational metadata to [`tmp/dev-headless-web.json`](https://github.com/different-ai/openwork/blob/main/tmp/dev-headless-web.json), including:

- **URLs** for the web interface and API endpoints
- **Authentication tokens** for seamless browser access
- **Process IDs** for stack management and health checks

Access the manifest programmatically to extract connection details:

```bash
cat tmp/dev-headless-web.json

```

## Programmatic Integration with Headless Threads

For agent-driven automation without browser interaction, use the `@openwork/headless-threads` client library to interact directly with the server:

```typescript
import { createHeadlessThreadClient } from "@openwork/headless-threads";

const client = createHeadlessThreadClient({
  baseUrl: "http://127.0.0.1:8778",
  workspaceId: "ws_1",
  token: process.env.OPENWORK_TOKEN,
  defaultModel: { providerId: "anthropic", modelId: "claude-sonnet-5" },
});

const thread = await client.createThread({
  title: "Sample thread",
  prompt: "Explain the benefits of headless mode.",
});
await client.waitForThread(thread.id, { timeoutMs: 60_000 });

```

This client abstracts the HTTP sequence required to interact with [`apps/server/src/cli.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/cli.ts), enabling direct API integration in scripts, test suites, and CI jobs without rendering the UI.

## Optional Den Proxy Configuration

When integrating with Den authentication services in headless environments, enable the optional proxy to avoid CORS issues and maintain seamless sign-in flows:

```bash
OPENWORK_DEV_HEADLESS_WEB_DEN_PROXY=1 pnpm dev:headless-web

```

The script handles `denTarget` and `denApiUrl` routing in [`scripts/dev-headless-web.ts`](https://github.com/different-ai/openwork/blob/main/scripts/dev-headless-web.ts), proxying `/api/den` requests to the real Den target. Disable this proxy when running isolated automation that doesn't require Den integration:

```bash
OPENWORK_DEV_HEADLESS_WEB_DEN_PROXY=0 pnpm dev:headless-web

```

## Summary

- Execute `pnpm dev:headless-web` to run OpenWork in headless mode, replacing the Electron shell with a browser-accessible Vite server
- Use `--detach` to run the stack as a background process independent of the launching terminal via `spawnLogged` with `detached: true`
- Workspace data persists across restarts through `mergeHeadlessServerConfig` in [`scripts/dev-headless-web-lib.ts`](https://github.com/different-ai/openwork/blob/main/scripts/dev-headless-web-lib.ts)
- The runtime manifest at [`tmp/dev-headless-web.json`](https://github.com/different-ai/openwork/blob/main/tmp/dev-headless-web.json) provides URLs, tokens, and process IDs for external tooling integration
- Control authentication token lifecycle with `--replace` and `--keep-tokens` flags managed by `resolveHeadlessTokens`
- Integrate programmatically using `createHeadlessThreadClient` from the `@openwork/headless-threads` package

## Frequently Asked Questions

### How do I keep OpenWork running in headless mode after closing the terminal?

Use the `--detach` flag when launching. The script calls `spawnLogged` with `detached: true`, creating a new process group that survives terminal closure. The stack continues running in the background and writes its state to [`tmp/dev-headless-web.json`](https://github.com/different-ai/openwork/blob/main/tmp/dev-headless-web.json) for reconnection by other processes or subsequent health checks.

### Will my workspaces persist when I restart the headless server?

Yes. The launcher uses `mergeHeadlessServerConfig` to combine the isolated server configuration with existing workspace data from previous sessions. Unless you manually delete the workspace storage files, your data persists across `--replace` restarts. Use `--keep-tokens` to maintain authentication state for existing browser tabs.

### How does the headless stack handle port conflicts?

The launcher automatically detects busy ports using `getFreePort` and `resolvePort`. If the default ports 5178 (web) or 8778 (server) are occupied, it allocates available alternatives and updates the runtime manifest accordingly. The single-instance-per-worktree check in `readExistingManifest` prevents accidental duplicate launches in the same directory.

### Can I disable the Den proxy if I don't need authentication?

Yes. Set the environment variable `OPENWORK_DEV_HEADLESS_WEB_DEN_PROXY=0` before launching. This disables the `/api/den` proxy routing defined in [`scripts/dev-headless-web.ts`](https://github.com/different-ai/openwork/blob/main/scripts/dev-headless-web.ts), which is useful when running isolated automation that doesn't require Den integration or when avoiding external authentication flows entirely.