tutorial

Monitoring Your Self-Hosted Eclipse Theia IDE with Vigilmon

Production monitoring for Eclipse Theia — web server availability, LSP service health, WebSocket stability, file system backend, Git integration, extension loading, terminal backend, and search indexing.

Monitoring Your Self-Hosted Eclipse Theia IDE with Vigilmon

Your Eclipse Theia IDE deployment went down. Developers can't open files. The terminal backend crashed silently. You found out when your team started complaining in chat.

Eclipse Theia is a cloud IDE framework used by companies to host browser-based development environments at scale. Running it self-hosted on port 3000 means you're responsible for keeping LSP services, WebSocket connections, file system access, and extension loading all healthy simultaneously.

By the end of this guide you'll have:

  • HTTP monitoring for the Theia web server
  • LSP service health checks (typescript-language-server, pylsp)
  • WebSocket connection stability monitoring
  • File system and terminal backend health
  • Git integration service monitoring
  • Slack alerts and a public status page

Why monitoring matters for Eclipse Theia

Theia is a Node.js/TypeScript application but it orchestrates many backend services that can fail independently:

  • LSP crashes — code completion and diagnostics stop working, but the IDE UI still loads
  • WebSocket instability — the editor frontend disconnects repeatedly, making the IDE unusable
  • Terminal backend failure — integrated terminals stop spawning, forcing users to SSH separately
  • Search indexing failures — workspace search returns incomplete or stale results
  • Extension loading errors — plugins silently fail to activate, removing key functionality

Without monitoring each layer, you're flying blind.


Step 1: Add a health check endpoint to Theia

Theia doesn't ship with a built-in /health endpoint, so add one via a lightweight Express sidecar or a Theia backend plugin.

Option A: Express health sidecar

// theia-health.js
const express = require('express')
const { execSync } = require('child_process')
const http = require('http')
const app = express()

function checkTheia() {
  return new Promise((resolve) => {
    const req = http.get('http://localhost:3000/', { timeout: 3000 }, (res) => {
      resolve(res.statusCode === 200 ? 'up' : 'degraded')
    })
    req.on('error', () => resolve('down'))
    req.on('timeout', () => { req.destroy(); resolve('down') })
  })
}

function checkLsp(processName) {
  try {
    const out = execSync(`pgrep -f ${processName}`, { timeout: 2000 }).toString()
    return out.trim().length > 0 ? 'up' : 'down'
  } catch {
    return 'down'
  }
}

app.get('/health', async (req, res) => {
  const checks = {
    theia_web: await checkTheia(),
    typescript_lsp: checkLsp('typescript-language-server'),
    python_lsp: checkLsp('pylsp'),
    git_service: checkLsp('git'),
  }

  const healthy = Object.values(checks).every(v => v === 'up')
  res.status(healthy ? 200 : 503).json({
    status: healthy ? 'ok' : 'degraded',
    checks,
  })
})

app.listen(3003, () => console.log('Theia health on :3003'))

Start it:

node theia-health.js &

Verify:

curl http://localhost:3003/health
# {"status":"ok","checks":{"theia_web":"up","typescript_lsp":"up","python_lsp":"up","git_service":"up"}}

Option B: Theia backend plugin

If you control the Theia distribution, register a health route in a backend plugin:

// packages/health/src/node/health-backend-module.ts
import { ContainerModule } from 'inversify'
import { BackendApplicationContribution } from '@theia/core/lib/node'
import { HealthContribution } from './health-contribution'

export default new ContainerModule(bind => {
  bind(BackendApplicationContribution).to(HealthContribution).inSingletonScope()
})
// packages/health/src/node/health-contribution.ts
import { injectable } from 'inversify'
import { BackendApplicationContribution } from '@theia/core/lib/node'
import * as express from 'express'

@injectable()
export class HealthContribution implements BackendApplicationContribution {
  configure(app: express.Application): void {
    app.get('/api/health', (req, res) => {
      res.json({ status: 'ok', timestamp: Date.now() })
    })
  }
}

Step 2: Monitor the Theia web server

Sign up at vigilmon.online (free, no card required).

Web server monitor:

  1. Click New Monitor → HTTP
  2. URL: https://theia.yourdomain.com
  3. Expected status: 200
  4. Check interval: 1 minute

Composite health check:

  1. Click New Monitor → HTTP
  2. URL: https://theia.yourdomain.com:3003/health
  3. Expected status: 200
  4. Keyword alert: "status":"ok"

Add a response time alert at 3000ms — if Theia's initial page load takes over 3 seconds, the developer experience is already degraded.


Step 3: Monitor LSP service health

Language Server Protocol services are the heart of IDE functionality. They're separate OS processes that Theia spawns on demand and can crash independently.

The health sidecar already checks for running LSP processes. You can extend it to actually test LSP responsiveness:

// Add to theia-health.js
const net = require('net')

function checkLspSocket(port) {
  return new Promise((resolve) => {
    const socket = new net.Socket()
    socket.setTimeout(2000)
    socket.on('connect', () => { socket.destroy(); resolve('up') })
    socket.on('timeout', () => { socket.destroy(); resolve('down') })
    socket.on('error', () => resolve('down'))
    socket.connect(port, '127.0.0.1')
  })
}

// typescript-language-server typically listens on a stdio pipe, not a TCP port
// Check the process is alive and responding to signals
function checkLspHealth(processName) {
  try {
    const pid = execSync(`pgrep -f ${processName}`).toString().trim()
    if (!pid) return 'down'
    // Send signal 0 to check process is alive
    process.kill(parseInt(pid), 0)
    return 'up'
  } catch {
    return 'down'
  }
}

In Vigilmon, set a keyword alert on "typescript_lsp":"down" or "python_lsp":"down" to get notified when specific LSP services fail.


Step 4: Monitor WebSocket connection stability

Theia uses WebSockets for all real-time communication between the browser client and backend. Disconnections cause the editor to go into a reconnecting state, which is disruptive for active work.

Create a WebSocket probe:

// ws-probe.js
const WebSocket = require('ws')

const ws = new WebSocket('wss://theia.yourdomain.com', {
  headers: { 'Origin': 'https://theia.yourdomain.com' }
})

let connected = false
const timeout = setTimeout(() => {
  if (!connected) {
    console.error('WebSocket connection timeout')
    process.exit(1)
  }
}, 5000)

ws.on('open', () => {
  connected = true
  clearTimeout(timeout)
  ws.close()
  process.exit(0)
})

ws.on('error', (err) => {
  clearTimeout(timeout)
  console.error('WebSocket error:', err.message)
  process.exit(1)
})

Run it as a cron heartbeat:

# /etc/cron.d/theia-ws-probe
*/3 * * * * node /opt/theia/ws-probe.js && curl -fsS "https://vigilmon.online/heartbeat/YOUR_HEARTBEAT_ID"

In Vigilmon, create a Heartbeat Monitor with a 6-minute expected interval (two missed checks = alert).


Step 5: Monitor the file system backend

The Theia file system backend (@theia/filesystem) handles all file reads and writes. If it becomes unresponsive, the editor loses the ability to open or save files.

Add a file system check to the health sidecar:

const fs = require('fs').promises

async function checkFilesystem(workspacePath) {
  try {
    const testFile = `${workspacePath}/.theia-health-probe`
    await fs.writeFile(testFile, Date.now().toString())
    await fs.readFile(testFile, 'utf8')
    await fs.unlink(testFile)
    return 'up'
  } catch {
    return 'down'
  }
}

// Add to health endpoint
checks.filesystem = await checkFilesystem(process.env.THEIA_WORKSPACE || '/workspace')

This write/read/delete cycle confirms the workspace filesystem is actually writable, not just mounted read-only or full.


Step 6: Monitor the terminal backend and search indexing

Terminal backend:

The Theia terminal backend (@theia/terminal) spawns shell processes via node-pty. Check that shells can be spawned:

const { spawn } = require('child_process')

function checkTerminalBackend() {
  return new Promise((resolve) => {
    const proc = spawn('echo', ['health-ok'], { timeout: 2000 })
    let output = ''
    proc.stdout.on('data', d => output += d)
    proc.on('close', (code) => {
      resolve(code === 0 && output.includes('health-ok') ? 'up' : 'down')
    })
    proc.on('error', () => resolve('down'))
  })
}

Search indexing:

Theia uses ripgrep for workspace search. Confirm the rg binary is available and functional:

function checkSearchIndexing() {
  try {
    execSync('rg --version', { timeout: 2000 })
    return 'up'
  } catch {
    return 'down'
  }
}

Step 7: Slack alerts and status page

Slack alerts:

  1. Go to Notifications → New Channel → Slack
  2. Paste your Slack incoming webhook URL
  3. Enable on all Theia monitors

When the TypeScript LSP crashes:

🔴 DEGRADED: theia.yourdomain.com:3003/health
Keyword mismatch: "typescript_lsp":"down" detected
Region: EU-Central

Status page:

  1. Status Pages → New Status Page
  2. Add all monitors
  3. Name it "Theia IDE Status"
  4. Share with your development team

What you've built

| What | How | |------|-----| | Web server availability | Vigilmon HTTP → port 3000 | | LSP service health | Health sidecar process checks + Vigilmon keyword alerts | | WebSocket stability | Heartbeat from ws-probe cron | | File system backend | Write/read/delete probe in health sidecar | | Terminal backend | Shell spawn probe in health sidecar | | Search indexing | ripgrep binary check in health sidecar | | Composite health | Vigilmon HTTP → port 3003 | | Slack alerts | Vigilmon Slack notification channel | | Status page | Vigilmon public status page |


Next steps

  • Monitor response time trends on the web server endpoint — Theia's startup time grows with large workspaces
  • Add SSL certificate expiry monitoring for your Theia domain
  • Set up separate heartbeat monitors for each language server you support at scale

Get started free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →