Summary
When a contextEngine plugin (e.g. lossless-claw) is active, contextEngine.assemble() runs after sanitizeSessionHistory() in the attempt loop. The assembled messages bypass the OpenAI-specific transcript sanitizers, which can re-introduce call_id|fc_id pairs that OpenAI Responses rejects.
Error seen:
Invalid 'input[86].id': 'fc_VlMFjbiUmP_P7YRExGpwuqcZ3Wxmoy6g522VwS00A3TOSbsgVftCkLT0bdYrd'.
Expected an ID that contains letters, numbers, underscores, or dashes,
but this value contained additional characters.
Code path
sanitizeSessionHistory() (src/agents/pi-embedded-runner/google.ts) — applies downgradeOpenAIFunctionCallReasoningPairs() and downgradeOpenAIReasoningBlocks() to the live session messages ✅
contextEngine.assemble() (src/agents/pi-embedded-runner/run/attempt.ts) — replaces the sanitized messages with assembled context from the plugin. This assembled context contains raw stored messages that were not passed through the OpenAI sanitizers ❌
- Provider call receives messages with
fc_* ids missing their paired reasoning items → OpenAI rejects
Root cause
The OpenAI transcript sanitizers run at step 1, but their work is effectively discarded when the context engine replaces the message array at step 2. The context engine plugin has no awareness of provider-specific replay constraints.
Suggested fix
Re-run the OpenAI transcript sanitizers after contextEngine.assemble() returns and before the provider call. Something like:
// After assemble() replaces session messages
if (isOpenAIResponsesModel(model)) {
messages = downgradeOpenAIReasoningBlocks(messages);
messages = downgradeOpenAIFunctionCallReasoningPairs(messages);
}
This keeps the sanitization concern in OpenClaw core where it belongs, rather than requiring every context engine plugin to know about OpenAI replay quirks.
Related
Workaround
Reducing freshTailCount in the context engine config lowers the probability (fewer raw turns replayed), but doesn't eliminate the issue.
Summary
When a
contextEngineplugin (e.g.lossless-claw) is active,contextEngine.assemble()runs aftersanitizeSessionHistory()in the attempt loop. The assembled messages bypass the OpenAI-specific transcript sanitizers, which can re-introducecall_id|fc_idpairs that OpenAI Responses rejects.Error seen:
Code path
sanitizeSessionHistory()(src/agents/pi-embedded-runner/google.ts) — appliesdowngradeOpenAIFunctionCallReasoningPairs()anddowngradeOpenAIReasoningBlocks()to the live session messages ✅contextEngine.assemble()(src/agents/pi-embedded-runner/run/attempt.ts) — replaces the sanitized messages with assembled context from the plugin. This assembled context contains raw stored messages that were not passed through the OpenAI sanitizers ❌fc_*ids missing their pairedreasoningitems → OpenAI rejectsRoot cause
The OpenAI transcript sanitizers run at step 1, but their work is effectively discarded when the context engine replaces the message array at step 2. The context engine plugin has no awareness of provider-specific replay constraints.
Suggested fix
Re-run the OpenAI transcript sanitizers after
contextEngine.assemble()returns and before the provider call. Something like:This keeps the sanitization concern in OpenClaw core where it belongs, rather than requiring every context engine plugin to know about OpenAI replay quirks.
Related
Workaround
Reducing
freshTailCountin the context engine config lowers the probability (fewer raw turns replayed), but doesn't eliminate the issue.