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

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.

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

A healthy response returns:

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

In apps/server/src/routes/core.ts, the health check is registered as a simple no-auth route:

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 via createServerLogger:

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:

export OPENWORK_LOG_FORMAT=json
npm run dev

Then filter for errors:

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:

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

The response includes the file path where logs are stored:

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

Tail this file to see real-time client activity:

tail -f /tmp/openwork-dev.log

You can also post manual test logs:

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, 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 handles all token operations:

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:

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

Create a new token with owner scope:

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 manages a local .env file. Query and modify values via the API:


# 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:

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

Watch for reload activity in logs:

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 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:

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) creates sessions with OpenAI or OpenWork Models:

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:

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:

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 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), 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →