Skip to content

fix: compaction safeguard falls through when ctx.model is unavailable#4223

Closed
hanxiao wants to merge 5 commits intoopenclaw:mainfrom
hanxiao:fix/compaction-safeguard-fallthrough
Closed

fix: compaction safeguard falls through when ctx.model is unavailable#4223
hanxiao wants to merge 5 commits intoopenclaw:mainfrom
hanxiao:fix/compaction-safeguard-fallthrough

Conversation

@hanxiao
Copy link
Contributor

@hanxiao hanxiao commented Jan 29, 2026

Problem

When Clawdbot runs in embedded mode, extensionRunner.initialize() is never called. This means ExtensionRunner.getModel stays at its default () => undefined.

When the compaction safeguard extension runs, it reads ctx.model which calls getModel() → returns undefined. The safeguard then returns a fallback summary with no actual content:

Summary unavailable due to context limits. Older messages were truncated.

This effectively discards all conversation history on every compaction. It also causes cascading re-compactions: the empty summary is only ~50 tokens, so the next tool call immediately fills the context again, triggering another compaction, which again produces an empty summary.

In a single session I observed 22 consecutive compactions, all with empty summaries.

Root Cause

ExtensionRunner.getModel defaults to () => undefined and is only set to the correct getter during initialize(). The three built-in pi-coding-agent modes (interactive, print, rpc) all call initialize(), but Clawdbot's embedded runner (compact.js, run/attempt.js) does not.

In AgentSession._runAutoCompaction(), the model and API key are correctly validated via this.model before emitting the session_before_compact event. So the model is available -- just not through the extension context's ctx.model getter.

Fix

When ctx.model or apiKey is unavailable, return undefined instead of the fallback summary. This lets the built-in compaction code in AgentSession handle summarization, which correctly accesses the model via this.model (agent.state.model) and produces a proper summary.

Alternative

The deeper fix would be to ensure extensionRunner.initialize() is called in the embedded runner flow, but that's a larger change. This fix is safe and minimal -- the built-in compaction path is well-tested and produces correct summaries.

Greptile Overview

Greptile Summary

This PR adjusts the session_before_compact compaction-safeguard extension so it no longer emits an “empty fallback” compaction result when ctx.model or its API key is unavailable (notably in embedded runner mode where extensionRunner.initialize() is not invoked). Instead it returns undefined, allowing AgentSession’s built-in compaction path to run with a valid model sourced from the session state, preventing repeated compactions caused by near-empty summaries.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk.
  • Change is narrowly scoped to early-return behavior in the compaction safeguard; it removes a harmful fallback output in cases where the extension lacks model/apiKey context and defers to the core, already-validated compaction logic. No API surface changes, and failure path for real summarization errors is unchanged.
  • No files require special attention

(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!

Context used:

  • Context from dashboard - CLAUDE.md (source)
  • Context from dashboard - AGENTS.md (source)

When extensionRunner.initialize() is not called (e.g. in embedded runner
mode), ctx.model resolves to undefined because the default getModel
returns undefined. Previously this caused the safeguard to return a
fallback summary with no actual content, effectively discarding all
conversation history on every compaction.

Instead of returning an empty fallback, return undefined so the built-in
compaction code in AgentSession handles summarization. The built-in path
correctly accesses the model via this.model (AgentSession.agent.state.model)
and produces a proper summary.

Same fix applied to the apiKey null case -- falling through is safer than
producing an empty summary that causes cascading re-compactions.
@openclaw-barnacle openclaw-barnacle bot added the agents Agent runtime and tooling label Jan 29, 2026
@SpacePlushy
Copy link

Nice find on the root cause! I ran into the same issue and took a slightly different approach — instead of falling back to built-in compaction, I initialized getModel on the extension runner after session creation in the embedded runner paths.

In src/agents/pi-embedded-runner/run/attempt.ts (after const activeSession = session;):

// Fix: Initialize extension runner's getModel so extensions can access the model.
const sessionAny = activeSession as any;
if (sessionAny._extensionRunner) {
  sessionAny._extensionRunner.getModel = () => activeSession.model;
}

And the same in src/agents/pi-embedded-runner/compact.ts after the createAgentSession call.

This makes ctx.model work correctly in extensions rather than bypassing them. The tradeoff is accessing a private property (_extensionRunner), but it fixes the actual initialization gap rather than working around it.

Both approaches solve the immediate problem — just wanted to share in case the maintainers prefer fixing the root cause vs. the fallback approach.

@hanxiao
Copy link
Contributor Author

hanxiao commented Feb 3, 2026

Thanks @SpacePlushy! Implemented your suggestion in commits 8afaa14 and f3e0aef.

Added getModel initialization on _extensionRunner in both locations:

  • src/agents/pi-embedded-runner/run/attempt.ts (after session creation)
  • src/agents/pi-embedded-runner/compact.ts (after createAgentSession call)
const sessionAny = activeSession as any;
if (sessionAny._extensionRunner) {
  sessionAny._extensionRunner.getModel = () => activeSession.model;
}

This fixes the root cause so ctx.model works correctly in extensions. The existing fallback in compaction-safeguard.ts (commit 8dc727e) now serves as defense-in-depth rather than the primary fix - it returns a meaningful fallback summary with tool failures and file ops instead of bypassing the extension entirely.

@vincentkoc
Copy link
Member

vincentkoc commented Feb 23, 2026

Thanks for digging into this root cause.

I’m closing this as a duplicate of #17864, which is now the canonical PR for the ctx.model compaction-safeguard failure path and carries current test coverage + review momentum.

Your investigation and implementation materially advanced the final fix path. You will be included in final credits

@vincentkoc vincentkoc closed this Feb 23, 2026
@vincentkoc vincentkoc added dedupe:child Duplicate issue/PR child in dedupe cluster close:duplicate Closed as duplicate labels Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling close:duplicate Closed as duplicate dedupe:child Duplicate issue/PR child in dedupe cluster

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants