Skip to content

macOS: manual update launchd job kept relaunching, repeatedly SIGTERMing gateway and consuming Telegram turns #82167

Description

@DougButdorf

Bug type

Regression / macOS launchd lifecycle bug

Summary

After updating to 2026.5.12, a submitted manual update launchd job kept relaunching even though the updater script had completed successfully (last exit code = 0). Each relaunch stopped/started the gateway, causing repeated clean SIGTERM shutdowns every few minutes.

During that churn, Telegram direct messages were received/spooled and the Telegram update offset advanced, but the messages did not appear in the active session transcript and no normal agent reply completed. From the user perspective, Telegram messages were being swallowed.

This looks related to the general launchd restart-loop class in #73673, but this report is specifically about a submitted manual update job with inferred keepalive behavior, not the main ai.openclaw.gateway LaunchAgent crashing on startup.

Environment

  • OpenClaw: 2026.5.12 (f066dd2)
  • macOS: 26.4.1 (25E253), Apple Silicon / arm64
  • Node: v25.9.0
  • Install: npm global under Homebrew Node
  • Gateway service: macOS LaunchAgent ai.openclaw.gateway, command:
    • /opt/homebrew/opt/node/bin/node /opt/homebrew/lib/node_modules/openclaw/dist/index.js gateway --port 18789
  • Gateway bind: loopback, port 18789
  • Telegram: bot channel, polling mode

Observed behavior

Telegram history showed the bot responding to earlier messages, then later only sending repeated watchdog/progress acknowledgements like “Got it. Working on message ...; The turn is still active,” without completing the actual turns.

Local evidence showed the Telegram updates existed in the local ingress spool and update-offset-default.json had advanced past them, but the corresponding direct session transcript under ~/.openclaw/agents/main/sessions/*.jsonl did not contain those user messages.

The gateway logs showed repeated startup followed by clean SIGTERM shutdowns. Example pattern:

[gateway] http server listening ...
[telegram] [default] starting provider (...)
[telegram] Inbound message telegram:<redacted-user-id> -> <redacted-bot> (direct, 178 chars)
[gateway] signal SIGTERM received
[gateway] received SIGTERM; shutting down
[shutdown] completed cleanly ...

The root local finding was this submitted launchd job:

launchctl print gui/$(id -u)/ai.openclaw.manual-update-2026-05-14-2026-5-12

Key redacted fields:

type = Submitted
state = running
program = /Users/<user>/.openclaw/workspace/tmp/openclaw-update-2026-05-14.sh
runs = 4843
last exit code = 0
properties = keepalive | inferred program

The updater script was a one-shot update helper that stopped the gateway, ran an OpenClaw update to 2026.5.12, restarted the gateway, and checked health. It appears to have succeeded, but launchd continued relaunching it because of inferred keepalive behavior.

Recovery performed locally

The machine was recovered by booting out the submitted manual update job:

launchctl bootout gui/$(id -u)/ai.openclaw.manual-update-2026-05-14-2026-5-12

After that:

  • launchctl list no longer showed the manual update job
  • ai.openclaw.gateway stayed running
  • openclaw gateway status --json reported the LaunchAgent active/running
  • openclaw channels status --json reported Telegram configured/running/connected in polling mode
  • No further repeated gateway SIGTERM loop was observed during the follow-up window

Current recovered service state:

ai.openclaw.gateway pid=<redacted> status=running
manual update launchd job absent from launchctl list
telegram channel configured=true running=true connected=true mode=polling

Expected behavior

A manual/update helper submitted to launchd should not be left in a relaunching state after successful completion. A successful updater should either:

  1. avoid inferred/implicit KeepAlive semantics entirely, or
  2. explicitly unload/bootout its submitted launchd job when done, or
  3. use a launchd policy that does not relaunch on successful exit.

Separately, Telegram spool/offset handling should avoid advancing irreversibly past an inbound update until the update is durably persisted/routed into the target session, or should provide a recovery/replay path when the gateway is killed during processing.

Actual behavior

The successful updater job had run thousands of times with last exit code = 0, repeatedly stopping/restarting the gateway. Telegram inbound updates during that period were consumed from Telegram’s perspective but did not complete as normal session turns.

Impact

High for macOS users using Telegram and launchd-managed gateway updates:

  • gateway repeatedly goes down/up despite a successful update
  • Telegram messages can appear received but go unanswered
  • Telegram update offset can advance, making the missed messages non-obvious to replay
  • cron jobs and watchdogs can pile up during the churn
  • user-visible symptom is “bot is alive enough to acknowledge/progress, but real messages are swallowed”

Related issues

Additional notes

I intentionally did not include tokens, bot credentials, full user IDs, the full OpenClaw config, or raw Telegram spool JSON. The local evidence above was enough to identify and stop the relaunch loop without exposing secrets.

Reported on behalf of Doug Butdorf after recovering the affected macOS node.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions