# How to Debug OpenWork: A Complete Troubleshooting Guide for the OpenWork Server

> Troubleshoot OpenWork server issues effectively. Learn to debug OpenWork by checking health endpoints, inspecting logs, and verifying configurations for seamless operation.

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

---

**Debug OpenWork by checking the health endpoint, inspecting structured JSON logs, capturing client output via `/dev/log`, and verifying token and environment configuration through dedicated API endpoints.**

OpenWork is an open-source, desktop-first development platform that combines a Node-based server with an Electron UI. When issues arise, the server provides multiple built-in observability tools to diagnose problems quickly. This guide walks through practical debugging techniques using the actual source code from `different-ai/openwork`.

## Checking Server Health and Status

The fastest way to verify that your OpenWork server is running correctly is the **health endpoint**.

```bash
curl -s http://localhost:8787/health | jq .

```

A healthy response returns:

```json
{
  "ok": true,
  "version": "1.2.3",
  "uptimeMs": 15420
}

```

In [`apps/server/src/routes/core.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/routes/core.ts), the health check is registered as a simple no-auth route:

```ts
addRoute(routes, "GET", "/health", "none", async () => healthResponse());

```

If this request times out or returns a non-200 status, the server has either crashed, is hung, or another process is occupying the configured port. Use `lsof -i :8787` to check for port conflicts.

## Reading Structured Server Logs

OpenWork emits structured logs to **stdout** in either plain-text or JSON format. The logger is created in [`apps/server/src/server.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/server.ts) via `createServerLogger`:

```ts
export function createServerLogger(config: ServerConfig): ServerLogger {
  const runId = process.env.OPENWORK_RUN_ID ?? shortId();
  const emit = (level, message, attributes) => {
    const merged = { ...baseAttributes, ...(attributes ?? {}) };
    if (config.logFormat === "json") {
      const record = { ... };
      process.stdout.write(`${JSON.stringify(record)}\n`);
      return;
    }
    process.stdout.write(`${message}\n`);
  };
  return { log: emit };
}

```

Enable JSON logging for easier filtering:

```bash
export OPENWORK_LOG_FORMAT=json
npm run dev

```

Then filter for errors:

```bash
npm run dev 2>&1 | grep '"severityText":"ERROR"'

```

Every request is automatically logged via `logRequest`, which includes method, path, status code, duration, and authentication mode. This makes it straightforward to spot failing endpoints or unauthorized requests.

## Capturing Client-Side Logs with `/dev/log`

The OpenWork Electron UI sends its console output to a server-side sink. Retrieve these logs via:

```bash
curl -s http://localhost:8787/dev/log | jq .

```

The response includes the file path where logs are stored:

```json
{
  "ok": true,
  "path": "/tmp/openwork-dev.log"
}

```

Tail this file to see real-time client activity:

```bash
tail -f /tmp/openwork-dev.log

```

You can also post manual test logs:

```bash
curl -X POST -H "Content-Type: application/json" \
  -d '[{"level":"info","msg":"debug test"}]' \
  http://localhost:8787/dev/log

```

In [`apps/server/src/routes/core.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/routes/core.ts), the dev-log route uses the `EnvService` to sync managed provider auth after successful writes, ensuring secrets propagate to the engine.

## Debugging Token Authentication

Authentication issues are the most common source of `401 Unauthorized` errors. OpenWork supports two token types:

- **Host tokens**: Passed via `x-openwork-host-token` header
- **Bearer tokens**: Passed via `Authorization: Bearer <token>` header

The `TokenService` class in [`apps/server/src/tokens.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/tokens.ts) handles all token operations:

```ts
export class TokenService {
  async create(scope, { label }) {
    const id = shortId();
    const token = `owt_${shortId().replace(/-/g, "")}`;
    const record = { id, hash: hashToken(token), scope, createdAt: Date.now(), label };
    this.tokens = [record, ...this.tokens];
    await writeTokenStore(this.path, this.tokens);
    return { id, token, scope, createdAt: record.createdAt, label };
  }
}

```

List existing tokens to verify scope:

```bash
curl -s -H "x-openwork-host-token: $HOST_TOKEN" \
  http://localhost:8787/tokens | jq .

```

Create a new token with owner scope:

```bash
curl -X POST -H "x-openwork-host-token: $HOST_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"scope":"owner","label":"debug-token"}' \
  http://localhost:8787/tokens | jq .

```

Routes declare required auth modes in their registration: `none`, `host`, `client`, or `host-token`. Mismatched scopes cause immediate rejection.

## Verifying Environment Variables and Configuration

The `EnvService` in [`apps/server/src/env-file.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/env-file.ts) manages a local `.env` file. Query and modify values via the API:

```bash

# List all environment variables (with values)

curl -s -H "x-openwork-host-token: $HOST_TOKEN" \
  "http://localhost:8787/env?includeValues=true" | jq .

# Read a specific key

curl -s -H "x-openwork-host-token: $HOST_TOKEN" \
  http://localhost:8787/env/OPENAI_API_KEY | jq .

```

Common errors include `EnvStoreReadError` for malformed `.env` files. Ensure each line follows `KEY=VALUE` format without stray characters.

Missing environment variables typically cause specific error responses. For example, voice session creation returns `openai_api_key_missing` when `OPENAI_API_KEY` is not set.

## Monitoring Reload Events

OpenWork watches configuration files and triggers hot reloads automatically. The watcher is started in [`apps/server/src/server.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/server.ts):

```ts
let watcherHandle = startReloadWatchers({ config, reloadEvents, logger });

```

Watch for reload activity in logs:

```bash
tail -f server.log | grep -i reload

```

When a file change is detected, you'll see:

```

[openwork] reload requested for workspace <id> (reason: file-modified)

```

The [`reload-watcher.ts`](https://github.com/different-ai/openwork/blob/main/reload-watcher.ts) module handles the actual file-system watching. If reloads don't fire, verify that:

- `config.watchPaths` includes your target directory
- The file is within the workspace root
- The reload fingerprint has actually changed (see `computeReloadFingerprint`)

Manually trigger a workspace reload:

```bash
curl -X POST -H "x-openwork-host-token: $HOST_TOKEN" \
  http://localhost:8787/w/<workspace-id>/reload

```

## Debugging OpenWork Voice Sessions

The realtime voice endpoint at `POST /voice/realtime/session` (lines 285-313 in [`apps/server/src/routes/core.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/routes/core.ts)) creates sessions with OpenAI or OpenWork Models:

```ts
addRoute(routes, "POST", "/voice/realtime/session", "host", async (ctx) => {
  const body = await readJsonBody(ctx.request);
  return jsonResponse(await createOpenAiRealtimeVoiceSession(env, body));
});

```

Test voice session creation:

```bash
curl -X POST -H "x-openwork-host-token: $HOST_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-realtime-2"}' \
  http://localhost:8787/voice/realtime/session | jq .

```

Expected response includes `clientSecret`. Common errors:

- `openai_api_key_missing`: Add `OPENAI_API_KEY` to `.env`
- `openwork_models_voice_unavailable`: Check `OPENWORK_API_KEY` configuration

After obtaining `clientSecret`, you can connect directly to `wss://api.openai.com/v1/realtime` with a WebSocket client for low-level protocol debugging.

## Common Error Scenarios and Solutions

| Symptom | Root Cause | Solution |
|--------|-----------|----------|
| `401 unauthorized` | Token scope mismatch or missing header | Verify `Authorization` or `x-openwork-host-token` header; check token scope with `GET /tokens` |
| `/health` timeout | Server crashed or port conflict | Check startup logs; run `lsof -i :8787` to find port conflicts |
| `404 /ui/assets/...` | Toy UI disabled | Ensure `OPENWORK_ENABLE_UI` is not set to disable the UI |
| `openai_api_key_missing` | Missing API key in environment | Add `OPENAI_API_KEY` to `.env` file via `/env` endpoints |
| Reload watcher ignores changes | Path not in `watchPaths` or outside workspace | Verify directory is in `config.watchPaths` and under workspace root |
| `EnvStoreReadError` | Malformed `.env` file | Fix syntax errors in `.env` or rewrite via `/env` API |
| `429 agent_diagnostics_busy` | Rate limit on diagnostics | Wait for `OPENWORK_AGENT_DIAGNOSTICS_COOLDOWN_MS` to elapse |

## Starting the Server for Debug Sessions

Clone and run OpenWork locally:

```bash
git clone https://github.com/different-ai/openwork.git
cd openwork/dev
pnpm install

# Generate a host token for API access

export OPENWORK_HOST_TOKEN=$(openssl rand -hex 16)

# Start with JSON logging enabled

export OPENWORK_LOG_FORMAT=json
pnpm dev

```

Watch for the startup banner:

```

[openwork] dev profile=default cdp=http://127.0.0.1:9223

```

This confirms the server is listening and provides the Chrome DevTools Protocol URL for additional debugging.

## Summary

- **Health checks**: Use `GET /health` as your first diagnostic step
- **Log inspection**: Enable `OPENWORK_LOG_FORMAT=json` for parseable structured logs
- **Client debugging**: Capture UI logs via `/dev/log` endpoint
- **Auth verification**: List and create tokens through `/tokens` API to resolve `401` errors
- **Environment management**: Query and update `.env` values via `/env` endpoints
- **Reload monitoring**: Watch logs for file-change events and verify `watchPaths` configuration
- **Voice troubleshooting**: Test with `/voice/realtime/session` and verify API keys are set

## Frequently Asked Questions

### How do I enable debug logging in OpenWork?

Set the `OPENWORK_LOG_FORMAT` environment variable to `json` before starting the server. This causes `createServerLogger` in [`apps/server/src/server.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/server.ts) to emit structured JSON logs instead of plain text, making it easier to filter and analyze output with tools like `jq` and `grep`.

### Why is my OpenWork API request returning 401 Unauthorized?

First, verify you're sending either the `x-openwork-host-token` header or an `Authorization: Bearer <token>` header. Then, use `GET /tokens` with your host token to inspect existing tokens and their scopes. Each route declares a required auth mode (`none`, `host`, `client`, or `host-token` in [`apps/server/src/routes/registry.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/routes/registry.ts)), and mismatched scopes cause immediate rejection.

### Where does OpenWork store server logs?

The server writes all logs to stdout. The `/dev/log` endpoint captures client-side logs from the Electron UI and returns a file path in its JSON response. For persistent storage, redirect stdout to a file or use your process manager's logging facilities. The structured JSON format enables easy ingestion into centralized logging systems.

### How do I fix `EnvStoreReadError` in OpenWork?

This error from [`apps/server/src/env-file.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/env-file.ts) indicates your `.env` file has malformed syntax. Common issues include missing `=` signs, stray comments, or special characters in values. You can either manually edit the file to ensure each line follows `KEY=VALUE` format, or use the `/env` API endpoints to rewrite the entire environment store cleanly.