openclaw - 💡(How to fix) Fix Preflight compaction throws fatal on claude-cli backend when OpenClaw's own jsonl is empty (subprocess transcript lives elsewhere)

Official PRs (…)
ON THIS PAGE

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…

When using a subprocess-backed provider (specifically claude-cli), the per-session token counter in sessions.json accurately tracks the upstream claude-cli conversation, but OpenClaw never writes those messages into its own session jsonl. Once the counter crosses the preflight-compaction threshold, OpenClaw tries to compact its own (empty) jsonl, the compactor correctly reports "no real conversation messages," and the caller treats that as a fatal preflight failure — bricking outbound replies on that channel until the entry is manually popped from sessions.json.

Reproduces reliably on every active channel: confirmed today on imessage, telegram, and slack with the same claude-cli backend.

Error Message

Gateway log:

[agent/embedded] [compaction] skipping — no real conversation messages (sessionKey=agent:main:imessage:direct:nick)
[diagnostic] message dispatch completed: channel=imessage … outcome=error
  error="Error: Preflight compaction required but failed: no real conversation messages"
[channels/imessage] imessage debounce flush failed: Error: Preflight compaction required but failed: no real conversation messages

Root Cause

Root cause (from dist code trace)

Fix Action

Fix / Workaround

Gateway log:

[agent/embedded] [compaction] skipping — no real conversation messages (sessionKey=agent:main:imessage:direct:nick)
[diagnostic] message dispatch completed: channel=imessage … outcome=error
  error="Error: Preflight compaction required but failed: no real conversation messages"
[channels/imessage] imessage debounce flush failed: Error: Preflight compaction required but failed: no real conversation messages

Workaround (current)

Code Example

[agent/embedded] [compaction] skipping — no real conversation messages (sessionKey=agent:main:imessage:direct:nick)
[diagnostic] message dispatch completed: channel=imessage … outcome=error
  error="Error: Preflight compaction required but failed: no real conversation messages"
[channels/imessage] imessage debounce flush failed: Error: Preflight compaction required but failed: no real conversation messages

---

sessionId            = cd1ef450-91df-4150-81aa-1c4d86fa4ecd
sessionFile          = ~/.openclaw/agents/main/sessions/cd1ef450-…jsonl     (155 bytes — header only)
claudeCliSessionId   = f54a61c3-f7a0-49f4-8c49-9a81a5249a6f
~/.claude/projects/<slug>/f54a61c3-…jsonl           (329 KB — real conversation)
totalTokens          = 1093276
totalTokensFresh     = true
contextTokens        = 1048576
status               = done

---

if (!(shouldRunPreflightCompaction({
    entry, tokenCount: tokenCountForCompaction,
    contextWindowTokens, reserveTokensFloor, softThresholdTokens
}) || shouldCompactByTranscriptBytes)) return entry ?? params.sessionEntry;
const result = await memoryDeps.compactEmbeddedPiSession({
    sessionId: entry.sessionId,
    sessionFile: sessionFile ?? params.followupRun.run.sessionFile,
});
if (!result?.ok || !result.compacted) {
    const reason = result?.reason ?? "not_compacted";
    throw new Error(`Preflight compaction required but failed: ${reason}`);
}

---

if (!containsRealConversationMessages(session.messages)) {
    log.info(`[compaction] skipping — no real conversation messages (sessionKey=…)`);
    return { ok: true, compacted: false, reason: "no real conversation messages" };
}

---

import json
p = "~/.openclaw/agents/main/sessions/sessions.json"
data = json.load(open(p))
data.pop("agent:main:imessage:direct:nick", None)  # or whichever channel
json.dump(data, open(p, "w"))
RAW_BUFFERClick to expand / collapse

Summary

When using a subprocess-backed provider (specifically claude-cli), the per-session token counter in sessions.json accurately tracks the upstream claude-cli conversation, but OpenClaw never writes those messages into its own session jsonl. Once the counter crosses the preflight-compaction threshold, OpenClaw tries to compact its own (empty) jsonl, the compactor correctly reports "no real conversation messages," and the caller treats that as a fatal preflight failure — bricking outbound replies on that channel until the entry is manually popped from sessions.json.

Reproduces reliably on every active channel: confirmed today on imessage, telegram, and slack with the same claude-cli backend.

Versions

  • OpenClaw 2026.5.22 (commit a374c3a), Homebrew install on macOS Darwin 25.3.0
  • Provider: claude-cli (Claude Pro OAuth), model claude-opus-4-7
  • 1M context window (contextTokens: 1048576)

Reproduction

  1. Configure agent with claude-cli provider on any messaging channel (iMessage / Slack / Telegram all reproduce).
  2. Have a long conversation that accumulates totalTokens near the 1M threshold. (claude-cli's own session file at ~/.claude/projects/<slug>/<sessionId>.jsonl grows normally.)
  3. Next inbound message triggers preflightCompaction.
  4. Outbound delivery dies with Preflight compaction required but failed: no real conversation messages. Inbound keeps being accepted; nothing recovers without manual intervention.

Observed behavior

Gateway log:

[agent/embedded] [compaction] skipping — no real conversation messages (sessionKey=agent:main:imessage:direct:nick)
[diagnostic] message dispatch completed: channel=imessage … outcome=error
  error="Error: Preflight compaction required but failed: no real conversation messages"
[channels/imessage] imessage debounce flush failed: Error: Preflight compaction required but failed: no real conversation messages

Session entry at time of failure (~/.openclaw/agents/main/sessions/sessions.json):

sessionId            = cd1ef450-91df-4150-81aa-1c4d86fa4ecd
sessionFile          = ~/.openclaw/agents/main/sessions/cd1ef450-…jsonl     (155 bytes — header only)
claudeCliSessionId   = f54a61c3-f7a0-49f4-8c49-9a81a5249a6f
                      → ~/.claude/projects/<slug>/f54a61c3-…jsonl           (329 KB — real conversation)
totalTokens          = 1093276
totalTokensFresh     = true
contextTokens        = 1048576
status               = done

The mismatch is the bug: the token counter is sourced from claude-cli (accurate), but the file the compactor opens is OpenClaw's own jsonl, which only ever contains the version-3 header line.

Root cause (from dist code trace)

dist/agent-runner.runtime-*.js around line 605–650:

if (!(shouldRunPreflightCompaction({
    entry, tokenCount: tokenCountForCompaction,
    contextWindowTokens, reserveTokensFloor, softThresholdTokens
}) || shouldCompactByTranscriptBytes)) return entry ?? params.sessionEntry;
const result = await memoryDeps.compactEmbeddedPiSession({
    sessionId: entry.sessionId,
    sessionFile: sessionFile ?? params.followupRun.run.sessionFile,
});
if (!result?.ok || !result.compacted) {
    const reason = result?.reason ?? "not_compacted";
    throw new Error(`Preflight compaction required but failed: ${reason}`);
}

dist/compact-*.js around line 964:

if (!containsRealConversationMessages(session.messages)) {
    log.info(`[compaction] skipping — no real conversation messages (sessionKey=…)`);
    return { ok: true, compacted: false, reason: "no real conversation messages" };
}

So compactEmbeddedPiSession returns ok:true, compacted:false as a benign skip — and the caller in agent-runner converts that benign skip into a fatal throw. That conversion is correct for backends that own their own transcript, but wrong for subprocess backends whose transcripts live elsewhere.

Suggested fixes (either or both)

  1. Treat "no real conversation messages" as a soft skip, not a fatal failure. In agent-runner.runtime, when result.ok && !result.compacted && result.reason === "no real conversation messages", log a warning and proceed with the reply rather than throwing. This restores the obvious intent of the compactor returning ok:true.

  2. For subprocess backends (claude-cli), point preflight compaction at the upstream session jsonl (e.g. ~/.claude/projects/<slug>/<claudeCliSessionId>.jsonl for claude-cli) so the compactor has actual messages to operate on, or skip OpenClaw-side preflight entirely since claude-cli runs its own compaction internally.

(2) is the more correct fix; (1) is a one-line safety net that would have prevented the multi-hour iMessage outages we've been triaging tonight.

Workaround (current)

Manually remove the stuck entry, then the next inbound message rebuilds a fresh entry:

import json
p = "~/.openclaw/agents/main/sessions/sessions.json"
data = json.load(open(p))
data.pop("agent:main:imessage:direct:nick", None)  # or whichever channel
json.dump(data, open(p, "w"))

We're running a 1-minute cron watchdog that tails gateway.log for the error and auto-pops the stuck sessionKey. Happy to share that script if useful.

Impact

This silently kills outbound replies on every long-running conversational channel using claude-cli. Inbound keeps logging "delivered to agent" so monitoring looks healthy; the user just doesn't get replies. Recurs cleanly every time the token counter rebuilds past threshold — ~10 minutes of active use per cycle in our case.

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

openclaw - 💡(How to fix) Fix Preflight compaction throws fatal on claude-cli backend when OpenClaw's own jsonl is empty (subprocess transcript lives elsewhere)