#23291: fix(tts): respect config auto=off over user prefs override
size: XS
Cluster:
Security Enhancements for TTS
## Summary
- **Problem:** Setting \`messages.tts.auto\` to \`"off"\` in \`openclaw.json\` is ignored when a user prefs file exists with \`tts.enabled: true\` or \`tts.auto: "always"\`. The prefs file overrides the config, causing unwanted MP3 attachments on every message.
- **Why it matters:** Users explicitly disable TTS in config but still receive audio files, wasting bandwidth and cluttering messages.
- **What changed:** In \`resolveTtsAutoMode\` (src/tts/tts.ts), when \`config.auto === "off"\`, return \`"off"\` immediately without checking user prefs. Session-level overrides (\`sessionAuto\`) still take priority.
- **What did NOT change:** Session-level TTS override still works. Config values other than \`"off"\` still allow prefs to override. The prefs file itself is not modified.
## Change Type (select all)
- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Docs
- [ ] Security hardening
- [ ] Chore/infra
## Scope (select all touched areas)
- [x] Gateway / orchestration
- [ ] Skills / tool execution
- [ ] Auth / tokens
- [ ] Memory / storage
- [x] Integrations
- [ ] API / contracts
- [ ] UI / DX
- [ ] CI/CD / infra
## Linked Issue/PR
- Closes #22871
## User-visible / Behavior Changes
- \`messages.tts.auto: "off"\` in config now definitively disables TTS — user prefs cannot override it
- Session-level \`ttsAuto\` override still works (higher priority)
## 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\`
## Repro + Verification
### Environment
- OS: macOS 15.3 (arm64)
- Runtime: Node v22+
- Integration/channel: Slack (reported), applies to all channels
### Steps
1. Set \`messages.tts.auto: "off"\` in \`openclaw.json\`
2. Restart gateway
3. Send a message
4. Check if MP3 attachment is generated
### Expected
- No TTS audio generated when auto is "off"
### Actual
- Before fix: MP3 still generated (prefs file with \`tts.enabled: true\` overrides config)
- After fix: Config \`"off"\` is respected, no MP3 generated
## Evidence
Priority chain before fix: \`sessionAuto > prefsAuto > config.auto\`
Priority chain after fix: \`sessionAuto > config.auto=="off" > prefsAuto > config.auto\`
```typescript
// Before: prefs could override config "off"
const prefsAuto = resolveTtsAutoModeFromPrefs(readPrefs(params.prefsPath));
if (prefsAuto) { return prefsAuto; } // returns "always" even when config is "off"
return params.config.auto;
// After: config "off" short-circuits before prefs check
if (params.config.auto === "off") { return "off"; }
const prefsAuto = resolveTtsAutoModeFromPrefs(readPrefs(params.prefsPath));
if (prefsAuto) { return prefsAuto; }
return params.config.auto;
```
## Human Verification (required)
- Verified scenarios: Traced the priority chain; confirmed \`config.auto === "off"\` now returns before prefs are checked; session override still takes priority (checked first)
- Edge cases checked: Config \`"always"\` still allows prefs override; undefined/missing config auto uses default; prefs file absent → no change
- What I did **not** verify: Live Slack message with TTS disabled
## Compatibility / Migration
- Backward compatible? \`Yes\` — only changes behavior when config explicitly sets \`"off"\`
- Config/env changes? \`No\`
- Migration needed? \`No\`
## Failure Recovery (if this breaks)
- How to disable/revert: Remove the \`config.auto === "off"\` early return
- Files/config to restore: \`src/tts/tts.ts\`
- Known bad symptoms: If a user has config \`"off"\` but wants per-session TTS via prefs (unlikely), they would need to use session-level override instead
## Risks and Mitigations
- Risk: Users who set config \`"off"\` but rely on prefs to re-enable TTS per-channel
- Mitigation: This is a very unlikely configuration; the session-level override still works as an alternative
Made with [Cursor](https://cursor.com)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Changes the TTS auto-mode resolution priority to respect `config.auto === "off"` over user preferences. When `messages.tts.auto` is set to `"off"` in `openclaw.json`, it now definitively disables TTS regardless of prefs file settings.
**Key change:**
- Added early return in `resolveTtsAutoMode` (src/tts/tts.ts:343-345) when `config.auto === "off"`
- New priority: `sessionAuto > config.auto=="off" > prefsAuto > config.auto`
- Previously: `sessionAuto > prefsAuto > config.auto`
**Impact:**
- Config `"off"` now definitively disables TTS (cannot be overridden by prefs)
- Config `"always"` can still be overridden by prefs (unchanged)
- Session-level overrides still take highest priority (unchanged)
The fix is minimal, well-targeted, and solves the reported issue where user prefs file with `tts.enabled: true` was incorrectly overriding explicit config disable.
<h3>Confidence Score: 5/5</h3>
- Safe to merge - minimal change with clear semantics and backward compatibility
- Three-line fix that solves a well-defined bug without changing other behavior paths. The logic is straightforward and preserves all other priority relationships. No test coverage for this specific function, but the change is self-contained and the existing integration tests should catch regressions.
- No files require special attention
<sub>Last reviewed commit: 4a03159</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#23071: fix(tts): make config auto=off a hard-disable that overrides user p...
by hydro13 · 2026-02-22
90.7%
#22086: fix(tts): honor explicit config provider and model/voice settings
by AIflow-Labs · 2026-02-20
80.9%
#11109: fix(tui): prefer config contextTokens over persisted session value
by marezgui · 2026-02-07
77.5%
#6677: fix(tts): always load fresh config for voice selection
by Jinqiao · 2026-02-01
74.6%
#21110: fix(tts): deliver audio via structured mediaUrl instead of MEDIA: t...
by hydro13 · 2026-02-19
74.4%
#20992: fix(tts): apply TTS processing to agentCommand outbound delivery path
by mmyyfirstb · 2026-02-19
73.9%
#19412: fix(status): prefer configured contextTokens over session entry
by rafaelipuente · 2026-02-17
73.8%
#20516: fix(tui): preserve streamed text on finalize for pure text responses
by MisterGuy420 · 2026-02-19
73.7%
#18193: fix: default elevatedDefault to 'off' instead of 'on' (#18177)
by lailoo · 2026-02-16
73.5%
#23320: fix(slack): respect replyToMode when incomingThreadTs is auto-created
by dorukardahan · 2026-02-22
73.2%