Voice recording fails with "Error: exit code Some(1)" after stopping recording #76
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Description
After pressing the record button and recording voice input, the expected behavior is to see the transcribed text appear in the editor. Instead, upon pressing the stop recording button, the following error is returned:
Error: exit code Some(1)The transcript is not inserted into the editor.
The same happens when pressed on
Instructthen pressed onRecordbut no errors, and nothing happenedI wonder, is it using hero_voice? So it requires it to be running? I am not sure!!
Implementation Spec: Fix Voice Recording — Issue #76
Root Cause Analysis
Bug A — Subprocess exits with code 1 ("exit code Some(1)")
submit_voice_transcribe_jobcalls the job with no env vars. The subprocesshero_slides voice transcribecallsgroq_provider()which readsGROQ_API_KEYfrom the environment. If hero_proc was started without those keys in its environment (e.g. launched as a service without the user's shell env), the subprocess has no keys and fails with exit code 1.The error text displayed is
"exit code Some(1)"because the executor's fallback reason fires when the log tail is empty.Bug B — Transcript fields not surfaced to the UI
handle_voice_transcribe_asyncregisters the job usingcontent_result_inputs(...), which sets__result_kind = "content". The manager readstranscript.jsonas raw text and stores it asJobResultPayload::Content { text: "<json string>" }.handle_ai_job_resultthen returns{ state: "done", result: { content: "..." } }.All three JavaScript entry points expect
result.cleaned(orresult.cleaned || result.raw). Since the result shape isresult.content(a raw JSON string),result.cleanedisundefined— the editor is never populated.Bug C — "Instruct → Record" produces no error and no result
Same as Bug B path — the call reaches
handle_ai_job_resultwithstate: "done"andresult.contentcontaining the JSON string, butresult.cleanedisundefined.Requirements
voice.transcribeAsyncjob must receiveGROQ_API_KEYandOPENROUTER_API_KEY, fetched from hero_proc secrets or the server's own environment, and passed explicitly in the job's env vars.agent.aiJobResultresponse for a completed voice transcription job must includeresult.rawandresult.cleanedfields directly accessible to the JavaScript callers.{ raw, cleaned }shape as a newTranscriptvariant ofJobResultPayload."exit code Some(1)").Files to Modify
crates/hero_slides_server/src/jobs/types.rsTranscript { raw: String, cleaned: String }variant toJobResultPayloadcrates/hero_slides_server/src/jobs/rpc.rsJobResultPayload::Transcriptinhandle_ai_job_result→ return{ raw, cleaned }crates/hero_slides_server/src/jobs/manager.rs__result_kind = "transcript"inmaybe_spawn_result_loadercrates/hero_slides_server/src/jobs/generate.rstranscript_result_inputshelper; use it; pass API key env vars to the jobcrates/hero_slides/src/main.rsGROQ_API_KEYbefore transcription and emit a clear errorImplementation Plan
Step 1 — Add
Transcriptvariant toJobResultPayloadFile:
crates/hero_slides_server/src/jobs/types.rsDependencies: None
Add to the
JobResultPayloadenum:Step 2 — Handle
Transcriptinhandle_ai_job_resultFile:
crates/hero_slides_server/src/jobs/rpc.rsDependencies: Step 1
Add match arm returning
{ "raw": raw, "cleaned": cleaned }.Step 3 — Handle
__result_kind = "transcript"in the result loaderFile:
crates/hero_slides_server/src/jobs/manager.rsDependencies: Step 1
Parse the JSON file, extract
raw/cleaned, store asJobResultPayload::Transcript.Step 4 — Add
transcript_result_inputsand use itFile:
crates/hero_slides_server/src/jobs/generate.rsDependencies: None (can be done in parallel with Steps 1–3)
Add helper that sets
__result_kind = "transcript"and__output_file. Replace thecontent_result_inputscall inhandle_voice_transcribe_async.Step 5 — Fetch API keys and pass them to the voice transcription job
File:
crates/hero_slides_server/src/jobs/generate.rsDependencies: None (can be done in parallel)
Fetch
GROQ_API_KEYandOPENROUTER_API_KEYfrom hero_proc secrets (falling back to env), pass them explicitly as env vars insubmit_voice_transcribe_job.Step 6 — Improve error message when keys are missing
File:
crates/hero_slides/src/main.rsDependencies: None (independent)
Pre-check
GROQ_API_KEYbefore attempting transcription; emit a clear error to stderr so the log tail contains a useful message.Acceptance Criteria
GROQ_API_KEYis missing, the error shown is human-readable (not"exit code Some(1)")agent.aiJobResultreturns{ state: "done", result: { raw: "...", cleaned: "..." } }for a completed voice jobNotes
result.cleaned || result.raw— no frontend changes needed after fixing the server payload.OPENROUTER_API_KEYis needed for the LLM cleanup step; both keys must be passed.voice.transcribeResultRPC reads directly from disk and returns{ raw, cleaned }correctly — it is not affected by this fix.Test Results
Status: Passed
Breakdown by crate:
cargo checkalso passed with no warnings or errors.Implementation Summary
Two root causes were identified and fixed.
Root Cause A — Subprocess exits with code 1
submit_voice_transcribe_jobpreviously passed no env vars to the job subprocess. Thehero_slides voice transcribeprocess readsGROQ_API_KEYandOPENROUTER_API_KEYfrom its environment. When hero_proc is launched as a service without these keys in its environment, the subprocess exits with code 1 and the log tail is empty, producing the unhelpful "exit code Some(1)" message.Fix:
submit_voice_transcribe_jobnow readsGROQ_API_KEYandOPENROUTER_API_KEYfrom the server process environment and passes them explicitly to the subprocess as env vars.Additionally, the
voice transcribeCLI subcommand now pre-validatesGROQ_API_KEYbefore attempting transcription and emits a clear error message if it is missing, so any future failure produces a human-readable log entry.Root Cause B — Transcript not inserted into the editor
handle_voice_transcribe_asyncusedcontent_result_inputs(...)which sets__result_kind = "content". The manager then stored the entiretranscript.jsonas a raw string inJobResultPayload::Content.handle_ai_job_resultreturned{ content: "<json string>" }. All three JavaScript callers expectresult.cleaned(orresult.cleaned || result.raw) —result.contentis never read, so the editor was never populated.Fix:
JobResultPayload::Transcript { raw: String, cleaned: String }variant totypes.rshandle_ai_job_resultmatch arm that returns{ "raw": raw, "cleaned": cleaned }inrpc.rs"transcript"arm to the result loader inmanager.rsthat parses the JSON file and stores the two fieldstranscript_result_inputshelper ingenerate.rsthat sets__result_kind = "transcript", replacing thecontent_result_inputscall inhandle_voice_transcribe_asyncFiles Modified
crates/hero_slides_server/src/jobs/types.rs— newTranscriptvariantcrates/hero_slides_server/src/jobs/rpc.rs— handleTranscriptinhandle_ai_job_resultcrates/hero_slides_server/src/jobs/manager.rs— parse transcript JSON in result loadercrates/hero_slides_server/src/jobs/generate.rs—transcript_result_inputshelper, API key env var injectioncrates/hero_slides/src/main.rs— pre-validateGROQ_API_KEYwith clear error messageTest Results
180 tests passed, 0 failed, 2 ignored (require live AI backend).