openclaw - ✅(Solved) Fix [Bug]: guarded runtime fetch drops multipart FormData fields, breaking OpenAI audio transcription [1 pull requests, 2 comments, 1 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#64312Fetched 2026-04-11 06:15:25
View on GitHub
Comments
2
Participants
1
Timeline
3
Reactions
0
Participants
Timeline (top)
commented ×2cross-referenced ×1

Telegram voice-note auto transcription can fail even when OpenAI auth is configured correctly and manual transcription works.

Observed symptom in chat:

  • inbound voice messages reach the agent only as [media attached: ... .ogg]
  • no transcript is injected before the message hits the agent

Observed API failure when tracing the media/audio path:

  • OpenAI returns HTTP 400: you must provide a model parameter

This is misleading at first glance because OpenClaw does resolve a default model (gpt-4o-transcribe) and manual fetch() / curl requests with the same file and key succeed.

Error Message

"error": {

Root Cause

The failure is in the guarded HTTP path, not in Telegram or provider auth.

fetchWithSsrFGuard() uses fetchWithRuntimeDispatcher() whenever a dispatcher is active for SSRF-safe requests. That runtime path uses undici.fetch from OpenClaw's bundled runtime.

For audio transcription requests, the body is built with global FormData and then passed into that runtime undici.fetch path. Because the runtime fetch and the original body come from different FormData implementations/realms, multipart fields are not serialized correctly.

Result:

  • the file may still be present enough for the request to parse
  • but text fields like model are dropped or not encoded correctly
  • OpenAI responds with you must provide a model parameter

Fix Action

Fix

Before calling runtime undici.fetch, normalize FormData bodies into the runtime's own undici.FormData implementation and clear content-type / content-length so the multipart boundary is regenerated correctly.

PR fix notes

PR #64349: fix: preserve multipart FormData fields in runtime fetch dispatch

Description (problem / solution / changelog)

Summary

  • normalize non-runtime FormData bodies before dispatching through runtime undici.fetch
  • expose runtime FormData from undici-runtime
  • drop stale multipart content-type and content-length headers when the body is rebuilt
  • add regression coverage for the runtime FormData normalization path

Problem

OpenClaw's guarded/runtime fetch path could cross a runtime boundary with a FormData instance created by a different implementation than runtime undici.FormData.

For OpenAI audio transcription requests, that caused multipart fields to be lost during dispatch, including model, which produced:

  • Audio transcription failed (HTTP 400)
  • you must provide a model parameter

Validation

  • pnpm exec vitest run --config vitest.runtime-fetch.temp.config.ts
    • src/infra/net/fetch-guard.ssrf.test.ts
    • src/media/input-files.fetch-guard.test.ts
    • src/infra/net/runtime-fetch.test.ts

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/infra/net/runtime-fetch.test.ts (added, +85/-0)
  • src/infra/net/runtime-fetch.ts (modified, +71/-3)
  • src/infra/net/undici-runtime.ts (modified, +2/-0)

Code Example

const resWrap = await fetchWithSsrFGuard({
  url: 'https://api.openai.com/v1/audio/transcriptions',
  fetchImpl: fetch,
  init: {
    method: 'POST',
    headers: new Headers({ authorization: `Bearer ${apiKey}` }),
    body: form // global FormData
  },
  timeoutMs: 60000,
  dispatcherPolicy: { mode: 'direct' }
});

---

HTTP 400
{
  "error": {
    "message": "you must provide a model parameter"
  }
}
RAW_BUFFERClick to expand / collapse

OpenClaw bug report draft: Telegram/OpenAI STT broken by runtime fetch FormData mismatch

Suggested title

[Bug]: multipart/FormData requests lose fields when fetchWithSsrFGuard routes through runtime undici fetch

Summary

Telegram voice-note auto transcription can fail even when OpenAI auth is configured correctly and manual transcription works.

Observed symptom in chat:

  • inbound voice messages reach the agent only as [media attached: ... .ogg]
  • no transcript is injected before the message hits the agent

Observed API failure when tracing the media/audio path:

  • OpenAI returns HTTP 400: you must provide a model parameter

This is misleading at first glance because OpenClaw does resolve a default model (gpt-4o-transcribe) and manual fetch() / curl requests with the same file and key succeed.

Root cause

The failure is in the guarded HTTP path, not in Telegram or provider auth.

fetchWithSsrFGuard() uses fetchWithRuntimeDispatcher() whenever a dispatcher is active for SSRF-safe requests. That runtime path uses undici.fetch from OpenClaw's bundled runtime.

For audio transcription requests, the body is built with global FormData and then passed into that runtime undici.fetch path. Because the runtime fetch and the original body come from different FormData implementations/realms, multipart fields are not serialized correctly.

Result:

  • the file may still be present enough for the request to parse
  • but text fields like model are dropped or not encoded correctly
  • OpenAI responds with you must provide a model parameter

Concrete repro

Environment:

  • OpenClaw 2026.4.9
  • Telegram inbound voice notes
  • OpenAI auth profile present (openai:default)
  • manual curl to /v1/audio/transcriptions works

This fails through the guarded/runtime fetch path:

const resWrap = await fetchWithSsrFGuard({
  url: 'https://api.openai.com/v1/audio/transcriptions',
  fetchImpl: fetch,
  init: {
    method: 'POST',
    headers: new Headers({ authorization: `Bearer ${apiKey}` }),
    body: form // global FormData
  },
  timeoutMs: 60000,
  dispatcherPolicy: { mode: 'direct' }
});

OpenAI response:

HTTP 400
{
  "error": {
    "message": "you must provide a model parameter"
  }
}

The same request works with plain global fetch() and also works with runtime undici.fetch() if the body is rebuilt using the runtime's own undici.FormData.

Fix

Before calling runtime undici.fetch, normalize FormData bodies into the runtime's own undici.FormData implementation and clear content-type / content-length so the multipart boundary is regenerated correctly.

Local patch that fixed it

File: dist/undici-runtime-C7foUhmW.js

  • expose FormData from the bundled undici runtime deps

File: dist/fetch-guard-Conjfhmz.js

  • add a normalization step in fetchWithRuntimeDispatcher()
  • when init.body is FormData, rebuild it into runtimeDeps.FormData
  • remove stale content-type and content-length headers before sending

Result after patch

  • provider-level transcription succeeds
  • media capability runner succeeds
  • Telegram voice note arrives already transcribed in chat
  • example successful inbound transcript:
    • Toto je poslední testovací zpráva.

Why this matters

This likely affects more than Telegram voice-note STT. Any guarded multipart request that goes through runtime undici.fetch may be vulnerable to the same FormData incompatibility.

That means this should probably be fixed centrally in the fetch/transport layer rather than only in the OpenAI audio transcription code path.

Suggested tests

Add regression coverage for:

  1. guarded multipart POST with dispatcher enabled
  2. global FormData body passed through runtime fetch path
  3. OpenAI audio transcription request preserving both file and model
  4. any other multipart provider flows that use the same guarded transport

Notes

Related symptom-level issues already existed around Telegram voice transcription, but this report identifies a lower-level transport root cause and a concrete fix.

extent analysis

TL;DR

To fix the issue with multipart/FormData requests losing fields when routed through the runtime undici fetch, normalize FormData bodies into the runtime's own undici.FormData implementation before calling undici.fetch.

Guidance

  • Identify all instances where fetchWithSsrFGuard is used with a FormData body and ensure the body is rebuilt using the runtime's undici.FormData implementation.
  • Verify that the content-type and content-length headers are removed before sending the request to allow the multipart boundary to be regenerated correctly.
  • Test the fix with different provider flows that use the same guarded transport to ensure the issue is resolved centrally.
  • Consider adding regression tests for guarded multipart POST requests with dispatcher enabled and global FormData bodies passed through the runtime fetch path.

Example

const runtimeFormData = new runtimeDeps.FormData();
for (const [key, value] of form.entries()) {
  runtimeFormData.append(key, value);
}
init.body = runtimeFormData;
delete init.headers['content-type'];
delete init.headers['content-length'];

Notes

The provided fix should be applied centrally in the fetch/transport layer to prevent similar issues with other multipart requests. Additionally, regression tests should be added to cover different scenarios and ensure the fix is effective.

Recommendation

Apply the workaround by normalizing FormData bodies into the runtime's own undici.FormData implementation before calling undici.fetch, as this fix addresses the root cause of the issue and prevents similar problems with other multipart requests.

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…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING