Skip to content

Bug: reopen can leave unresolved tail tool calls and relies on local transcript compensation #64530

@glfruit

Description

@glfruit

Summary

When an OpenClaw run is interrupted after persisting an assistant message with toolCall blocks but before all matching toolResult messages are written, reopening the session can leave unresolved tail tool calls in transcript history.

In the same family of failures, persisted orphan user turns on reopen should be preserved explicitly rather than silently rewound.

Why this matters

This is a runtime/session-transcript integrity issue, not a plugin issue.

The affected seams are around:

  • session transcript persistence
  • reopen/startup transcript reconciliation
  • toolCall/toolResult pairing at the tail of history
  • interrupted-run recovery semantics

User-visible impact:

  • dangling tail toolCalls remain in transcript history
  • reopen behavior depends on local repair behavior instead of explicit first-party source truth
  • interrupted sessions can produce confusing repaired history unless compensation is deterministic and append-only

Repro shape

A minimal broken tail looks like one of these:

  1. assistant tool call with no trailing tool result
{ "role": "assistant", "content": [{ "type": "toolCall", "id": "tc_1", "name": "read" }], "stopReason": "tool_use" }
  1. assistant tool calls with only partial trailing tool results
{ "role": "assistant", "content": [
  { "type": "toolCall", "id": "tc_1", "name": "exec" },
  { "type": "toolCall", "id": "tc_2", "name": "read" }
]}
{ "role": "toolResult", "toolCallId": "tc_1", "toolName": "exec", ... }
  1. persisted orphan user tail
{ "role": "user", "content": [{ "type": "text", "text": "please continue" }] }

followed by process interruption before any next assistant turn is persisted.

Expected behavior

On reopen/startup, the runtime should:

  • preserve existing transcript rows
  • append explicit synthetic recovery rows only at the tail
  • append synthetic error toolResult rows for unresolved tail tool calls
  • append one explicit assistant recovery marker for a persisted orphan-user tail
  • be idempotent on repeated reopen
  • never re-execute tools during repair
  • never silently rewind or delete prior transcript rows

Safe compensation strategy

The bounded first slice that worked well locally is:

  • repair unresolved tail-only tool calls before prompt build
  • then run orphan-user preservation logic
  • use append-only synthetic recovery rows
  • log structured repair telemetry
  • do nothing for already-healthy transcripts

Validation performed locally

I validated this behavior in a local installed-package environment with targeted executable regressions covering:

  • orphan-user replay marker append
  • unresolved tail toolCall -> synthetic error toolResult repair
  • idempotent second reopen
  • healthy transcript no-op
  • seam/order check that tail repair runs before orphan-user repair

Important environment note

The installed package available locally did not include a full upstream editable source tree / test harness suitable for a normal upstream patch cycle, so the local validation was done as:

  • runtime-active bundle patch for immediate behavior validation
  • installed-package source-truth overlay + targeted executable tests for parity evidence

That means this issue is not claiming a clean upstream repo patch was already merged. The request here is to absorb this behavior into official first-party runtime source + tests.

Requested upstream fix

Please add a first-party source-level transcript compensation seam that:

  1. repairs unresolved tail toolCall / toolResult pairing on reopen
  2. preserves orphan-user tails with an explicit assistant recovery marker
  3. guarantees append-only, idempotent repair
  4. includes upstream tests for:
    • missing tail toolResult
    • orphan-user replay
    • repeated reopen no-op
    • healthy transcript no-op

Non-goals for this issue

This is not asking for:

  • deep historical transcript rewrite
  • tool re-execution
  • fabricated successful outputs
  • a full write-atomicity redesign in the same patch

Those can be separate follow-ups if needed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    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