openclaw - ✅(Solved) Fix [Bug]: Compaction safeguard mode produces empty fallback summaries that loop until 60s aggregate timeout [1 pull requests, 2 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#78300Fetched 2026-05-07 03:38:34
View on GitHub
Comments
2
Participants
2
Timeline
7
Reactions
2
Author
Timeline (top)
commented ×2cross-referenced ×2labeled ×2closed ×1

When agents.defaults.compaction.mode = "safeguard", certain sessions repeatedly trigger the safeguard's "no real conversation messages" branch and write a 144-char fallback summary (## Decisions\nNo prior history.\n...). The actual compaction LLM call then runs in parallel but is canceled after the 60s COMPACTION_RETRY_AGGREGATE_TIMEOUT_MS, and the runtime falls back to a "pre-compaction snapshot" — meaning the transcript still has full size, but a placeholder compaction marker has been recorded. On the next turn the same session re-enters the same path, creating a loop of 60s-stalled turns with empty summaries.

Switching to mode: "default" makes the symptom go away (no safeguard hook → no empty-fallback writes).

The sessions where this triggers are real working sessions with substantive user and assistant messages plus tool calls. A simulation replay of isRealConversationMessage on the same transcript reports 14/108 "real" messages — well above the threshold the safeguard's if (!hasRealSummarizable && !hasRealTurnPrefix) check should require — so something in the message set passed to the safeguard hook is being seen as "no real conversation" even though the file-on-disk transcript clearly contains real conversation.

This affects:

  • Cron-triggered sessions (e.g. daily-report cron at 38K tokens)
  • Newly-reset interactive sessions (/new flow)
  • Long DM sessions

I have not yet identified the upstream commit that introduced the regression — containsRealConversationMessages and the 60s aggregate timer existed in 2026.4.23 (still ran fine for me there), so the divergence likely lives in how messagesToSummarize is constructed for the session_before_compact event. Happy to dig further if maintainers point at the right area.

Root Cause

  1. Run OpenClaw 2026.5.3-1 with agents.defaults.compaction.mode: "safeguard" and Anthropic-style provider (modelsproxy → claude-opus-4-7 in my case)
  2. Either:
    • Trigger a cron task that does a few read / exec tool calls (the daily report pipeline I have hits this every morning), OR
    • Start a /new session that does a few rounds of investigation (read files, run exec, etc.)
  3. Wait until preflight compaction triggers (this happens well before context limit because of truncateAfterCompaction being false in my config — I am NOT setting maxActiveTranscriptBytes)
  4. Watch the gateway logs

Fix Action

Fix / Workaround

Workaround that resolved my issue

 "compaction": {
-  "mode": "safeguard"
+  "mode": "default"
 }

After systemctl --user restart openclaw-gateway no further [compaction-safeguard] log lines appear.

PR fix notes

PR #78390: fix: recognize custom compaction conversation

Description (problem / solution / changelog)

Summary

  • Fixes #78300: safeguard-mode compaction could write the empty No prior history boundary for custom-message/split-turn sessions that had real custom prompt/tool work.
  • Treat visible custom, bashExecution, and branchSummary messages as real conversation anchors, including nearby tool results.
  • Adds a production-shape fallback for cases where Pi's compaction preparation omits the real custom-message content: the safeguard inspects the live session branch before deciding to write an empty boundary.
  • Adds a changelog entry credited to @amknight.

Verification

  • Local pre-fix targeted repro: failed with empty No prior history fallback.
  • Local targeted tests: pnpm test src/agents/pi-hooks/compaction-safeguard.test.ts src/agents/pi-embedded-runner/compact.hooks.test.ts (128 tests passed).
  • Local formatting/checks: git diff --check; pnpm exec oxfmt --check --threads=1 src/agents/compaction-real-conversation.ts src/agents/pi-hooks/compaction-safeguard.ts src/agents/pi-hooks/compaction-safeguard.test.ts src/agents/pi-embedded-runner/compact.hooks.test.ts.
  • Crabbox pre-fix repro: tbx_01kqy8jxhem91xbrtva6bgbsm8 failed with empty fallback when classifier behavior was restored to HEAD.
  • Crabbox CLI proof: tbx_01kqybhp2c6517haqm3rgwkaqy ran node scripts/run-node.mjs agent --local --agent main --session-id issue78300-cli --model fake/fake-model --message ... --json --timeout 60 against a local fake OpenAI-compatible provider. Assertions passed: providerCalls=1, compactionCount=2, hasEmptyFallback=false, summaryHasCliOk=true.
  • Crabbox final changed gate on latest pushed branch: tbx_01kqybn9ymet502k8wjw9smkqp ran pnpm check:changed with exit 0.

Security Impact

  • New permissions/capabilities: No
  • Secrets/tokens handling changed: No
  • New/changed network calls: No
  • Command/tool execution surface changed: No

Notes

The CLI proof deliberately uses a tiny fake model context to force OpenClaw's real CLI compaction path. The final agent payload still reports context overflow after the successful compaction because the synthetic transcript remains too large for the fake model; the regression assertion is that compaction no longer writes the empty fallback summary.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/agents/compaction-real-conversation.ts (modified, +45/-3)
  • src/agents/pi-embedded-runner/compact.hooks.test.ts (modified, +24/-0)
  • src/agents/pi-hooks/compaction-safeguard.test.ts (modified, +132/-0)
  • src/agents/pi-hooks/compaction-safeguard.ts (modified, +97/-8)

Code Example

[compaction-safeguard] Compaction safeguard: no real conversation messages to summarize; writing compaction boundary to suppress re-trigger loop.
[agent/embedded] compaction retry aggregate timeout (60000ms): proceeding with pre-compaction state runId=... sessionId=...
[agent/embedded] using pre-compaction snapshot: timed out during compaction runId=... sessionId=...

---

{"type":"compaction","summary":"## Decisions\nNo prior history.\n\n## Open TODOs\nNone.\n\n## Constraints/Rules\nNone.\n\n## Pending user asks\nNone.\n\n## Exact identifiers\nNone captured.","tokensBefore":38085,"fromHook":true}

---

May 06 08:42:16 ... [compaction-safeguard] Compaction safeguard: no real conversation messages to summarize; writing compaction boundary to suppress re-trigger loop.
May 06 08:43:16 ... [agent/embedded] compaction retry aggregate timeout (60000ms): proceeding with pre-compaction state runId=abf1edff-9629-4b4b-9168-fe4ec4d1c41a sessionId=abf1edff-9629-4b4b-9168-fe4ec4d1c41a
May 06 08:43:16 ... [agent/embedded] using pre-compaction snapshot: timed out during compaction runId=abf1edff-9629-4b4b-9168-fe4ec4d1c41a sessionId=abf1edff-9629-4b4b-9168-fe4ec4d1c41a

---

{
  "type": "compaction",
  "id": "17c34363",
  "parentId": "3aa68b51",
  "timestamp": "2026-05-06T00:42:16.544Z",
  "summary": "## Decisions\nNo prior history.\n\n## Open TODOs\nNone.\n\n## Constraints/Rules\nNone.\n\n## Pending user asks\nNone.\n\n## Exact identifiers\nNone captured.",
  "firstKeptEntryId": "1e91a7ec",
  "tokensBefore": 38085,
  "fromHook": true
}

---

"compaction": {
-  "mode": "safeguard"
+  "mode": "default"
 }

---

if (!hasRealSummarizable && !hasRealTurnPrefix) {
    log.info("Compaction safeguard: no real conversation messages to summarize; writing compaction boundary to suppress re-trigger loop.");
    return { compaction: {
        summary: buildStructuredFallbackSummary(preparation.previousSummary),
        firstKeptEntryId: preparation.firstKeptEntryId,
        tokensBefore: preparation.tokensBefore
    } };
}
RAW_BUFFERClick to expand / collapse

Bug type

Regression (worked before, now fails)

Beta release blocker

No

Summary

When agents.defaults.compaction.mode = "safeguard", certain sessions repeatedly trigger the safeguard's "no real conversation messages" branch and write a 144-char fallback summary (## Decisions\nNo prior history.\n...). The actual compaction LLM call then runs in parallel but is canceled after the 60s COMPACTION_RETRY_AGGREGATE_TIMEOUT_MS, and the runtime falls back to a "pre-compaction snapshot" — meaning the transcript still has full size, but a placeholder compaction marker has been recorded. On the next turn the same session re-enters the same path, creating a loop of 60s-stalled turns with empty summaries.

Switching to mode: "default" makes the symptom go away (no safeguard hook → no empty-fallback writes).

The sessions where this triggers are real working sessions with substantive user and assistant messages plus tool calls. A simulation replay of isRealConversationMessage on the same transcript reports 14/108 "real" messages — well above the threshold the safeguard's if (!hasRealSummarizable && !hasRealTurnPrefix) check should require — so something in the message set passed to the safeguard hook is being seen as "no real conversation" even though the file-on-disk transcript clearly contains real conversation.

This affects:

  • Cron-triggered sessions (e.g. daily-report cron at 38K tokens)
  • Newly-reset interactive sessions (/new flow)
  • Long DM sessions

I have not yet identified the upstream commit that introduced the regression — containsRealConversationMessages and the 60s aggregate timer existed in 2026.4.23 (still ran fine for me there), so the divergence likely lives in how messagesToSummarize is constructed for the session_before_compact event. Happy to dig further if maintainers point at the right area.

Steps to reproduce

  1. Run OpenClaw 2026.5.3-1 with agents.defaults.compaction.mode: "safeguard" and Anthropic-style provider (modelsproxy → claude-opus-4-7 in my case)
  2. Either:
    • Trigger a cron task that does a few read / exec tool calls (the daily report pipeline I have hits this every morning), OR
    • Start a /new session that does a few rounds of investigation (read files, run exec, etc.)
  3. Wait until preflight compaction triggers (this happens well before context limit because of truncateAfterCompaction being false in my config — I am NOT setting maxActiveTranscriptBytes)
  4. Watch the gateway logs

Expected: compaction summary captures the conversation so far. Actual: 144-char fallback summary; 60s aggregate timeout; pre-compaction snapshot used; same session re-triggers compaction on the next turn.

Expected behavior

Either:

  1. Fix root cause — the safeguard hook should agree with the underlying transcript on whether real conversation exists. Real cron / interactive sessions with user prompts + assistant tool-using turns should always summarize, not fall through to "No prior history".
  2. Surface the gap — if the safeguard intentionally treats some message classes as non-conversation (e.g. all toolCall blocks with empty assistant text), the fallback summary should at least note "compaction skipped — N messages not classified as conversation" rather than "No prior history", to help operators diagnose.
  3. Make default the documented recommendation for users without specific safeguard requirements — currently the schema help text says "Keep 'default' unless you observe aggressive history loss near limit boundaries" but the wizard / docs are silent on the regression risk of safeguard.

Actual behavior

Logs show repeated:

[compaction-safeguard] Compaction safeguard: no real conversation messages to summarize; writing compaction boundary to suppress re-trigger loop.
[agent/embedded] compaction retry aggregate timeout (60000ms): proceeding with pre-compaction state runId=... sessionId=...
[agent/embedded] using pre-compaction snapshot: timed out during compaction runId=... sessionId=...

Recorded compaction entries in transcript files have:

{"type":"compaction","summary":"## Decisions\nNo prior history.\n\n## Open TODOs\nNone.\n\n## Constraints/Rules\nNone.\n\n## Pending user asks\nNone.\n\n## Exact identifiers\nNone captured.","tokensBefore":38085,"fromHook":true}

User-visible impact:

  • Each compaction-loop turn adds ~60s of dead time
  • API token usage is wasted on the aborted summarization calls
  • Session history is not actually compressed — pre-compaction snapshot is restored, so the next turn faces the same pressure
  • Cron pipelines that should finish in 5–10 min stretch to 15+ min

OpenClaw version

2026.5.3-1 (commit 2eae30e)

Operating system

Ubuntu 20.04 LTS, kernel 5.10.25, x64

Install method

npm install -g openclaw via tarball (proxy-bound install)

Model

claude-opus-4-7

Provider / routing chain

feishu (DM / cron) → openclaw gateway → model-provider(enterprize) → anthropic-messages

Additional provider/model setup details

  • Compaction model NOT explicitly overridden — uses session default
  • agents.defaults.compaction.mode: "safeguard" (was the trigger; switching to "default" resolved symptoms)
  • agents.defaults.compaction.timeoutSeconds: not set, so 900s default
  • agents.defaults.compaction.truncateAfterCompaction: not set (so byte-trigger is disabled, only token threshold is active)

Logs, screenshots, and evidence

Repro count over ~12 hours on 2026-05-06

Trigger time (HKT)SessiontokensBeforeOutcome
08:42abf1edff (caixuedan-daily-report cron)38085Empty fallback + 60s timeout
11:0275e6e379 (/new interactive)76486Empty fallback + 60s timeout
11:3175e6e379 sameEmpty fallback (re-loop)
11:5175e6e379 same60s timeout
12:11001e8097 (DM)69035Empty fallback + 60s timeout
12:14, 12:1975e6e379 againMore re-loops
12:2075e6e379 again60s timeout

For comparison, on 2026-05-05 (same install, same config) one compaction at 178K tokens did produce a real ~5KB summary on session 963e608a. So the safeguard path is not 100% broken — it's an as-yet-uncharacterized condition that some sessions hit and others don't.

Sample log excerpt

May 06 08:42:16 ... [compaction-safeguard] Compaction safeguard: no real conversation messages to summarize; writing compaction boundary to suppress re-trigger loop.
May 06 08:43:16 ... [agent/embedded] compaction retry aggregate timeout (60000ms): proceeding with pre-compaction state runId=abf1edff-9629-4b4b-9168-fe4ec4d1c41a sessionId=abf1edff-9629-4b4b-9168-fe4ec4d1c41a
May 06 08:43:16 ... [agent/embedded] using pre-compaction snapshot: timed out during compaction runId=abf1edff-9629-4b4b-9168-fe4ec4d1c41a sessionId=abf1edff-9629-4b4b-9168-fe4ec4d1c41a

Sample empty fallback summary entry from transcript

{
  "type": "compaction",
  "id": "17c34363",
  "parentId": "3aa68b51",
  "timestamp": "2026-05-06T00:42:16.544Z",
  "summary": "## Decisions\nNo prior history.\n\n## Open TODOs\nNone.\n\n## Constraints/Rules\nNone.\n\n## Pending user asks\nNone.\n\n## Exact identifiers\nNone captured.",
  "firstKeptEntryId": "1e91a7ec",
  "tokensBefore": 38085,
  "fromHook": true
}

Workaround that resolved my issue

 "compaction": {
-  "mode": "safeguard"
+  "mode": "default"
 }

After systemctl --user restart openclaw-gateway no further [compaction-safeguard] log lines appear.

Hypothesis (for maintainer triage)

compaction-successor-transcript-D4FqvNh7.js line 3585 (in 2026.5.3-1 dist):

if (!hasRealSummarizable && !hasRealTurnPrefix) {
    log.info("Compaction safeguard: no real conversation messages to summarize; writing compaction boundary to suppress re-trigger loop.");
    return { compaction: {
        summary: buildStructuredFallbackSummary(preparation.previousSummary),
        firstKeptEntryId: preparation.firstKeptEntryId,
        tokensBefore: preparation.tokensBefore
    } };
}

hasRealSummarizable is computed from stripRuntimeContextCustomMessages(preparation.messagesToSummarize). My guess: preparation.messagesToSummarize in some code paths contains only the slice of messages already split off as "to-summarize" while real conversation messages are in preparation.turnPrefixMessages or a different field, so the OR-check (!hasRealSummarizable && !hasRealTurnPrefix) should be evaluating both, but one or both views may be wrong post-strip.

A repro with [compaction-diag] debug logging enabled would pinpoint which of messagesToSummarize / turnPrefixMessages is empty when the safeguard fires.

Happy to provide the affected *.jsonl transcripts (after redaction) and a longer log window if useful.

Impact and severity

No response

Additional information

No response

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

Either:

  1. Fix root cause — the safeguard hook should agree with the underlying transcript on whether real conversation exists. Real cron / interactive sessions with user prompts + assistant tool-using turns should always summarize, not fall through to "No prior history".
  2. Surface the gap — if the safeguard intentionally treats some message classes as non-conversation (e.g. all toolCall blocks with empty assistant text), the fallback summary should at least note "compaction skipped — N messages not classified as conversation" rather than "No prior history", to help operators diagnose.
  3. Make default the documented recommendation for users without specific safeguard requirements — currently the schema help text says "Keep 'default' unless you observe aggressive history loss near limit boundaries" but the wizard / docs are silent on the regression risk of safeguard.

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]: Compaction safeguard mode produces empty fallback summaries that loop until 60s aggregate timeout [1 pull requests, 2 comments, 2 participants]