Skip to content

Commit 714f3b5

Browse files
committed
fix: preserve unknown compaction failure detail
1 parent 34a0a9f commit 714f3b5

4 files changed

Lines changed: 57 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Docs: https://docs.openclaw.ai
4343

4444
- Gateway/hooks: route non-delivered hook completion and error summaries to the target agent's main session instead of the default agent session, preserving multi-agent hook isolation. Fixes #24693; carries forward #68667. Thanks @abersonFAC and @bluesky6868.
4545
- Discord: own the Carbon interaction listener and hand off Discord slash/component handling asynchronously, so compaction or long session locks no longer trip `InteractionEventListener` listener timeouts. Fixes #73204. Thanks @slideshow-dingo.
46+
- Compaction/diagnostics: keep unknown compaction failure classifications stable while logging sanitized detail for unclassified provider errors such as missing Ollama provider adapters. Thanks @gzsiang.
4647
- Gateway/startup: keep value-option foreground starts on the gateway fast path and skip proxy bootstrap unless proxy env is configured, reducing normal gateway startup RSS and avoiding full CLI graph loading. Thanks @vincentkoc.
4748
- Heartbeat/models: show heartbeat model bleed guidance on context-overflow resets when the last runtime model matches configured `heartbeat.model`, so smaller local heartbeat models point users to `isolatedSession` or `lightContext` instead of only compaction-buffer tuning. Fixes #67314. Thanks @Knightmare6890.
4849
- Subagents/models: persist `sessions_spawn.model` and configured subagent models as child-session model overrides before the first turn, so spawned subagents actually run on the requested provider/model instead of reverting to the target agent default. Fixes #73180. Thanks @danielzinhu99.

src/agents/pi-embedded-runner/compact-reasons.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { describe, expect, it } from "vitest";
2-
import { classifyCompactionReason, resolveCompactionFailureReason } from "./compact-reasons.js";
2+
import {
3+
classifyCompactionReason,
4+
formatUnknownCompactionReasonDetail,
5+
resolveCompactionFailureReason,
6+
} from "./compact-reasons.js";
37

48
describe("resolveCompactionFailureReason", () => {
59
it("replaces generic compaction cancellation with the safeguard reason", () => {
@@ -37,4 +41,30 @@ describe("classifyCompactionReason", () => {
3741
),
3842
).toBe("guard_blocked");
3943
});
44+
45+
it("keeps unclassified provider errors in the stable unknown bucket", () => {
46+
expect(classifyCompactionReason("No API provider registered for api: ollama")).toBe("unknown");
47+
});
48+
});
49+
50+
describe("formatUnknownCompactionReasonDetail", () => {
51+
it("formats unknown reasons as single-token diagnostic detail", () => {
52+
expect(formatUnknownCompactionReasonDetail("No API provider registered for api: ollama")).toBe(
53+
"No_API_provider_registered_for_api:_ollama",
54+
);
55+
});
56+
57+
it("strips terminal escapes and log separators from unknown reasons", () => {
58+
expect(
59+
formatUnknownCompactionReasonDetail("\u001b[31mNo API\u001b[0m provider = ollama\nnext"),
60+
).toBe("No_API_provider_ollama_next");
61+
});
62+
63+
it("omits empty unknown reason detail", () => {
64+
expect(formatUnknownCompactionReasonDetail(" \n\t ")).toBeUndefined();
65+
});
66+
67+
it("limits unknown reason detail length", () => {
68+
expect(formatUnknownCompactionReasonDetail("x".repeat(120))).toHaveLength(100);
69+
});
4070
});

src/agents/pi-embedded-runner/compact-reasons.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
2+
import { sanitizeForLog } from "../../terminal/ansi.js";
3+
4+
const MAX_COMPACTION_REASON_DETAIL_CHARS = 100;
25

36
function isGenericCompactionCancelledReason(reason: string): boolean {
47
const normalized = normalizeLowercaseStringOrEmpty(reason);
@@ -59,3 +62,15 @@ export function classifyCompactionReason(reason?: string): string {
5962
}
6063
return "unknown";
6164
}
65+
66+
export function formatUnknownCompactionReasonDetail(reason?: string): string | undefined {
67+
const sanitized = sanitizeForLog((reason ?? "").replace(/\s+/g, " "))
68+
.trim()
69+
.replace(/[^A-Za-z0-9._:@/+~-]+/g, "_")
70+
.replace(/_+/g, "_")
71+
.replace(/^_+|_+$/g, "");
72+
if (!sanitized) {
73+
return undefined;
74+
}
75+
return sanitized.slice(0, MAX_COMPACTION_REASON_DETAIL_CHARS);
76+
}

src/agents/pi-embedded-runner/compact.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,11 @@ import {
9797
resolveSkillsPromptForRun,
9898
} from "../skills.js";
9999
import { resolveSystemPromptOverride } from "../system-prompt-override.js";
100-
import { classifyCompactionReason, resolveCompactionFailureReason } from "./compact-reasons.js";
100+
import {
101+
classifyCompactionReason,
102+
formatUnknownCompactionReasonDetail,
103+
resolveCompactionFailureReason,
104+
} from "./compact-reasons.js";
101105
import type { CompactEmbeddedPiSessionParams, CompactionMessageMetrics } from "./compact.types.js";
102106
import { dedupeDuplicateUserMessagesForCompaction } from "./compaction-duplicate-user-messages.js";
103107
import {
@@ -351,10 +355,14 @@ export async function compactEmbeddedPiSessionDirect(
351355
let thinkLevel: ThinkLevel = params.thinkLevel ?? "off";
352356
const attemptedThinking = new Set<ThinkLevel>();
353357
const fail = (reason: string): EmbeddedPiCompactResult => {
358+
const failureReason = classifyCompactionReason(reason);
359+
const detail =
360+
failureReason === "unknown" ? formatUnknownCompactionReasonDetail(reason) : undefined;
361+
const detailSuffix = detail ? ` detail=${detail}` : "";
354362
log.warn(
355363
`[compaction-diag] end runId=${runId} sessionKey=${params.sessionKey ?? params.sessionId} ` +
356364
`diagId=${diagId} trigger=${trigger} provider=${provider}/${modelId} ` +
357-
`attempt=${attempt} maxAttempts=${maxAttempts} outcome=failed reason=${classifyCompactionReason(reason)} ` +
365+
`attempt=${attempt} maxAttempts=${maxAttempts} outcome=failed reason=${failureReason}${detailSuffix} ` +
358366
`durationMs=${Date.now() - startedAt}`,
359367
);
360368
return {

0 commit comments

Comments
 (0)