Skip to content

OpenAICompatibleTranscriber: cancel only honored at remux boundary, not during HTTP request #177

Description

@utof

Problem

The cloud transcription adapter (crates/transcribe/src/openai_compat.rs::OpenAICompatibleTranscriber::transcribe) checks the CancellationToken only at two boundaries:

  1. Before/during ffmpeg remux (via AudioPipeline::remux_for_upload)
  2. After the HTTP call returns (the use-case can drop the result)

The actual upload + HTTP request itself is not cancellable — once the request is in-flight, calling cancel.cancel() has no effect until the response arrives. For a 50 MB audio file on a slow connection, that can mean tens of seconds of unresponsive UI after the user clicks Cancel.

Why it's not in v1

async-openai's client wraps reqwest::Client but doesn't expose a per-request abort handle. The plumbing options are:

  • Wrap each call in tokio::select! against cancel.cancelled() — drops the future, the underlying connection gets reset (or pooled), works but is awkward to test
  • Wrap with tokio::time::timeout — bounds the worst case but doesn't honor user-driven cancel
  • Fork async-openai to expose a cancel hook — high-friction
  • Switch to a hand-rolled reqwest multipart upload — loses the typed builder ergonomics

For v1 we accepted the "click Cancel; UI updates after current request finishes" UX. The user-facing impact is bounded because:

  • Average Groq turnaround is ~3s for 1-hour video (audio-only post-remux)
  • The use-case (T5) will not start a new transcribe after cancel, so subsequent items in the queue honor cancel immediately
  • Cancel during remux (the slowest local step for big files) IS honored

Suggested approach

Wrap the client.audio().transcription().create_verbose_json(request) await in tokio::select! against the cancel token. On cancel-fire, drop the future and return TranscriptionError::Cancelled. Add a wiremock test with a ResponseTemplate::new(200).set_delay(Duration::from_secs(10)) and assert cancel after 100ms aborts within < 200ms.

Definition of done

  • HTTP call honors cancel within ~50ms of cancel.cancel() being called
  • New wiremock test verifies the cancel-during-HTTP path
  • No regression to the existing happy-path / error-mapping coverage

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/cliTouches the perima CLI binarypriority/mediumDefault priority — address in normal flowtype/featureNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions