openclaw - ✅(Solved) Fix [Bug]: lifecycle:end event payload missing aborted and stopReason on pi-embedded path [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#66534Fetched 2026-04-15 06:25:46
View on GitHub
Comments
1
Participants
2
Timeline
4
Reactions
0
Author
Participants
Timeline (top)
commented ×1cross-referenced ×1mentioned ×1subscribed ×1

The main lifecycle:end event emitted by handleAgentEnd in src/agents/pi-embedded-subscribe.handlers.lifecycle.ts does not carry aborted or stopReason fields. Subscribers cannot distinguish cancellation from natural completion without falling back to the agent() RPC completion frame. The fallback emitter in agent-command.ts already constructs both fields but is unreachable because lifecycleEnded = true is set earlier in the same flow.

Root Cause

The main lifecycle:end event emitted by handleAgentEnd in src/agents/pi-embedded-subscribe.handlers.lifecycle.ts does not carry aborted or stopReason fields. Subscribers cannot distinguish cancellation from natural completion without falling back to the agent() RPC completion frame. The fallback emitter in agent-command.ts already constructs both fields but is unreachable because lifecycleEnded = true is set earlier in the same flow.

Fix Action

Fix / Workaround

  1. Start OpenClaw and subscribe to event:agent events for a given sessionKey (or have a plugin install an onAgentEvent listener).
  2. Dispatch an agent() RPC call that starts a long-running run on that session.
  3. While the run is executing, call abortEmbeddedPiRun(sessionId) directly (for example via the globalThis[Symbol.for("openclaw.embeddedRunState")].activeRuns.get(sessionId).abort() side door, or via chat.abort for a chat.send-initiated run).
  4. Observe the lifecycle:end payload delivered to the subscriber.

PR fix notes

PR #66574: fix(lifecycle): add missing aborted and stopReason fields to lifecycle:end event

Description (problem / solution / changelog)

Fixes #66534

The lifecycle:end event payload was missing aborted and stopReason fields, preventing subscribers from distinguishing cancellation from natural completion.

Root cause: handleAgentEnd in pi-embedded-subscribe.handlers.lifecycle.ts only emitted basic fields (phase, livenessState, replayInvalid, endedAt) while the fallback path in agent-command.ts already constructed the missing fields.

Changes

  • Add aborted field: true when stopReason is not 'end_turn' or 'stop_sequence'
  • Add stopReason field from lastAssistant.stopReason
  • Apply to both emitAgentEvent and onAgentEvent calls
  • Handle missing lastAssistant gracefully (aborted: false, stopReason: undefined)

Impact

Event subscribers can now distinguish abort from completion without falling back to RPC completion frames, fixing plugin bridges, UI clients, and telemetry pipelines

Testing

✅ Code follows existing patterns in fallback emitter ✅ Handles all edge cases (natural completion, cancellation, missing assistant)

Changed files

  • extensions/telegram/src/bot-message-context.audio-transcript.test.ts (modified, +22/-0)
  • extensions/telegram/src/bot-message-context.body.ts (modified, +1/-1)
  • extensions/telegram/src/bot-message-context.session.ts (modified, +2/-2)
  • src/agents/bash-tools.exec-approval-followup.test.ts (modified, +19/-0)
  • src/agents/bash-tools.exec-approval-followup.ts (modified, +7/-3)
  • src/agents/pi-embedded-subscribe.handlers.lifecycle.ts (modified, +6/-0)
  • src/auto-reply/reply/reply-payloads-base.ts (modified, +1/-4)
  • src/auto-reply/reply/reply-plumbing.test.ts (modified, +14/-0)
  • src/commands/message.test.ts (modified, +47/-0)
  • src/commands/message.ts (modified, +21/-10)
  • ui/src/styles/components.css (modified, +7/-0)

Code Example

{
  phase: "end",
  livenessState: ...,
  replayInvalid: ...,
  endedAt: Date.now(),
}

---

aborted: result.meta.aborted ?? false,
stopReason,
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

The main lifecycle:end event emitted by handleAgentEnd in src/agents/pi-embedded-subscribe.handlers.lifecycle.ts does not carry aborted or stopReason fields. Subscribers cannot distinguish cancellation from natural completion without falling back to the agent() RPC completion frame. The fallback emitter in agent-command.ts already constructs both fields but is unreachable because lifecycleEnded = true is set earlier in the same flow.

Steps to reproduce

  1. Start OpenClaw and subscribe to event:agent events for a given sessionKey (or have a plugin install an onAgentEvent listener).
  2. Dispatch an agent() RPC call that starts a long-running run on that session.
  3. While the run is executing, call abortEmbeddedPiRun(sessionId) directly (for example via the globalThis[Symbol.for("openclaw.embeddedRunState")].activeRuns.get(sessionId).abort() side door, or via chat.abort for a chat.send-initiated run).
  4. Observe the lifecycle:end payload delivered to the subscriber.

Expected behavior

The lifecycle:end payload contains a boolean aborted and a string stopReason (for example "aborted" when the run was canceled, "end_turn" when it completed naturally). This matches what the fallback emitter at src/agents/agent-command.ts:919-934 is already coded to produce for runs that bypass the main emit path.

Actual behavior

The lifecycle:end payload contains only {phase: "end", livenessState?, replayInvalid?, endedAt} (see src/agents/pi-embedded-subscribe.handlers.lifecycle.ts:130-148). Neither aborted nor stopReason is present, so subscribers cannot tell cancellation from completion.

OpenClaw version

2026.4.12

Operating system

Ubuntu 22.04.5 LTS on WSL2 (Linux 6.6.87.2-microsoft-standard-WSL2)

Install method

npm global

Model

N/A (model-agnostic; the bug is in event emitter code paths that run after the model response)

Provider / routing chain

N/A (bug occurs regardless of provider/routing)

Additional provider/model setup details

N/A

Logs, screenshots, and evidence

src/agents/pi-embedded-subscribe.handlers.lifecycle.ts handleAgentEnd emits at approximately L130-148 with a payload shape of roughly:

{
  phase: "end",
  livenessState: ...,
  replayInvalid: ...,
  endedAt: Date.now(),
}

No aborted or stopReason field is constructed there.

By contrast, the fallback path at src/agents/agent-command.ts:919-934 does construct both fields:

aborted: result.meta.aborted ?? false,
stopReason,

but the guard if (!lifecycleEnded) around that fallback emit is never true in practice. handleAgentEnd sets lifecycleEnded = true at agent-command.ts:910 before the fallback runs, so the enriched payload is prepared and then discarded. Downstream consumers (event subscribers, plugin bridges, UI clients) receive the field-less lifecycle:end exclusively.

Impact and severity

  • Affected: every subscriber to event:agent that needs to distinguish cancel from completion — plugin bridges, UI clients, telemetry pipelines.
  • Severity: Medium. Not a crash, but it forces clients to inspect the agent() RPC completion frame (result.meta.aborted) to recover information the event stream is already supposed to carry. This is problematic when the RPC caller and the event subscriber are different parties (for example a plugin that only observes events but did not initiate the run).
  • Frequency: Every cancel on an agent()-initiated run.
  • Consequence: Clients either carry duplicate plumbing to correlate completion frames back to runs, or they misreport cancel as completion.

Additional information

The likely minimum fix is to have handleAgentEnd read aborted and stopReason from the attempt result (or from lastAssistant.stopReason, as the agent-command.ts fallback already does) and include them in the emitted payload. The field set at the subscribe layer would then match what agent-command.ts:919-934 already intends to produce.

Verified against OpenClaw commit d7cc6f7643 (v2026.4.14-beta.1+69).

Related: feature request #66531 (proposing an agent.abort RPC). Enriching the lifecycle:end payload is useful regardless of whether that dedicated RPC lands, because it is the primary event-layer signal external clients receive.


Reported by the CoClaw team. This issue was discovered while developing @coclaw/openclaw-coclaw, a CoClaw channel plugin for OpenClaw.

extent analysis

TL;DR

The lifecycle:end event emitted by handleAgentEnd should be modified to include aborted and stopReason fields to distinguish between cancellation and natural completion.

Guidance

  • Review the handleAgentEnd function in src/agents/pi-embedded-subscribe.handlers.lifecycle.ts to determine how to include aborted and stopReason in the emitted payload.
  • Consider using the attempt result or lastAssistant.stopReason to populate these fields, as suggested in the agent-command.ts fallback path.
  • Verify that the updated lifecycle:end payload is correctly received by subscribers and used to distinguish between cancellation and completion.
  • Test the fix with different scenarios, such as aborting an agent()-initiated run and verifying that the lifecycle:end payload contains the expected aborted and stopReason fields.

Example

// src/agents/pi-embedded-subscribe.handlers.lifecycle.ts
function handleAgentEnd(attempt) {
  const payload = {
    phase: "end",
    livenessState: ...,
    replayInvalid: ...,
    endedAt: Date.now(),
    aborted: attempt.meta.aborted ?? false,
    stopReason: lastAssistant.stopReason,
  };
  // Emit the updated payload
}

Notes

The fix should be verified against the OpenClaw commit d7cc6f7643 (v2026.4.14-beta.1+69) to ensure compatibility.

Recommendation

Apply the workaround by modifying the handleAgentEnd function to include aborted and stopReason in the emitted payload, as this will provide the necessary information for subscribers to distinguish between cancellation and completion.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

The lifecycle:end payload contains a boolean aborted and a string stopReason (for example "aborted" when the run was canceled, "end_turn" when it completed naturally). This matches what the fallback emitter at src/agents/agent-command.ts:919-934 is already coded to produce for runs that bypass the main emit path.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING