fix(messages): reduce audio transcription 400 retry noise (#13487)

## Summary
This PR reduces duplicate failure noise for audio transcription jobs
that fail with permanent HTTP 400 responses, and fixes a file-format
edge case causing intermittent 400s.

Sentry issue: [CHATWOOT-99E /
6660541334](https://chatwoot-p3.sentry.io/issues/6660541334/)

## Confirmed root cause
For some attachments, the stored filename had no extension (example:
`speech`, content type `audio/mpeg`).
When the temporary transcription upload file was created without an
extension, OpenAI returned:
`Unrecognized file format` (HTTP 400).

## Scope of changes
1. `Messages::AudioTranscriptionJob`
- Keeps `discard_on Faraday::BadRequestError` to avoid retry storms on
permanent request errors.
- Adds explicit Rails warning logs for discarded jobs with
attachment/job/status context.

2. `Messages::AudioTranscriptionService`
- Keeps guaranteed temp file cleanup via `ensure`.
- Ensures temp upload files include an extension when the original
filename has none, derived from blob `content_type`.
- This addresses intermittent failures like extensionless `audio/mpeg`
files.

## Reproduction
Enable audio transcription for an account and process an audio
attachment whose stored filename has no extension (for example `speech`)
but valid audio content type (`audio/mpeg`).
Before this fix, OpenAI transcription could return HTTP 400
`Unrecognized file format` for that attachment while similar attachments
with extensions succeeded.

## Testing
Ran:
`bundle exec rubocop
enterprise/app/jobs/messages/audio_transcription_job.rb
enterprise/app/services/messages/audio_transcription_service.rb`

Result: both modified files pass lint with no offenses.
This commit is contained in:
Sojan Jose
2026-02-16 23:55:13 -08:00
committed by GitHub
parent 9cd7c4ef89
commit 61eaa098ae
3 changed files with 55 additions and 8 deletions

View File

@@ -8,8 +8,8 @@ RSpec.describe Messages::AudioTranscriptionService, type: :service do
before do
# Create required installation configs
create(:installation_config, name: 'CAPTAIN_OPEN_AI_API_KEY', value: 'test-api-key')
create(:installation_config, name: 'CAPTAIN_OPEN_AI_MODEL', value: 'gpt-4o-mini')
InstallationConfig.find_or_create_by!(name: 'CAPTAIN_OPEN_AI_API_KEY') { |config| config.value = 'test-api-key' }
InstallationConfig.find_or_create_by!(name: 'CAPTAIN_OPEN_AI_MODEL') { |config| config.value = 'gpt-4o-mini' }
# Mock usage limits for transcription to be available
allow(account).to receive(:usage_limits).and_return({ captain: { responses: { current_available: 100 } } })
@@ -64,4 +64,24 @@ RSpec.describe Messages::AudioTranscriptionService, type: :service do
end
end
end
describe '#fetch_audio_file' do
let(:service) { described_class.new(attachment) }
before do
attachment.file.attach(
io: File.open(Rails.public_path.join('audio/widget/ding.mp3')),
filename: 'speech',
content_type: 'audio/mpeg'
)
end
it 'adds extension from content type when filename has no extension' do
temp_file_path = service.send(:fetch_audio_file)
expect(File.extname(temp_file_path)).to eq('.mpeg')
ensure
FileUtils.rm_f(temp_file_path) if temp_file_path.present?
end
end
end