Voice recording fails with "Error: exit code Some(1)" after stopping recording #76

Open
opened 2026-05-20 09:55:51 +00:00 by mahmoud · 4 comments
Owner

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.

### 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.
Author
Owner

The same happens when pressed on Instruct then pressed on Record but no errors, and nothing happened

I wonder, is it using hero_voice? So it requires it to be running? I am not sure!!

The same happens when pressed on `Instruct` then pressed on `Record` but no errors, and nothing happened I wonder, is it using hero_voice? So it requires it to be running? I am not sure!!
Member

Implementation Spec: Fix Voice Recording — Issue #76

Root Cause Analysis

Bug A — Subprocess exits with code 1 ("exit code Some(1)")

submit_voice_transcribe_job calls the job with no env vars. The subprocess hero_slides voice transcribe calls groq_provider() which reads GROQ_API_KEY from 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_async registers the job using content_result_inputs(...), which sets __result_kind = "content". The manager reads transcript.json as raw text and stores it as JobResultPayload::Content { text: "<json string>" }. handle_ai_job_result then returns { state: "done", result: { content: "..." } }.

All three JavaScript entry points expect result.cleaned (or result.cleaned || result.raw). Since the result shape is result.content (a raw JSON string), result.cleaned is undefined — 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_result with state: "done" and result.content containing the JSON string, but result.cleaned is undefined.


Requirements

  • The voice.transcribeAsync job must receive GROQ_API_KEY and OPENROUTER_API_KEY, fetched from hero_proc secrets or the server's own environment, and passed explicitly in the job's env vars.
  • The agent.aiJobResult response for a completed voice transcription job must include result.raw and result.cleaned fields directly accessible to the JavaScript callers.
  • The manager must represent the transcript { raw, cleaned } shape as a new Transcript variant of JobResultPayload.
  • When API keys are missing, the error message surfaced to the user must be human-readable (not "exit code Some(1)").

Files to Modify

File Change
crates/hero_slides_server/src/jobs/types.rs Add Transcript { raw: String, cleaned: String } variant to JobResultPayload
crates/hero_slides_server/src/jobs/rpc.rs Handle JobResultPayload::Transcript in handle_ai_job_result → return { raw, cleaned }
crates/hero_slides_server/src/jobs/manager.rs Handle __result_kind = "transcript" in maybe_spawn_result_loader
crates/hero_slides_server/src/jobs/generate.rs Add transcript_result_inputs helper; use it; pass API key env vars to the job
crates/hero_slides/src/main.rs Pre-validate GROQ_API_KEY before transcription and emit a clear error

Implementation Plan

Step 1 — Add Transcript variant to JobResultPayload

File: crates/hero_slides_server/src/jobs/types.rs
Dependencies: None

Add to the JobResultPayload enum:

Transcript { raw: String, cleaned: String },

Step 2 — Handle Transcript in handle_ai_job_result

File: crates/hero_slides_server/src/jobs/rpc.rs
Dependencies: Step 1

Add match arm returning { "raw": raw, "cleaned": cleaned }.

Step 3 — Handle __result_kind = "transcript" in the result loader

File: crates/hero_slides_server/src/jobs/manager.rs
Dependencies: Step 1

Parse the JSON file, extract raw/cleaned, store as JobResultPayload::Transcript.

Step 4 — Add transcript_result_inputs and use it

File: crates/hero_slides_server/src/jobs/generate.rs
Dependencies: None (can be done in parallel with Steps 1–3)

Add helper that sets __result_kind = "transcript" and __output_file. Replace the content_result_inputs call in handle_voice_transcribe_async.

Step 5 — Fetch API keys and pass them to the voice transcription job

File: crates/hero_slides_server/src/jobs/generate.rs
Dependencies: None (can be done in parallel)

Fetch GROQ_API_KEY and OPENROUTER_API_KEY from hero_proc secrets (falling back to env), pass them explicitly as env vars in submit_voice_transcribe_job.

Step 6 — Improve error message when keys are missing

File: crates/hero_slides/src/main.rs
Dependencies: None (independent)

Pre-check GROQ_API_KEY before attempting transcription; emit a clear error to stderr so the log tail contains a useful message.


Acceptance Criteria

  • Pressing Record, recording voice, pressing Stop results in transcribed text appearing in the editor textarea
  • Instruct → Record → Stop results in transcribed text appearing in the instruct input
  • The Create Slide voice recording flow populates the intent field
  • When GROQ_API_KEY is missing, the error shown is human-readable (not "exit code Some(1)")
  • agent.aiJobResult returns { state: "done", result: { raw: "...", cleaned: "..." } } for a completed voice job
  • Existing content/instruct/wizard job flows are unaffected

Notes

  • Steps 1–4 and Steps 5–6 can all be started in parallel — no cross-step dependencies at compile time.
  • All three JS callers already use result.cleaned || result.raw — no frontend changes needed after fixing the server payload.
  • OPENROUTER_API_KEY is needed for the LLM cleanup step; both keys must be passed.
  • The legacy voice.transcribeResult RPC reads directly from disk and returns { raw, cleaned } correctly — it is not affected by this fix.
## Implementation Spec: Fix Voice Recording — Issue #76 ### Root Cause Analysis **Bug A — Subprocess exits with code 1 ("exit code Some(1)")** `submit_voice_transcribe_job` calls the job with no env vars. The subprocess `hero_slides voice transcribe` calls `groq_provider()` which reads `GROQ_API_KEY` from 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_async` registers the job using `content_result_inputs(...)`, which sets `__result_kind = "content"`. The manager reads `transcript.json` as raw text and stores it as `JobResultPayload::Content { text: "<json string>" }`. `handle_ai_job_result` then returns `{ state: "done", result: { content: "..." } }`. All three JavaScript entry points expect `result.cleaned` (or `result.cleaned || result.raw`). Since the result shape is `result.content` (a raw JSON string), `result.cleaned` is `undefined` — 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_result` with `state: "done"` and `result.content` containing the JSON string, but `result.cleaned` is `undefined`. --- ### Requirements - The `voice.transcribeAsync` job must receive `GROQ_API_KEY` and `OPENROUTER_API_KEY`, fetched from hero_proc secrets or the server's own environment, and passed explicitly in the job's env vars. - The `agent.aiJobResult` response for a completed voice transcription job must include `result.raw` and `result.cleaned` fields directly accessible to the JavaScript callers. - The manager must represent the transcript `{ raw, cleaned }` shape as a new `Transcript` variant of `JobResultPayload`. - When API keys are missing, the error message surfaced to the user must be human-readable (not `"exit code Some(1)"`). --- ### Files to Modify | File | Change | |---|---| | `crates/hero_slides_server/src/jobs/types.rs` | Add `Transcript { raw: String, cleaned: String }` variant to `JobResultPayload` | | `crates/hero_slides_server/src/jobs/rpc.rs` | Handle `JobResultPayload::Transcript` in `handle_ai_job_result` → return `{ raw, cleaned }` | | `crates/hero_slides_server/src/jobs/manager.rs` | Handle `__result_kind = "transcript"` in `maybe_spawn_result_loader` | | `crates/hero_slides_server/src/jobs/generate.rs` | Add `transcript_result_inputs` helper; use it; pass API key env vars to the job | | `crates/hero_slides/src/main.rs` | Pre-validate `GROQ_API_KEY` before transcription and emit a clear error | --- ### Implementation Plan #### Step 1 — Add `Transcript` variant to `JobResultPayload` **File:** `crates/hero_slides_server/src/jobs/types.rs` **Dependencies:** None Add to the `JobResultPayload` enum: ```rust Transcript { raw: String, cleaned: String }, ``` #### Step 2 — Handle `Transcript` in `handle_ai_job_result` **File:** `crates/hero_slides_server/src/jobs/rpc.rs` **Dependencies:** Step 1 Add match arm returning `{ "raw": raw, "cleaned": cleaned }`. #### Step 3 — Handle `__result_kind = "transcript"` in the result loader **File:** `crates/hero_slides_server/src/jobs/manager.rs` **Dependencies:** Step 1 Parse the JSON file, extract `raw`/`cleaned`, store as `JobResultPayload::Transcript`. #### Step 4 — Add `transcript_result_inputs` and use it **File:** `crates/hero_slides_server/src/jobs/generate.rs` **Dependencies:** None (can be done in parallel with Steps 1–3) Add helper that sets `__result_kind = "transcript"` and `__output_file`. Replace the `content_result_inputs` call in `handle_voice_transcribe_async`. #### Step 5 — Fetch API keys and pass them to the voice transcription job **File:** `crates/hero_slides_server/src/jobs/generate.rs` **Dependencies:** None (can be done in parallel) Fetch `GROQ_API_KEY` and `OPENROUTER_API_KEY` from hero_proc secrets (falling back to env), pass them explicitly as env vars in `submit_voice_transcribe_job`. #### Step 6 — Improve error message when keys are missing **File:** `crates/hero_slides/src/main.rs` **Dependencies:** None (independent) Pre-check `GROQ_API_KEY` before attempting transcription; emit a clear error to stderr so the log tail contains a useful message. --- ### Acceptance Criteria - [ ] Pressing Record, recording voice, pressing Stop results in transcribed text appearing in the editor textarea - [ ] Instruct → Record → Stop results in transcribed text appearing in the instruct input - [ ] The Create Slide voice recording flow populates the intent field - [ ] When `GROQ_API_KEY` is missing, the error shown is human-readable (not `"exit code Some(1)"`) - [ ] `agent.aiJobResult` returns `{ state: "done", result: { raw: "...", cleaned: "..." } }` for a completed voice job - [ ] Existing content/instruct/wizard job flows are unaffected --- ### Notes - Steps 1–4 and Steps 5–6 can all be started in parallel — no cross-step dependencies at compile time. - All three JS callers already use `result.cleaned || result.raw` — no frontend changes needed after fixing the server payload. - `OPENROUTER_API_KEY` is needed for the LLM cleanup step; both keys must be passed. - The legacy `voice.transcribeResult` RPC reads directly from disk and returns `{ raw, cleaned }` correctly — it is not affected by this fix.
Member

Test Results

Status: Passed

  • Total: 182 (2 ignored)
  • Passed: 180
  • Failed: 0
  • Ignored: 2 (test_generate_single_slide_ai - requires live AI)

Breakdown by crate:

Crate Tests Result
hero_slides_lib (unit) 111 ok
hero_slides_lib (deck_safety_test) 6 ok
hero_slides_server (unit) 23 ok
hero_slides_rhai (integration) 13 passed, 1 ignored ok
hero_slides_server (integration) 13 passed, 1 ignored ok
doc-tests (hero_slides_lib) 2 ok
doc-tests (hero_slides_rhai) 1 ok

cargo check also passed with no warnings or errors.

## Test Results **Status:** Passed - Total: 182 (2 ignored) - Passed: 180 - Failed: 0 - Ignored: 2 (test_generate_single_slide_ai - requires live AI) **Breakdown by crate:** | Crate | Tests | Result | |---|---|---| | hero_slides_lib (unit) | 111 | ok | | hero_slides_lib (deck_safety_test) | 6 | ok | | hero_slides_server (unit) | 23 | ok | | hero_slides_rhai (integration) | 13 passed, 1 ignored | ok | | hero_slides_server (integration) | 13 passed, 1 ignored | ok | | doc-tests (hero_slides_lib) | 2 | ok | | doc-tests (hero_slides_rhai) | 1 | ok | `cargo check` also passed with no warnings or errors.
Member

Implementation Summary

Two root causes were identified and fixed.

Root Cause A — Subprocess exits with code 1

submit_voice_transcribe_job previously passed no env vars to the job subprocess. The hero_slides voice transcribe process reads GROQ_API_KEY and OPENROUTER_API_KEY from 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_job now reads GROQ_API_KEY and OPENROUTER_API_KEY from the server process environment and passes them explicitly to the subprocess as env vars.

Additionally, the voice transcribe CLI subcommand now pre-validates GROQ_API_KEY before 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_async used content_result_inputs(...) which sets __result_kind = "content". The manager then stored the entire transcript.json as a raw string in JobResultPayload::Content. handle_ai_job_result returned { content: "<json string>" }. All three JavaScript callers expect result.cleaned (or result.cleaned || result.raw) — result.content is never read, so the editor was never populated.

Fix:

  • Added JobResultPayload::Transcript { raw: String, cleaned: String } variant to types.rs
  • Added handle_ai_job_result match arm that returns { "raw": raw, "cleaned": cleaned } in rpc.rs
  • Added "transcript" arm to the result loader in manager.rs that parses the JSON file and stores the two fields
  • Added transcript_result_inputs helper in generate.rs that sets __result_kind = "transcript", replacing the content_result_inputs call in handle_voice_transcribe_async

Files Modified

  • crates/hero_slides_server/src/jobs/types.rs — new Transcript variant
  • crates/hero_slides_server/src/jobs/rpc.rs — handle Transcript in handle_ai_job_result
  • crates/hero_slides_server/src/jobs/manager.rs — parse transcript JSON in result loader
  • crates/hero_slides_server/src/jobs/generate.rstranscript_result_inputs helper, API key env var injection
  • crates/hero_slides/src/main.rs — pre-validate GROQ_API_KEY with clear error message

Test Results

180 tests passed, 0 failed, 2 ignored (require live AI backend).

## Implementation Summary Two root causes were identified and fixed. ### Root Cause A — Subprocess exits with code 1 `submit_voice_transcribe_job` previously passed no env vars to the job subprocess. The `hero_slides voice transcribe` process reads `GROQ_API_KEY` and `OPENROUTER_API_KEY` from 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_job` now reads `GROQ_API_KEY` and `OPENROUTER_API_KEY` from the server process environment and passes them explicitly to the subprocess as env vars. Additionally, the `voice transcribe` CLI subcommand now pre-validates `GROQ_API_KEY` before 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_async` used `content_result_inputs(...)` which sets `__result_kind = "content"`. The manager then stored the entire `transcript.json` as a raw string in `JobResultPayload::Content`. `handle_ai_job_result` returned `{ content: "<json string>" }`. All three JavaScript callers expect `result.cleaned` (or `result.cleaned || result.raw`) — `result.content` is never read, so the editor was never populated. **Fix:** - Added `JobResultPayload::Transcript { raw: String, cleaned: String }` variant to `types.rs` - Added `handle_ai_job_result` match arm that returns `{ "raw": raw, "cleaned": cleaned }` in `rpc.rs` - Added `"transcript"` arm to the result loader in `manager.rs` that parses the JSON file and stores the two fields - Added `transcript_result_inputs` helper in `generate.rs` that sets `__result_kind = "transcript"`, replacing the `content_result_inputs` call in `handle_voice_transcribe_async` ### Files Modified - `crates/hero_slides_server/src/jobs/types.rs` — new `Transcript` variant - `crates/hero_slides_server/src/jobs/rpc.rs` — handle `Transcript` in `handle_ai_job_result` - `crates/hero_slides_server/src/jobs/manager.rs` — parse transcript JSON in result loader - `crates/hero_slides_server/src/jobs/generate.rs` — `transcript_result_inputs` helper, API key env var injection - `crates/hero_slides/src/main.rs` — pre-validate `GROQ_API_KEY` with clear error message ### Test Results 180 tests passed, 0 failed, 2 ignored (require live AI backend).
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_slides#76
No description provided.