openclaw - ✅(Solved) Fix TTS tool audio silently dropped: isAllowedAbsoluteReplyMediaPath rejects /tmp/openclaw paths [1 pull requests, 2 comments, 2 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#65964Fetched 2026-04-14 05:39:32
View on GitHub
Comments
2
Participants
2
Timeline
4
Reactions
1
Author
Participants
Timeline (top)
commented ×2cross-referenced ×1subscribed ×1

Error Message

The built-in tts tool generates audio successfully but voice notes are silently dropped during delivery to Matrix (and likely all channels). No error is logged. 5. Voice note never reaches the channel — no error logged

Root Cause

isAllowedAbsoluteReplyMediaPath() in agent-runner.runtime only allows media from workspace/sandbox dirs and managed global media subdirs. The TTS tool writes audio to /tmp/openclaw/tts-*/voice-*.opus (via resolvePreferredOpenClawTmpDir()), which is outside these allowed roots. The path check fails silently — the audio file exists but is never uploaded or delivered.

The call chain:

  1. TTS tool generates audio → /tmp/openclaw/tts-XXXXX/voice-TIMESTAMP.opus
  2. queuePendingToolMedia() picks up the media URL from details.media.mediaUrl
  3. flushPendingMediaAndChannel()emitBlockReply()onBlockReply()
  4. createBlockReplyDeliveryHandler() calls normalizeMediaPaths()
  5. normalizeMediaSource()isAllowedAbsoluteReplyMediaPath() rejects the /tmp/openclaw/ path
  6. Throws "Absolute host-local MEDIA paths are blocked in normal replies" — caught and swallowed silently

Fix Action

Fix / Workaround

Workaround (local patch)

Patch isAllowedAbsoluteReplyMediaPath in agent-runner.runtime-*.js to accept paths under the OpenClaw tmp dir:

function isAllowedAbsoluteReplyMediaPath(params) {
    if (isManagedGlobalReplyMediaPath(params.candidate)) return true;
    // PATCH: allow TTS tool output from OpenClaw tmp dir
    const resolved = path.resolve(params.candidate);
    const tmpBase = "/tmp/openclaw";
    if (resolved.startsWith(tmpBase + path.sep) || resolved.startsWith(tmpBase + "-")) return true;
    return [params.workspaceDir, params.sandboxRoot]...
}

PR fix notes

PR #66085: fix(reply): harden preferred tmp media allowlist

Description (problem / solution / changelog)

Summary

Follow-up hardening on top of #63511, which already restored TTS reply delivery from the preferred OpenClaw tmp root.

  • keep the functional TTS tmp-root delivery path merged in #63511
  • narrow reply-media allowlisting and persistence to managed TTS artifacts shaped like tts-*/voice-* instead of trusting the whole preferred tmp root
  • re-resolve the preferred tmp root for new candidates while keeping a per-media cache inside one normalization pass so a valid artifact is handled consistently
  • add regression coverage for dynamic tmp-root changes, blocked non-media tmp artifacts, and a hermetic agent-runner seam test

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Related #65964
  • Follow-up to #63511
  • Related #64539
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: #63511 correctly restored TTS delivery by trusting the preferred OpenClaw tmp root, but that root also stores non-media artifacts and should not be trusted wholesale when only managed TTS artifacts need to pass.
  • Missing detection / guardrail: reply-media tests did not lock in a negative case for non-media temp artifacts under the preferred tmp root, a dynamic tmp-root change case, or a hermetic agent-runner seam for preferred-tmp TTS media.
  • Contributing context (if known): the preferred tmp root can move between fallback and preferred locations during a long-lived process, so this follow-up must avoid pinning the first successful root forever while still handling one candidate consistently inside a normalization pass.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/auto-reply/reply/reply-media-paths.test.ts, src/auto-reply/reply/agent-runner.media-paths.test.ts
  • Scenario the test should lock in: managed TTS temp artifacts under tts-*/voice-* survive normalization and persistence, unrelated temp artifacts under the same root remain blocked, and valid artifacts still work after the preferred tmp root changes.
  • Why this is the smallest reliable guardrail: the change is in shared reply-media normalization, and the agent-runner seam confirms the final MEDIA: reply path survives the real pipeline without touching the real tmp-dir helper.
  • Existing test that already covers this (if any): #63511 added broad tmp-root coverage, but not the narrowed trust boundary or dynamic-root follow-up cases.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

  • TTS-generated voice replies under tts-*/voice-* still deliver.
  • Unrelated files under the preferred OpenClaw tmp root stay blocked instead of being treated as valid reply media.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local source tree rebased onto current main
  • Model/provider: N/A for the path-normalization follow-up itself
  • Integration/channel (if any): reply-media normalizer and agent-runner seam
  • Relevant config (redacted): existing Telegram direct-chat setup was used for earlier narrow-path smoke before the rebase

Steps

  1. Route preferred-tmp TTS artifacts shaped like tts-*/voice-* through the shared reply-media normalizer.
  2. Verify they remain allowed and persistable.
  3. Verify unrelated artifacts under the same preferred tmp root are still dropped.
  4. Verify a later valid artifact still works after the preferred tmp root changes.

Expected

  • Managed TTS temp media survives normalization and persistence.
  • Unrelated preferred-tmp artifacts stay blocked.

Actual

  • Targeted tests passed on the rebased follow-up branch.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: ran pnpm test src/auto-reply/reply/reply-media-paths.test.ts src/auto-reply/reply/agent-runner.media-paths.test.ts on the rebased branch.
  • Edge cases checked: preferred-tmp TTS media still persists, unrelated temp artifacts remain blocked, dynamic preferred tmp-root changes work, and the agent-runner seam is hermetic.
  • What you did not verify: a fresh post-rebase live Telegram smoke on this exact commit.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps:

Risks and Mitigations

  • Risk: a valid TTS artifact could be checked against two different tmp roots inside one normalization pass if the resolver changes between calls.
    • Mitigation: the normalizer now memoizes the resolved tmp root per media candidate within the pass while still re-resolving for new candidates over the lifetime of the process.
  • Risk: reviewers may compare this against the already-merged broad fix and think the branch is stale.
    • Mitigation: this PR is now explicitly framed as a hardening follow-up on top of #63511 rather than a parallel replacement.

Changed files

  • src/auto-reply/reply/agent-runner.media-paths.test.ts (modified, +67/-0)
  • src/auto-reply/reply/reply-media-paths.test.ts (modified, +53/-0)
  • src/auto-reply/reply/reply-media-paths.ts (modified, +82/-34)

Code Example

function isAllowedAbsoluteReplyMediaPath(params) {
    if (isManagedGlobalReplyMediaPath(params.candidate)) return true;
    // PATCH: allow TTS tool output from OpenClaw tmp dir
    const resolved = path.resolve(params.candidate);
    const tmpBase = "/tmp/openclaw";
    if (resolved.startsWith(tmpBase + path.sep) || resolved.startsWith(tmpBase + "-")) return true;
    return [params.workspaceDir, params.sandboxRoot]...
}
RAW_BUFFERClick to expand / collapse

Bug

The built-in tts tool generates audio successfully but voice notes are silently dropped during delivery to Matrix (and likely all channels). No error is logged.

Root Cause

isAllowedAbsoluteReplyMediaPath() in agent-runner.runtime only allows media from workspace/sandbox dirs and managed global media subdirs. The TTS tool writes audio to /tmp/openclaw/tts-*/voice-*.opus (via resolvePreferredOpenClawTmpDir()), which is outside these allowed roots. The path check fails silently — the audio file exists but is never uploaded or delivered.

The call chain:

  1. TTS tool generates audio → /tmp/openclaw/tts-XXXXX/voice-TIMESTAMP.opus
  2. queuePendingToolMedia() picks up the media URL from details.media.mediaUrl
  3. flushPendingMediaAndChannel()emitBlockReply()onBlockReply()
  4. createBlockReplyDeliveryHandler() calls normalizeMediaPaths()
  5. normalizeMediaSource()isAllowedAbsoluteReplyMediaPath() rejects the /tmp/openclaw/ path
  6. Throws "Absolute host-local MEDIA paths are blocked in normal replies" — caught and swallowed silently

Environment

  • OpenClaw 2026.4.11 (769908e)
  • Platform: Linux (Debian 13, x86_64)
  • Channel: Matrix (encrypted rooms)
  • TTS provider: OpenAI-compatible (self-hosted Kokoro)
  • Multi-agent host (3 agents, different UIDs → /tmp/openclaw-<uid>/ fallback paths also affected)

Reproduction

  1. Configure messages.tts with any provider (auto: "off")
  2. Agent calls built-in tts tool
  3. Tool returns details.audioPath: "/tmp/openclaw/tts-XXXXX/voice-*.opus" with audioAsVoice: true
  4. Session shows "Generated audio reply." + NO_REPLY — appears successful
  5. Voice note never reaches the channel — no error logged

Workaround (local patch)

Patch isAllowedAbsoluteReplyMediaPath in agent-runner.runtime-*.js to accept paths under the OpenClaw tmp dir:

function isAllowedAbsoluteReplyMediaPath(params) {
    if (isManagedGlobalReplyMediaPath(params.candidate)) return true;
    // PATCH: allow TTS tool output from OpenClaw tmp dir
    const resolved = path.resolve(params.candidate);
    const tmpBase = "/tmp/openclaw";
    if (resolved.startsWith(tmpBase + path.sep) || resolved.startsWith(tmpBase + "-")) return true;
    return [params.workspaceDir, params.sandboxRoot]...
}

Suggested Fixes

  1. Add OpenClaw tmp dir to allowed rootsisAllowedAbsoluteReplyMediaPath should accept paths from resolvePreferredOpenClawTmpDir() since that's where OpenClaw's own tools write output
  2. Configurable TTS output path — Allow messages.tts.outputDir to override where TTS audio is written, so operators can point it at a workspace-local dir that's already in the allowed roots
  3. Extract audioPath from tool details — Bypass MEDIA: text parsing entirely by passing audioPath through the onToolResult callback chain (as proposed in #4915)
  4. Log the rejection — At minimum, the silent drop should emit a warning so operators can diagnose delivery failures

Related Issues

  • #63896 — TTS audio delivery failure (same root cause, Telegram)
  • #4915 — Proposed fix with audioPath extraction from tool details
  • #9722 — Closed as not planned, same underlying bug
  • #13378 — Closed as duplicate of #9722

extent analysis

TL;DR

The most likely fix is to patch isAllowedAbsoluteReplyMediaPath to accept paths under the OpenClaw tmp dir or configure the TTS output path to a workspace-local directory.

Guidance

  • Verify that the isAllowedAbsoluteReplyMediaPath function is indeed the cause of the issue by checking the call chain and the paths being rejected.
  • Consider applying the suggested local patch to isAllowedAbsoluteReplyMediaPath to allow TTS tool output from the OpenClaw tmp dir.
  • Alternatively, explore configuring the TTS output path to a workspace-local directory that is already in the allowed roots.
  • To diagnose delivery failures, consider logging the rejection of media paths to emit a warning.

Example

The provided local patch code snippet can be used as a starting point:

function isAllowedAbsoluteReplyMediaPath(params) {
    if (isManagedGlobalReplyMediaPath(params.candidate)) return true;
    // PATCH: allow TTS tool output from OpenClaw tmp dir
    const resolved = path.resolve(params.candidate);
    const tmpBase = "/tmp/openclaw";
    if (resolved.startsWith(tmpBase + path.sep) || resolved.startsWith(tmpBase + "-")) return true;
    return [params.workspaceDir, params.sandboxRoot]...
}

Notes

The suggested fixes and workarounds may have varying degrees of complexity and impact on the system. It is essential to evaluate and test each approach carefully before implementing a solution.

Recommendation

Apply the workaround by patching isAllowedAbsoluteReplyMediaPath to accept paths under the OpenClaw tmp dir, as this directly addresses the root cause of the issue. This approach provides a targeted solution to the problem, allowing for the delivery of TTS-generated audio files.

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