openclaw - 💡(How to fix) Fix Heartbeat / cron / exec wakeups submit empty user prompt to model providers in 4.25, causing 400 INVALID_ARGUMENT on Vertex/Gemini [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#73879Fetched 2026-04-29 06:13:44
View on GitHub
Comments
1
Participants
2
Timeline
2
Reactions
0
Timeline (top)
closed ×1commented ×1

In 4.25, heartbeat/cron/exec wakeups are submitted as transient runtime context instead of visible user prompts (per CHANGELOG #66496/#66814). However, the new code path in selection-ABXC-aG3.js resolveRuntimeContextPromptParts() returns prompt: "" when transcriptPrompt is empty and effectivePrompt is non-empty — submitting an empty user prompt to the model. Vertex/Gemini rejects this with 400 INVALID_ARGUMENT. OpenRouter/xiaomi/mimo-v2-pro tolerates it (returns content normally).

Reproduces consistently on every heartbeat tick in agents pinned to Vertex/Gemini.

Root Cause

In 4.25, heartbeat/cron/exec wakeups are submitted as transient runtime context instead of visible user prompts (per CHANGELOG #66496/#66814). However, the new code path in selection-ABXC-aG3.js resolveRuntimeContextPromptParts() returns prompt: "" when transcriptPrompt is empty and effectivePrompt is non-empty — submitting an empty user prompt to the model. Vertex/Gemini rejects this with 400 INVALID_ARGUMENT. OpenRouter/xiaomi/mimo-v2-pro tolerates it (returns content normally).

Reproduces consistently on every heartbeat tick in agents pinned to Vertex/Gemini.

Fix Action

Fix / Workaround

  • Originally observed on OpenClaw 2026.4.25 (aa36ee6)
  • Status on 4.26 (be8c246): UNVERIFIED — source line refs below are from the 4.25 dist; 4.26 builds rename the selection-* file (different hash suffix), need to re-grep before filing
  • Vanilla runtime (no local patches)
  • Install: npm-global at /home/openclawops/.npm-global/lib/node_modules/openclaw/openclaw.mjs
  • Affected providers: Vertex/Gemini (rejects with 400 Model input cannot be empty)
  • Tolerated by: OpenRouter / xiaomi / mimo-v2-pro

For agents pinned to Vertex/Gemini as their heartbeat model, every 5-min tick fails with the empty-input 400. We mitigated by swapping the heartbeat model away from Vertex/Gemini (currently OpenRouter/xiaomi/mimo-v2-pro, durable since 2026-04-27 22:11Z). Heartbeat empty-input storm stopped post-swap and has NOT reappeared on 4.26 — mitigation holds.

Workaround (current)

RAW_BUFFERClick to expand / collapse

Upstream bug report DRAFT — pending Trent blind-pass

Target: github.com/openclaw/openclaw/issues Status: DRAFT v0 — not filed. Needs Trent review (RCA primarily lives in his workspace memory).


Title

Heartbeat / cron / exec wakeups submit empty user prompt to model providers in 4.25, causing 400 INVALID_ARGUMENT on Vertex/Gemini

Environment

  • Originally observed on OpenClaw 2026.4.25 (aa36ee6)
  • Status on 4.26 (be8c246): UNVERIFIED — source line refs below are from the 4.25 dist; 4.26 builds rename the selection-* file (different hash suffix), need to re-grep before filing
  • Vanilla runtime (no local patches)
  • Install: npm-global at /home/openclawops/.npm-global/lib/node_modules/openclaw/openclaw.mjs
  • Affected providers: Vertex/Gemini (rejects with 400 Model input cannot be empty)
  • Tolerated by: OpenRouter / xiaomi / mimo-v2-pro

Summary

In 4.25, heartbeat/cron/exec wakeups are submitted as transient runtime context instead of visible user prompts (per CHANGELOG #66496/#66814). However, the new code path in selection-ABXC-aG3.js resolveRuntimeContextPromptParts() returns prompt: "" when transcriptPrompt is empty and effectivePrompt is non-empty — submitting an empty user prompt to the model. Vertex/Gemini rejects this with 400 INVALID_ARGUMENT. OpenRouter/xiaomi/mimo-v2-pro tolerates it (returns content normally).

Reproduces consistently on every heartbeat tick in agents pinned to Vertex/Gemini.

Source chain

4.25 source (where bug was first traced)

  1. heartbeat-runner-CUi7jNCS.js — builds non-empty ctx.Body
  2. get-reply-ltpHDntZ.js line ~2261 — sets transcriptBodyBase="" for heartbeat
  3. runReplyAgent — receives non-empty commandBody and empty transcriptCommandBody
  4. Embedded path — passes effectivePrompt=commandBody and transcriptPrompt=transcriptCommandBody
  5. selection-ABXC-aG3.js lines ~5590-5605 resolveRuntimeContextPromptParts() — converts that into prompt:"" + runtimeOnly:true + populated runtimeSystemContext
  6. activeSession.prompt("") — submits empty user input
  7. Vertex/Gemini — rejects with 400 INVALID_ARGUMENT / Model input cannot be empty

4.26 source (still present, file renamed)

  • dist/runtime-context-prompt-BZWyPA2L.js:8-18resolveRuntimeContextPromptParts, runtimeOnly: true when transcript prompt is empty but runtime context exists
  • dist/selection-D9uTvvsw.js:7426-7431 — runtime-context prompt parts resolved + runtime-only system context applied
  • dist/selection-D9uTvvsw.js:7470-7477 — empty-prompt skip only applies when !promptSubmission.runtimeOnly
  • dist/selection-D9uTvvsw.js:7600 — runtime-only path still submits activeSession.prompt(promptSubmission.prompt) even when prompt is ""

The 4.26 code path preserves the same empty-prompt submission behavior on the runtime-only path. Bug is unchanged across 4.25 → 4.26.

Pre-condition for the bug

resolveRuntimeContextPromptParts and runtimeOnly are NEW in 4.25. Verified zero hits in all 4.24 dist for both symbols. 4.24 has no selection-* file. Maps to CHANGELOG line 188 #66496/#66814: "submit heartbeat, cron, and exec wakeups as transient runtime context instead of visible user prompts."

Other relevant CHANGELOG entries:

  • Line 512 #69079/#69278 — stop injecting heartbeat system prompt into non-heartbeat runs
  • Line 1403 #66035
  • Line 1405 #66073/#63733/#35300
  • Line 1412 #66139

Suggested fix

When effectivePrompt is non-empty but transcriptPrompt is empty, the runtime should either:

  • Submit effectivePrompt as the user prompt (preserve historical behavior — visible prompt path), OR
  • Submit a documented sentinel (e.g. "<runtime-only>") that providers like Vertex/Gemini will accept, with the actual content carried via runtimeSystemContext

The current prompt: "" submission violates the implicit contract that user prompt is non-empty.

Production impact

For agents pinned to Vertex/Gemini as their heartbeat model, every 5-min tick fails with the empty-input 400. We mitigated by swapping the heartbeat model away from Vertex/Gemini (currently OpenRouter/xiaomi/mimo-v2-pro, durable since 2026-04-27 22:11Z). Heartbeat empty-input storm stopped post-swap and has NOT reappeared on 4.26 — mitigation holds.

Workaround (current)

Set heartbeat model to a provider that tolerates empty user prompts (e.g. OpenRouter/xiaomi/mimo-v2-pro). Vertex/Gemini cannot be used as heartbeat model on 4.25 or 4.26.

extent analysis

TL;DR

Modify the resolveRuntimeContextPromptParts function to submit a non-empty user prompt, such as the effectivePrompt or a documented sentinel, when transcriptPrompt is empty.

Guidance

  • Identify the resolveRuntimeContextPromptParts function in the selection-ABXC-aG3.js file (4.25) or dist/runtime-context-prompt-BZWyPA2L.js file (4.26) and modify it to handle the case where transcriptPrompt is empty.
  • Consider submitting the effectivePrompt as the user prompt to preserve historical behavior, or submit a documented sentinel (e.g. "<runtime-only>") that providers like Vertex/Gemini will accept.
  • Verify that the modified function correctly handles the case where transcriptPrompt is empty and effectivePrompt is non-empty.
  • Test the modified function with different input scenarios to ensure it behaves as expected.

Example

// Modified resolveRuntimeContextPromptParts function
function resolveRuntimeContextPromptParts(effectivePrompt, transcriptPrompt) {
  if (transcriptPrompt === "" && effectivePrompt!== "") {
    // Submit effectivePrompt as user prompt or a documented sentinel
    return { prompt: effectivePrompt, runtimeOnly: true };
  } else {
    // Existing logic
  }
}

Notes

  • The modification should be applied to the resolveRuntimeContextPromptParts function in the relevant file (4.25 or 4.26).
  • The exact implementation details may vary depending on the specific requirements and constraints of the system.

Recommendation

Apply the workaround by setting the heartbeat model to a provider that tolerates empty user prompts (e.g. OpenRouter/xiaomi/mimo-v2-pro) until the modified function is deployed and verified to work correctly.

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…

Still need to ship something?

×6

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

Back to top recommendations

TRENDING