Skip to content

OpenAI fc_* transcript sanitizers bypassed when contextEngine.assemble() replaces session messages #42794

@Grynn

Description

@Grynn

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

  1. sanitizeSessionHistory() (src/agents/pi-embedded-runner/google.ts) — applies downgradeOpenAIFunctionCallReasoningPairs() and downgradeOpenAIReasoningBlocks() to the live session messages ✅
  2. 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 ❌
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions