Skip to content

[Bug]: lifecycle:end event payload missing aborted and stopReason on pi-embedded path #66534

@xiaohuaxi

Description

@xiaohuaxi

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.

Metadata

Metadata

Assignees

Labels

P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.staleMarked as stale due to inactivity

Type

No type
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