Skip to content

refactor: gateway supervisor + disk logging + quit cleanup (PR-1 of v1.3.39) - #10

Merged
needsbuilder merged 2 commits into
mainfrom
feat/gateway-supervisor
Apr 28, 2026
Merged

needsbuilder merged 2 commits into
mainfrom
feat/gateway-supervisor

Conversation

@needsbuilder

Copy link
Copy Markdown
Owner

Summary

PR-1 of two for the v1.3.39 stability release addressing the bug
multiple users reported in the Kakao group chat: the gateway keeps
shutting down (often right after a Telegram command), and EasyClaw
silently leaves them with an unresponsive bot.

This PR is the safety core — it restructures gateway lifecycle
internals without changing user-visible behavior. Auto-restart, the
3-state tray, and the diagnostic copy modal land in PR-2.

  • Supervisor abstraction — new gateway-supervisor.ts owns lifecycle
    via an EventEmitter singleton with macOS-launchd and WSL adapters.
    Existing IPC / tray / index call sites keep working through a thin
    gateway.ts wrapper.
  • Real liveness probes — new gateway-probes.ts replaces the brittle
    wsl.exe child .killed check (which kept saying "running" while the
    WSL-internal openclaw was already dead). macOS uses launchctl print
    • lsof :18789; WSL runs ss → lsof → /proc/net/tcp inside the distro.
  • Persistent gateway log — new gateway-log-rotator.ts writes daemon
    stdout/stderr to userData/logs/gateway-YYYY-MM-DD.log with 7-day
    retention + 50 MB cap. Combined with the new StandardOutPath /
    StandardErrorPath launchd plist patch, future PR-2 diagnostics will
    always have the last crash output on disk.
  • Quit cleanup — before-quit now stops the supervisor + flushes the
    log before exit with a 3 s cap, fixing orphaned WSL daemons that caused
    port-already-in-use on next launch.
  • Plist migration — migrateGatewayPlist() runs once at startup so
    existing v1.3.38 users upgrade to capturing daemon stdout/stderr without
    needing to re-onboard.

Test plan

  • npm run typecheck — passes (node + web)
  • npm run lint — no new warnings (1 pre-existing prettier warning in
    LobsterLogo.tsx unchanged)
  • macOS smoke: open EasyClaw, verify Gateway shows "running", then
    pkill -9 -f openclaw-gateway. Tray should flip to "stopped" within
    ~10s. Manual restart via tray menu should bring it back.
  • macOS plist migration: with an existing install, launch EasyClaw
    and verify ~/Library/LaunchAgents/ai.openclaw.gateway.plist
    contains StandardOutPath after first run, and that
    userData/logs/launchd-gateway.{out,err}.log start populating.
  • Windows WSL smoke: open EasyClaw, then in another terminal
    wsl -d Ubuntu -u root -- pkill -9 -f openclaw. Tray should flip
    to "stopped" (this is the regression case — previously the tray
    stayed at "running" because wsl.exe's .killed flag was false).
  • Quit cleanup: trigger Quit from tray, then verify
    wsl --list --running does not show openclaw on Windows, and
    pgrep -f openclaw-gateway returns nothing on macOS.

PR-2 (auto-restart, 3-state tray, diagnostic modal, switchProvider boot
order fix) follows immediately.

Background: users in the Kakao group reported that the gateway keeps
shutting down — particularly after sending a Telegram command — and
EasyClaw silently leaves them with an unresponsive bot.

Phase 1 (PR-1) lays the safety core without changing any user-visible
behavior. Auto-restart, diagnostic UI, and the 3-state tray come in a
follow-up PR (PR-2).

What this changes:
- New gateway-supervisor.ts owns the Gateway lifecycle through an
  EventEmitter-based singleton with platform adapters for macOS launchd
  and Windows WSL. Internal status machine: idle → starting → running →
  stopped/failed. Emits 'died' / 'started' / 'failed' / 'log' /
  'status-changed'.
- New gateway-probes.ts replaces the brittle "wsl.exe child .killed"
  check with a real liveness probe: macOS uses launchctl print + lsof
  on port 18789, WSL runs ss / lsof / /proc/net/tcp inside the distro.
  Works around lsof being missing in some Ubuntu images.
- New gateway-log-rotator.ts persists daemon stdout/stderr to
  userData/logs/gateway-YYYY-MM-DD.log with 7-day retention and a 50 MB
  total cap so we always have the last crash output on disk.
- gateway.ts is now a thin wrapper delegating to the supervisor —
  existing IPC / tray / index call sites keep their imports.
- onboarder.ts patches the launchd plist to add StandardOutPath /
  StandardErrorPath and exposes a one-shot migrateGatewayPlist() helper
  that upgrades existing installs on first boot. Also exports
  wslKillOpenclaw() so the supervisor can reuse the same kill logic.
- index.ts before-quit now stops the supervisor and flushes the log
  before exiting (with a 3 s cap and re-entry guard) so a force quit
  no longer leaves the WSL daemon orphaned.

Verification: npm run typecheck and npm run lint both pass clean for
the modified files. End-to-end QA scenarios are scheduled for the
PR-2 release.
@vercel

vercel Bot commented Apr 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
easyclaw Ready Ready Preview, Comment Apr 27, 2026 6:21am

Request Review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a8b64aadb4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +22 to 26
while (Date.now() < deadline) {
if (!(await supervisor.isAlive())) return
await new Promise((r) => setTimeout(r, 500))
}
if (platform() !== 'win32') {
await forceKillGateway()
await new Promise((r) => setTimeout(r, 1000))
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore force-stop behavior after waitUntilStopped timeout

waitUntilStopped now exits silently once the deadline passes, even if supervisor.isAlive() is still true. Callers like backup restore depend on this helper to guarantee the gateway is down before modifying on-disk state; returning while the daemon is still running can leave concurrent writes during restore and produce inconsistent or failed imports. This refactor removed the previous hard-stop fallback without adding an equivalent safeguard.

Useful? React with 👍 / 👎.

Comment on lines +80 to +82
`(ss -ltn 2>/dev/null | awk '$4 ~ /:${port}$/ {print "OK"; exit 0}')`,
`(lsof -i :${port} -t 2>/dev/null | head -n1 | grep -q . && echo OK)`,
`(awk '$2 ~ /:${hex}$/ {print "OK"; exit}' /proc/net/tcp /proc/net/tcp6 2>/dev/null)`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make WSL probe fallback chain execute on ss failure

The first branch of WSL_PROBE_SCRIPT is a pipeline (ss ... | awk ...) inside an || chain, but without pipefail the pipeline status comes from awk, which is 0 even when ss is missing or returns no match. That means the later lsof and /proc/net/tcp checks are skipped, so environments where ss is unavailable will incorrectly report the gateway as not alive despite the service running.

Useful? React with 👍 / 👎.

@needsbuilder
needsbuilder merged commit 9f8b9bc into main Apr 28, 2026
3 checks passed
needsbuilder added a commit that referenced this pull request Apr 28, 2026
…(v1.3.39)

PR-2 of two for the v1.3.39 stability release. Builds on the supervisor
core from PR-1 (#10) and adds the user-visible behavior: gateway
crashes now self-heal, the tray surfaces what's happening, and a single
"Copy Diagnostic Info" button gives non-developer users a way to share
the actual crash output in the Kakao group chat.

What this changes:
- Auto-restart with exponential backoff (0/2/5/15/60 s) and a 5-per-hour
  hard cap. Manual user actions clear the cap so users can recover from
  the gave_up state. Restart attempts are persisted to
  gateway-restart-history.json so the cap survives app restarts.
- New diagnostic-collector aggregates Electron / OS / Node info, the
  last 200 stderr lines, recent stdout, restart history, port
  ownership, plus per-platform extras (launchctl print on macOS, wsl
  --version + ss output inside the distro on Windows). All output is
  PII-masked: Telegram bot tokens, sk-* / AIza* / sk-ant-* API keys
  are redacted before leaving the diagnostic surface.
- New diagnostic:collect / diagnostic:copy IPC + electronAPI.diagnostic
  bridge.
- Tray gains a 3-state label (running / restarting / failed), a "Copy
  Diagnostic Info" menu item, and notifications for died / gave_up
  events (5 min throttle, except gave_up which always fires).
- DoneStep shows a yellow "Gateway 자동 재시작 중" banner during
  restart, and a red "Gateway 재시작 실패" banner with a copy button
  when the cap is hit. The DiagnosticModal opens automatically on
  gave_up and lets the user retry, copy to clipboard, or jump to the
  Kakao open chat.
- ipc-handlers now drives the supervisor from onboard:run and
  config:switch-provider so the gateway is alive when DoneStep mounts
  (no more 30 s polling fallback) and switchProvider boots the daemon
  exactly once instead of twice.
- i18n keys for ko/en/ja/zh.
- Bump version to 1.3.39 + QA checklist (docs/QA-1.3.39.md).

Verification: npm run typecheck and npm run lint both clean (only the
pre-existing LobsterLogo prettier warning remains, untouched).
needsbuilder added a commit that referenced this pull request Apr 28, 2026
* feat: auto-restart + diagnostic copy + 3-state tray + boot order fix (v1.3.39)

PR-2 of two for the v1.3.39 stability release. Builds on the supervisor
core from PR-1 (#10) and adds the user-visible behavior: gateway
crashes now self-heal, the tray surfaces what's happening, and a single
"Copy Diagnostic Info" button gives non-developer users a way to share
the actual crash output in the Kakao group chat.

What this changes:
- Auto-restart with exponential backoff (0/2/5/15/60 s) and a 5-per-hour
  hard cap. Manual user actions clear the cap so users can recover from
  the gave_up state. Restart attempts are persisted to
  gateway-restart-history.json so the cap survives app restarts.
- New diagnostic-collector aggregates Electron / OS / Node info, the
  last 200 stderr lines, recent stdout, restart history, port
  ownership, plus per-platform extras (launchctl print on macOS, wsl
  --version + ss output inside the distro on Windows). All output is
  PII-masked: Telegram bot tokens, sk-* / AIza* / sk-ant-* API keys
  are redacted before leaving the diagnostic surface.
- New diagnostic:collect / diagnostic:copy IPC + electronAPI.diagnostic
  bridge.
- Tray gains a 3-state label (running / restarting / failed), a "Copy
  Diagnostic Info" menu item, and notifications for died / gave_up
  events (5 min throttle, except gave_up which always fires).
- DoneStep shows a yellow "Gateway 자동 재시작 중" banner during
  restart, and a red "Gateway 재시작 실패" banner with a copy button
  when the cap is hit. The DiagnosticModal opens automatically on
  gave_up and lets the user retry, copy to clipboard, or jump to the
  Kakao open chat.
- ipc-handlers now drives the supervisor from onboard:run and
  config:switch-provider so the gateway is alive when DoneStep mounts
  (no more 30 s polling fallback) and switchProvider boots the daemon
  exactly once instead of twice.
- i18n keys for ko/en/ja/zh.
- Bump version to 1.3.39 + QA checklist (docs/QA-1.3.39.md).

Verification: npm run typecheck and npm run lint both clean (only the
pre-existing LobsterLogo prettier warning remains, untouched).

* chore: revert manual version bump — release.mjs handles it

scripts/release.mjs runs npm version patch on its own. Pre-bumping in the PR would push the next release to 1.3.40 instead of 1.3.39.

* docs: replace fake-but-matching bot token in QA checklist with placeholder shape

GitGuardian flagged the literal string in S7 because it matched the Telegram bot token regex. Switch to placeholder syntax that conveys the same instruction without tripping secret scanners.
@needsbuilder
needsbuilder deleted the feat/gateway-supervisor branch April 28, 2026 03:01

This branch was successfully deployed

1 active deployment
Preview — a8b64aad Deployed Apr 27, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant