openclaw - ✅(Solved) Fix [Bug]: Fresh-session `[object Object]` hallucination persists after session/database wipe on fix branch for #69079 [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#70734Fetched 2026-04-24 05:54:15
View on GitHub
Comments
1
Participants
2
Timeline
4
Reactions
0
Timeline (top)
labeled ×2commented ×1cross-referenced ×1

After building from source on the fix/issue-69079-heartbeat-user-turn-leak branch (PR #69278, commit f8b7862) to get the heartbeat trigger-policy fix, the main agent no longer returns HEARTBEAT_OK on user turns. Good. However, every user turn now produces a hallucinated reply about [object Object] serialization errors, as if the agent is continuing a prior support conversation. This persists even after:

Quarantining all 150 session JSONL files containing the [object Object] pattern Deleting ~/.openclaw/tasks/runs.sqlite and letting it regenerate fresh Using --session-id <random> to create sessions that had never existed before Restarting the gateway cleanly between each test

The hallucinated reply text appears on the first user message of a brand-new session, with no prior turns. The message in the fresh session file on disk shows only the clean user turn and the hallucinated assistant turn, nothing injected between them.

Error Message

Separate build error discovered during this test (worth filing as its own issue if not already known):

Root Cause

Severity: High for users affected, low if rare. Impact for affected users: the main agent is effectively unusable for interactive chat. Every user turn produces hallucinated support-narrative output unrelated to the actual input. CLI, Telegram, Discord, and Mission Control all route through the same broken path. Cron agents are unaffected because they use the cron trigger which was always excluded from the heartbeat prompt injection. Workaround: none found. Reverting to the pre-fix release does not help because the pre-fix release still has the original heartbeat bug from #69079.

Fix Action

Fix / Workaround

Severity: High for users affected, low if rare. Impact for affected users: the main agent is effectively unusable for interactive chat. Every user turn produces hallucinated support-narrative output unrelated to the actual input. CLI, Telegram, Discord, and Mission Control all route through the same broken path. Cron agents are unaffected because they use the cron trigger which was always excluded from the heartbeat prompt injection. Workaround: none found. Reverting to the pre-fix release does not help because the pre-fix release still has the original heartbeat bug from #69079.

Separate build error discovered during this test (worth filing as its own issue if not already known): First build attempt with standalone pnpm from get.pnpm.io/install.sh failed at canvas:a2ui:bundle with SyntaxError: Invalid or unexpected token. Root cause: scripts/pnpm-runner.mjs uses isPnpmExecPath() to check npm_execpath. When pnpm is a compiled standalone binary rather than a JS entry point, the regex matches on the filename pnpm and the runner invokes Node against the binary. Node chokes parsing the binary as JavaScript. Workaround: install pnpm via Corepack, which provides a JS entry point. Backup state retained: I kept the following local backups in case maintainers want specific content to reproduce or inspect:

PR fix notes

PR #69278: fix(agents): stop injecting heartbeat system prompt on non-heartbeat runs (#69079)

Description (problem / solution / changelog)

Summary

  • Problem: On a fresh session, the main agent does not respond to user messages. Two reproducible failure modes: (1) the agent replies with the literal string HEARTBEAT_OK, which the delivery runtime treats as "no reply" and sends nothing to the channel (user sees silence); (2) with HEARTBEAT references stripped from AGENTS.md, the agent fabricates "Looks like your message came through as \[object Object]`..."` — a hallucinated serialization error that does not exist anywhere in OpenClaw or the user's actual payload.
  • Why it matters: The main agent is completely unresponsive on the first turn of a fresh session. Reproduces via openclaw agent --agent main -m "..." (bypasses all channel plugins), via Telegram DM, on both Sonnet 4 and Opus 4.7, on 2026.4.15 and 2026.4.19-beta.2.
  • What changed: Flip the embedded-run trigger policy. The heartbeat system prompt (which instructs the model to reply exactly HEARTBEAT_OK when nothing needs attention) now opts in on the heartbeat trigger only, rather than opting out on cron only and defaulting on for everything else. User, manual, memory, and overflow triggers — plus callers that pass no trigger — no longer inject heartbeat instructions into the system prompt.
  • What did NOT change (scope boundary): Heartbeat runs themselves still get the heartbeat prompt (they need it). The HEARTBEAT_OK token filter in the delivery pipeline is untouched. HEARTBEAT.md parsing, heartbeat cron scheduling, and the resolveHeartbeatPromptForSystemPrompt helper are all unchanged.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • Gateway / orchestration

Linked Issue/PR

  • Closes #69079
  • Related: still-open parent #50797 (same symptom class, surfaced on the gateway-poll/user-message race; this fix addresses the underlying trigger policy).
  • This PR fixes a bug or regression

Root Cause

  • Root cause: src/agents/pi-embedded-runner/run/trigger-policy.ts set DEFAULT_EMBEDDED_RUN_TRIGGER_POLICY.injectHeartbeatPrompt = true and only excluded cron via an explicit entry. Every other trigger (user, heartbeat, manual, memory, overflow) fell through to the default-true path. Callers with trigger: "user" (channels + openclaw agent CLI) therefore got the heartbeat system prompt block injected at src/agents/pi-embedded-runner/run/attempt.ts:733, which includes the instruction "If the current user message is a heartbeat poll and nothing needs attention, reply exactly: HEARTBEAT_OK". Smaller or weaker models (or any model under prompt pressure) fail the conditional and pattern-match on the directive, producing the literal HEARTBEAT_OK output on real user turns. When that instruction is not obviously reachable (HEARTBEAT stripped from AGENTS.md), the same confusion surfaces as the fabricated [object Object] explanation — "object Object" does not exist anywhere in the source tree (verified via grep), confirming the model is hallucinating it while trying to reconcile the heartbeat context with a non-heartbeat message.
  • Missing detection / guardrail: no test existed for shouldInjectHeartbeatPromptForTrigger across the full trigger enum. Existing coverage only exercised cron (excluded) and heartbeat / user indirectly via higher-level tests that did not assert the prompt-injection decision.
  • Contributing context: the policy shape implied opt-out semantics ("list the triggers that should NOT inject"), which is backwards for this kind of feature — only one trigger should opt in. The original intent was likely "heartbeat only," but the implementation was the inverse.

Regression Test Plan

  • Coverage level that should have caught this:
    • Unit test
  • Target test or file: new src/agents/pi-embedded-runner/run/trigger-policy.test.ts
  • Scenario the test should lock in: shouldInjectHeartbeatPromptForTrigger("heartbeat") returns true; every other trigger (user, manual, cron, memory, overflow) returns false; absent trigger returns false.
  • Why this is the smallest reliable guardrail: the bug is entirely a single boolean decision on a single helper. Asserting the helper's output across the full EmbeddedRunTrigger union is the cheapest, most direct regression lock. Running via attempt.test.ts (106 tests) verifies no downstream behavior breaks.

User-visible / Behavior Changes

Main-agent user turns no longer see the heartbeat system prompt block. Users who reported silent sessions or fabricated [object Object] explanations should get normal replies. Heartbeat-triggered polls continue to work the same way.

Diagram

Before:
user message via channel or CLI
  → trigger = "user"
  → shouldInjectHeartbeatPromptForTrigger("user") = true (default)
  → system prompt includes "reply HEARTBEAT_OK when nothing needs attention"
  → model sees heartbeat instructions on a real user turn
  → outputs literal "HEARTBEAT_OK" (delivery treats as no-reply → silence)
  OR fabricates "[object Object]" explanation (hallucinated reconciliation)

After:
user message via channel or CLI
  → trigger = "user"
  → shouldInjectHeartbeatPromptForTrigger("user") = false (default is off)
  → heartbeat block omitted; model sees a clean user-turn system prompt
  → normal reply

heartbeat poll
  → trigger = "heartbeat"
  → shouldInjectHeartbeatPromptForTrigger("heartbeat") = true (explicit opt-in)
  → heartbeat block included, HEARTBEAT_OK semantics preserved

Security Impact

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: macOS 15 (reporter), reproducible on any platform
  • Runtime: OpenClaw 2026.4.15 and 2026.4.19-beta.2
  • Model/provider: claude-max via claude-max-api-proxy (openai-completions API). Also reproduces on Sonnet 4 and Opus 4.7 via stock Anthropic routing.
  • Integration: CLI (openclaw agent --agent main -m ...) and Telegram DM

Steps

  1. Configure the main agent with standard workspace files (SOUL.md, AGENTS.md including heartbeat references, etc.).
  2. Run openclaw agent --agent main -m "Pierce here. Status check on project X." --json and grep finalAssistantVisibleText.

Expected

Model returns an actual reply to the user's content.

Actual (before fix)

finalAssistantVisibleText": "HEARTBEAT_OK" or finalAssistantVisibleText": "Looks like your message came through as \[object Object]`..."`. Session jsonl confirms the user's message arrived intact; the model is emitting HEARTBEAT_OK or hallucinating the serialization error.

Evidence

  • Failing test/log before + passing after

New test src/agents/pi-embedded-runner/run/trigger-policy.test.ts (7 cases) would have failed on current main for every non-heartbeat trigger. Passes after this change.

Human Verification

  • Verified scenarios:
    • pnpm tsgo clean
    • pnpm test src/agents/pi-embedded-runner/run/trigger-policy.test.ts — 7 pass
    • pnpm test src/agents/pi-embedded-runner/run/attempt.test.ts — 106 pass (no regression in downstream attempt logic)
    • pnpm test src/agents/pi-embedded-runner/run/attempt.prompt-helpers.test.ts — 2 pass
    • pnpm test src/auto-reply/reply/session.heartbeat-no-reset.test.ts — 5 pass (heartbeat-reset behavior untouched)
    • pnpm oxlint clean on both changed files
  • Edge cases checked:
    • Heartbeat trigger still gets the prompt (explicit opt-in).
    • Cron still does not get the prompt (was explicit exclusion, now falls through the safe default — same result, cleaner policy).
    • Callers without a trigger (defensive): now safely default off rather than silently injecting heartbeat instructions.
    • resolveHeartbeatPromptForSystemPrompt itself is untouched; the helper's output is still wired into heartbeat turns.
  • What I did NOT verify:
    • Live repro against claude-max-api-proxy. The unit-level assertion on the trigger policy + the 106 passing attempt tests is sufficient evidence that the reported symptom is gone.

Compatibility / Migration

  • Backward compatible? Yes — user-facing behavior improves (agent stops hallucinating heartbeat acks on user turns). Heartbeat semantics unchanged.
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: some tests or harnesses might construct embedded runs without a trigger and rely on the old default-true behavior.
    • Mitigation: grepped all call sites of shouldInjectHeartbeatPromptForTrigger and shouldInjectHeartbeatPrompt (the wrapper at attempt.prompt-helpers.ts:98). The only call site is attempt.ts:733, and all runtime callers pass an explicit trigger. The 106-test attempt.test.ts suite still passes, including cases with trigger: "user" and trigger: "heartbeat".
  • Risk: removing the explicit cron entry loses documentation of the intended cron behavior.
    • Mitigation: added a block comment explaining the policy — heartbeat only opts in; everything else defaults off. Cron's behavior is preserved through the new default.

Changed files

  • src/agents/pi-embedded-runner/run/trigger-policy.test.ts (added, +32/-0)
  • src/agents/pi-embedded-runner/run/trigger-policy.ts (modified, +11/-3)

Code Example

Gateway startup log excerpt
2026-04-23T11:15:20.028-07:00 [gateway] signal SIGTERM received
2026-04-23T11:15:20.031-07:00 [gateway] received SIGTERM; shutting down
2026-04-23T11:15:24.727-07:00 [gateway] loading configuration…
2026-04-23T11:15:24.790-07:00 [gateway] resolving authentication…
2026-04-23T11:15:24.802-07:00 [gateway] starting...
Repeated stderr warning after downgrade
Config was last written by a newer OpenClaw (2026.4.19-beta.2); current version is 2026.4.14.
Session file content after "hello" test
Raw JSONL from ~/.openclaw/agents/main/sessions/<uuid>.jsonl:
[4] msg role=user: [{'type': 'text', 'text': '[Thu 2026-04-23 11:27 PDT] hello'}]
[5] msg role=assistant: [{'type': 'text', 'text': "Looks like something didn't serialize properly on your end — got `[object Object]` instead of actual content. What were you trying to send?"}]
Database state before wipe

~/.openclaw/tasks/runs.sqlite had 673 rows in task_runs
646 of those rows contained [object Object] in progress_summary or terminal_summary fields
Most were labeled ORACLE Drift Check (cron agent runs)

What was tried before filing

Quarantined 150 session JSONL files containing [object Object] to ~/.openclaw/agents/main/sessions/_quarantine_20260423/
Stopped gateway, backed up and deleted ~/.openclaw/tasks/runs.sqlite*, restarted gateway, database recreated fresh
Used --session-id <random-timestamp> to force never-before-seen session keys
Verified AGENTS.md, MEMORY.md, SOUL.md, HEARTBEAT.md, and daily memory files contain no [object Object], no HEARTBEAT_OK, no webchat beyond one innocuous line in AGENTS.md listing DM channels

The hallucination persisted through all of the above.
RAW_BUFFERClick to expand / collapse

Bug type

Regression (worked before, now fails)

Beta release blocker

No

Summary

After building from source on the fix/issue-69079-heartbeat-user-turn-leak branch (PR #69278, commit f8b7862) to get the heartbeat trigger-policy fix, the main agent no longer returns HEARTBEAT_OK on user turns. Good. However, every user turn now produces a hallucinated reply about [object Object] serialization errors, as if the agent is continuing a prior support conversation. This persists even after:

Quarantining all 150 session JSONL files containing the [object Object] pattern Deleting ~/.openclaw/tasks/runs.sqlite and letting it regenerate fresh Using --session-id <random> to create sessions that had never existed before Restarting the gateway cleanly between each test

The hallucinated reply text appears on the first user message of a brand-new session, with no prior turns. The message in the fresh session file on disk shows only the clean user turn and the hallucinated assistant turn, nothing injected between them.

Steps to reproduce

Clone stainlu/openclaw branch fix/issue-69079-heartbeat-user-turn-leak (commit f8b78629a1b94de05b64fa27b4c7eed6fa48bac5, the fix branch for PR #69278) Install dependencies with pnpm install and build with pnpm build (Corepack-managed pnpm required, see note in report) Modify launchd plist ai.openclaw.gateway.plist to point at the built entry point openclaw.mjs instead of the globally installed dist/index.js Bootstrap the gateway: launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist Confirm the gateway is live: curl -s http://127.0.0.1:18789/health returns {"ok":true,"status":"live"} Send a trivial user message via CLI:

node ~/src/openclaw-fix/openclaw.mjs agent --agent main -m "hello"

Expected behavior

The agent replies contextually to the word "hello" with something like a greeting or an offer to help.

Actual behavior

The agent replies as if continuing a prior support conversation about JavaScript serialization errors, with text such as:

Still getting the same issue. Whatever you're trying to send isn't being serialized to text before it hits the chat. Try typing your message directly into the input field and hitting send. If you're copying/pasting from somewhere, try typing it out manually instead.

This occurs on the first user turn of a brand-new session. The session file on disk shows only the clean user message and the hallucinated assistant response, nothing injected between them. The heartbeat fix itself from PR #69278 does work. No more HEARTBEAT_OK leaking into user turns. The bug described here is a separate behavior that showed up only after we installed the fix branch and started sending fresh user messages.

OpenClaw version

Reproduced on: OpenClaw 2026.4.14 built from source at commit f8b7862 on the fix/issue-69079-heartbeat-user-turn-leak branch Previous version running locally: OpenClaw 2026.4.19-beta.2 from Homebrew

Operating system

macOS 26.x (arm64, Mac Mini M4 Pro, 48GB RAM)

Install method

Source install via git clone plus pnpm install && pnpm build, launchd service pointed at the built openclaw.mjs. Previously installed via Homebrew. Reverted to Homebrew after the test.

Model

claude-sonnet-4 (confirmed in session metadata: "provider": "claude-max", "model": "claude-sonnet-4")

Provider / routing chain

claude-max (Anthropic, via OpenClaw's claude-max provider integration)

Additional provider/model setup details

Custom Node.js translator at ~/claude-translator/translator.js on port 3457 bridges Anthropic Messages API to OpenAI chat completions format openclaw.json config sets baseUrl to http://localhost:3457 and api to anthropic-messages This translator setup was in place and working correctly before the bug appeared Bootstrap context configuration: bootstrapMaxChars: 20000, bootstrapTotalMaxChars: 150000

Logs, screenshots, and evidence

Gateway startup log excerpt
2026-04-23T11:15:20.028-07:00 [gateway] signal SIGTERM received
2026-04-23T11:15:20.031-07:00 [gateway] received SIGTERM; shutting down
2026-04-23T11:15:24.727-07:00 [gateway] loading configuration…
2026-04-23T11:15:24.790-07:00 [gateway] resolving authentication…
2026-04-23T11:15:24.802-07:00 [gateway] starting...
Repeated stderr warning after downgrade
Config was last written by a newer OpenClaw (2026.4.19-beta.2); current version is 2026.4.14.
Session file content after "hello" test
Raw JSONL from ~/.openclaw/agents/main/sessions/<uuid>.jsonl:
[4] msg role=user: [{'type': 'text', 'text': '[Thu 2026-04-23 11:27 PDT] hello'}]
[5] msg role=assistant: [{'type': 'text', 'text': "Looks like something didn't serialize properly on your end — got `[object Object]` instead of actual content. What were you trying to send?"}]
Database state before wipe

~/.openclaw/tasks/runs.sqlite had 673 rows in task_runs
646 of those rows contained [object Object] in progress_summary or terminal_summary fields
Most were labeled ORACLE Drift Check (cron agent runs)

What was tried before filing

Quarantined 150 session JSONL files containing [object Object] to ~/.openclaw/agents/main/sessions/_quarantine_20260423/
Stopped gateway, backed up and deleted ~/.openclaw/tasks/runs.sqlite*, restarted gateway, database recreated fresh
Used --session-id <random-timestamp> to force never-before-seen session keys
Verified AGENTS.md, MEMORY.md, SOUL.md, HEARTBEAT.md, and daily memory files contain no [object Object], no HEARTBEAT_OK, no webchat beyond one innocuous line in AGENTS.md listing DM channels

The hallucination persisted through all of the above.

Impact and severity

Severity: High for users affected, low if rare. Impact for affected users: the main agent is effectively unusable for interactive chat. Every user turn produces hallucinated support-narrative output unrelated to the actual input. CLI, Telegram, Discord, and Mission Control all route through the same broken path. Cron agents are unaffected because they use the cron trigger which was always excluded from the heartbeat prompt injection. Workaround: none found. Reverting to the pre-fix release does not help because the pre-fix release still has the original heartbeat bug from #69079.

Additional information

What we could not rule out:

Bootstrap context injection from a source we did not identify. Something is prepended at session start that primes the model to hallucinate about [object Object] webchat bugs. This content is not saved to the session transcript. Memory recall from locations outside the primary agent sessions directory. In-memory gateway state that survives task database deletion but was perhaps not fully cleared even by gateway restart.

Debugging aids that would help:

A config flag or debug command to dump the full composed prompt (system, bootstrap, recalled memory, user message) exactly as sent to the provider for one request Documentation of all locations the gateway reads from when composing the fresh-session prompt, so users can audit systematically

Separate build error discovered during this test (worth filing as its own issue if not already known): First build attempt with standalone pnpm from get.pnpm.io/install.sh failed at canvas:a2ui:bundle with SyntaxError: Invalid or unexpected token. Root cause: scripts/pnpm-runner.mjs uses isPnpmExecPath() to check npm_execpath. When pnpm is a compiled standalone binary rather than a JS entry point, the regex matches on the filename pnpm and the runner invokes Node against the binary. Node chokes parsing the binary as JavaScript. Workaround: install pnpm via Corepack, which provides a JS entry point. Backup state retained: I kept the following local backups in case maintainers want specific content to reproduce or inspect:

~/.openclaw/_backups/runs.sqlite.bak.* (pre-wipe task database, full 673 rows including 646 contaminated) ~/.openclaw/agents/main/sessions/_quarantine_20260423/ (150 quarantined session files) ~/.openclaw/agents/main/sessions/0ca55a2d-*.jsonl.contaminated.2026-04-23 (the original contaminated main session) ~/Library/LaunchAgents/ai.openclaw.gateway.plist.bak.BEFORE-LOCAL-BUILD (pre-edit launchd config)

Related:

PR #69278 (the heartbeat fix we installed) Issue #69079 (the original heartbeat bug) Greptile confidence 5/5 on PR #69278

extent analysis

TL;DR

The most likely fix for the hallucinated reply issue is to identify and remove the source of the bootstrap context injection that primes the model to hallucinate about [object Object] webchat bugs.

Guidance

  • Investigate the custom Node.js translator at ~/claude-translator/translator.js to ensure it's not introducing any unexpected data that could be causing the hallucination.
  • Review the openclaw.json config file to verify that the baseUrl and api settings are correct and not contributing to the issue.
  • Use the --session-id <random> flag to create a new session and verify if the hallucination persists, which could help determine if the issue is related to session-specific data.
  • Consider adding a debug flag or command to dump the full composed prompt sent to the provider, as suggested in the issue, to gain more insight into the issue.

Example

No specific code example is provided, as the issue is more related to configuration and data flow.

Notes

The issue seems to be related to the introduction of the heartbeat fix, but it's not clear if it's a direct result of the fix or a separate issue that was uncovered. Further investigation is needed to determine the root cause.

Recommendation

Apply a workaround by trying to identify and remove the source of the bootstrap context injection, as this seems to be the most likely cause of the hallucination. If this is not possible, consider reverting to a previous version of the code and waiting for a more permanent fix.

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 agent replies contextually to the word "hello" with something like a greeting or an offer to help.

Still need to ship something?

×6

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

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [Bug]: Fresh-session `[object Object]` hallucination persists after session/database wipe on fix branch for #69079 [1 pull requests, 1 comments, 2 participants]