Skip to content

[Bug]: isolated cron turns can misclassify exact NO_REPLY tool results as incomplete when the final assistant message is empty #68452

Description

@anyech

Bug type

Reliability / silent-turn regression

Summary

Isolated cron runs with payload.kind: "agentTurn" can fail with incomplete turn detected ... payloads=0 even when the latest successful tool result is exact NO_REPLY and the unchanged path should be a quiet success.

The bad path is:

  1. the assistant makes a tool call
  2. the tool succeeds and returns exact NO_REPLY
  3. the model then emits an empty final assistant message (stopReason=stop, content: [])
  4. embedded finalization drops into the incomplete-turn path instead of preserving the silent success

On announce surfaces this can also produce duplicate user-visible failures: the raw embedded error payload plus the cron-layer wrapped failure message.

Version

Observed on OpenClaw 2026.4.15.

I validated the source fix against current main as well.

Minimal repro shape

A narrow cron repro only needs an isolated agentTurn job whose unchanged path is intentionally silent.

Prompt shape:

  • use a single tool call (for example exec)
  • the tool returns exact NO_REPLY on the no-change path
  • the prompt tells the assistant: if stdout is exactly NO_REPLY, reply with only NO_REPLY

Sanitized transcript shape from a failing run:

  • user: cron prompt
  • assistant: tool call
  • tool result: NO_REPLY
  • assistant: final message with stopReason=stop and content: []

Sanitized gateway log line from the same failure mode:

[agent/embedded] incomplete turn detected: runId=<redacted> sessionId=<redacted> stopReason=stop payloads=0 — surfacing error to user

Expected behavior

When the latest successful tool result is exact NO_REPLY, the cron run should complete as a quiet success.

That means:

  • no incomplete-turn error
  • no user-visible failure message
  • cron delivery treats the run as silent / unchanged

Actual behavior

The embedded runner only considers assistant-derived payloads (plus tool media) during finalization.

If the assistant ends with an empty final message after the successful NO_REPLY tool result, the run reaches payloadCount=0 and is treated as an incomplete turn.

That converts a valid quiet-success path into an error.

Root-cause seam

The source seam is in embedded runner finalization:

  • buildEmbeddedRunPayloads(...) returns no assistant payloads
  • mergeAttemptToolMediaPayloads(...) still leaves no payloads
  • resolveIncompleteTurnPayloadText(...) sees payloadCount=0
  • but the latest successful tool result already contains a valid silent sentinel (NO_REPLY)

So the runner loses a legitimate silent outcome because it only trusts the final assistant payload path, not the latest successful tool-result path.

Fix shape validated locally

The narrow fix that worked for me was:

  1. when the trigger is cron
  2. and final payload count is still zero
  3. and there is no timeout / abort / tool-error blocker
  4. inspect the latest successful tool result in messagesSnapshot
  5. if that tool result is exact silent NO_REPLY, synthesize a final silent payload instead of surfacing an incomplete-turn error

This keeps the change scoped to cron silent-success handling and does not change normal visible replies.

Validation

Targeted source-level validation passed:

  • src/agents/pi-embedded-runner/run.incomplete-turn.test.ts
  • src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts

The regression test covers the exact shape above:

  • latest successful tool result is NO_REPLY
  • final assistant message is empty
  • trigger is cron
  • final result is quiet success instead of incomplete-turn error

Why I think this should be a separate tracked bug

This is related to earlier cron / NO_REPLY delivery work, but it appears to be a different seam:

  • previous fixes were largely about delivery-time stripping / suppression
  • this one happens earlier, inside embedded-run finalization, before cron delivery gets a correct silent payload to work with

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