Skip to content

Add graceful shutdown #634

Description

@itsmiso-ai

Parent umbrella issue: #622
Source audit: Weekly tech debt audit: miso-chat - 2026-06-24
Source audit date: 2026-06-24

Original recommendation

P2 — Add graceful shutdown: install process.on('SIGTERM'|'SIGINT') handler that closes the HTTP server, drains the SSE client set, calls gatewayWsManager.disconnect(), waits up to a budget, then exits. Test the drain with a fake server.

Matched top finding

  • public/index.html is still 4,085 lines mixing UI, auth, queue, SSE, rendering, reactions, and notifications. Some extraction to public/lib/ (api-client, capacitor-detect, render-utils, reaction-events-browser) happened, but session-key hydration, mobile auth callback, queueing, theme, message rendering, history poll, and the rest still live in one HTML file. This is the same P2 Extract public/index.html runtime modules #541 had, partially closed.

  • server.js is 1,513 lines. Auth/sessions, Gateway WS, sessions routes, link previews, mobile OTA manifest proxying, SSE, reactions, and reactions all coexist. #540 partially closed; remaining hotspots are session/agent routes (738-1346) and reactions (1321-1365).

  • No tests cover /api/agents, /api/assistant-identity, /api/csrf-token, /api/openclaw-status, /api/openclaw-stop, or the /api/config exposure contract (only /api/sessions redirects and CSRF-origin cases in tests/authz-integration.test.js). The previously closed Add authorization/integration test matrix #539 added the test file but the matrix is sparse for new endpoints.

  • No graceful shutdown handler. No process.on('SIGTERM'/'SIGINT') in server.js or lib/*.js — the gateway WebSocket manager does not get a disconnect() on SIGTERM, leaving an open WS until process exit. Affects rollouts in Kubernetes and the Docker HEALTHCHECK drain.

  • Dead devDependency: typescript ^6.0.0 in package.json:40 with no .ts files anywhere in the repo (only .js and one .gradle).

  • Two update managers with overlapping concepts. lib/update-manager.js (server metadata helper, 164 lines) and public/mobile/update-manager.js (browser OTA lifecycle, 280 lines) both compute semver comparisons, both look at GitHub releases, but they live separately. Consolidate mobile update manager logic #542 partially closed — the client-side module now documents lib/update-manager.js as "for reference" but the server module still pulls @capacitor/core at import time, which is only safe because Capacitor?.isNativePlatform?.() short-circuits in Node. Importing the server module on Node without @capacitor/core installed would still crash.

  • README/OTA-docs drift. README changelog stops at v0.4.13. docs/OTA-UPDATES.md still references CAPGO_API_KEY and Capgo Cloud despite public/mobile/update-manager.js:7-9 saying "No Capgo account/API key required" and the validator rejecting cloud-shaped artifacts. Current package.json is 0.4.19, current release is 0.4.19.

  • Link preview performance still soft-bounded. Cache + coalescer exist (lib/link-preview-cache.js, applied at server.js:967-983), but _fetchLinkPreview can still do DNS resolution + up to 5 redirect hops + up to LINK_PREVIEW_MAX_HTML_CHARS (default 250,000) per uncached request. Tests cover cache behavior but not concurrent SSRF-DNS timeout pressure.

  • CSRF/origin integration tests don't cover the rotated-token path or the X-CSRF-Token missing-with-token-in-session path. tests/authz-integration.test.js exercises csrfOriginCheck (trusted/untrusted) and the unauthenticated redirects for /api/sessions//api/agents. It does not exercise csrfTokenCheck at all (the middleware name is even destructured out in tests/security.test.js:4).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions