Skip to content

fix(mcp): bound merge_recordings and upload_recording waits (#151)#185

Merged
massive-value merged 2 commits into
mainfrom
fix/151-bound-merge-upload-waits
Jul 7, 2026
Merged

fix(mcp): bound merge_recordings and upload_recording waits (#151)#185
massive-value merged 2 commits into
mainfrom
fix/151-bound-merge-upload-waits

Conversation

@massive-value

@massive-value massive-value commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Closes #151

What

Extends the soft-deadline pattern already added for process_recording's transcript/summary waits (mcp.py's _WAIT_TIMEOUT_S / _wait_or_still_processing, see the prior partial fix) to the two remaining unbounded blocking waits the issue calls out:

  • merge_recordings: client.merge_recordings already had a timeout_s kwarg (default 300s) — the MCP handler now passes timeout_s=_WAIT_TIMEOUT_S and, on the resulting soft-deadline PlaudApiError, returns {"recording_ids": ..., "title": ..., "status": "still_processing"} instead of blocking the handler thread for the full window. The merge task keeps running server-side (it's task_id-backed, same shape as transcription/summary), so "still_processing" is an accurate status here.
  • upload_recording: the multipart S3 chunk loop in client.py had no overall bound (only a per-part 120s ceiling). Added an optional timeout_s param, checked once per chunk, threaded through transcode.upload_with_transcode so the MCP facade can opt in (timeout_s=_WAIT_TIMEOUT_S) without changing the CLI's call site — None (the CLI default) is fully unbounded, preserving existing behavior exactly.

Also extracted _is_soft_deadline_timeout() out of _wait_or_still_processing so all three call sites (transcript/summary wait, merge, upload) share one "is this our soft-deadline timeout" check instead of duplicating the http_status is None and "timed out" in str(exc) test.

Important caveat (called out in code comments)

Unlike merge/transcription/summary, an aborted upload_recording has no background job to check back on — nothing was created on Plaud's side yet, so a timed-out upload is genuinely lost and must be retried, not polled. The response still uses the still_processing-shaped envelope for consistency with the other bounded waits, but this is a real semantic difference worth flagging for review.

Scope: soft deadline is the accepted resolution (cancel-on-disconnect is YAGNI)

The 90s soft deadline is the settled fix for #151, so this PR fully closes the issue on merge. True cancellation on client disconnect (wiring a threading.Event through server.py's asyncio.to_thread so an orphaned handler stops mid-poll/mid-upload) is a deliberate YAGNI decision (settled 2026-07-07), not a pending follow-up: the ~90s cap is already below the exe-lock contention window the updater fights, so the Event path buys nothing measurable. It's documented in the code (ponytail: note at mcp.py's _WAIT_TIMEOUT_S) and ADR-006 purely as an upgrade path should orphan telemetry ever show 90s still matters.

Test plan

  • python -m pytest tests/ — 1025 passed, 2 skipped
  • python -m mypy src/plaud_tools — clean
  • python -m ruff check . / ruff format --check . — clean
  • Added TestMergeRecordingsSlimResponse.test_wait_timeout_returns_still_processing mirroring the existing TestProcessRecordingBoundedWait pattern

process_recording's transcript/summary waits already got a soft deadline
(_WAIT_TIMEOUT_S) so a disconnected MCP client can't orphan the process for
the full 600s poll window. merge_recordings (up to 300s poll) and
upload_recording (unbounded multipart S3 loop) still had no cap, so they
retained the full orphan window client.py:309/300 called out in the issue.

- mcp.py: pass timeout_s=_WAIT_TIMEOUT_S into client.merge_recordings
  (already had a timeout_s kwarg) and catch the soft-deadline PlaudApiError
  the same way process_recording does, returning a still_processing-shaped
  result with the source recording_ids/title (no merged id exists yet).
- client.py: add an optional timeout_s to upload_recording's multipart S3
  loop, checked once per chunk; None (the CLI's default) preserves the
  historical unbounded behavior exactly.
- transcode.py: thread timeout_s through upload_with_transcode so the MCP
  facade can opt in without touching the CLI's call site.
- mcp.py: upload_recording catches the same soft-deadline error and returns
  a still_processing-shaped result (title/filename, no recording_id yet —
  nothing was created). Noted in comments that, unlike merge/transcription,
  there's no background job continuing after the abort; the upload itself
  is lost and must be retried.
- Extracted _is_soft_deadline_timeout() out of _wait_or_still_processing so
  all three call sites share one timeout-detection check.

True cancellation on client disconnect (threading.Event wired through
server.py's asyncio.to_thread) remains out of scope, as before — tracked as
a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Maintainer decision (2026-07-07): the 90s soft deadline is the accepted
resolution for #151, not a stopgap. Reword the mcp.py comment and the ADR-006
note so neither reads as unfinished work:

- mcp.py: replace "true cancellation ... tracked as follow-up" with a
  ponytail: note stating the soft deadline is the accepted fix and the
  threading.Event cancel path is intentionally not implemented (YAGNI),
  naming it only as an upgrade path if orphan telemetry ever shows 90s matters.
- ADR-006: append a dated (2026-07-07) update line noting the soft deadline
  now covers process/merge/upload and full cancellation was deemed YAGNI and
  closed — not rewriting the original follow-up text, just settling it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@massive-value massive-value merged commit d0e851b into main Jul 7, 2026
16 checks passed
@massive-value massive-value deleted the fix/151-bound-merge-upload-waits branch July 7, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

plaud-mcp is orphaned after client disconnect during long-blocking handlers (up to ~20 min)

1 participant