How the Open Notebook REST API Handles Podcast Episode Retry on Failure
The Open Notebook REST API exposes a POST /podcasts/episodes/{episode_id}/retry endpoint that validates the failure state, cleans up orphaned audio files, deletes the old record, and submits a fresh generation job via the surreal-commands system.
The lfnovo/open-notebook repository provides a robust backend for podcast generation that includes dedicated recovery mechanisms for failed episodes. When a podcast generation job fails or encounters an error, the REST API offers a stateless retry mechanism that ensures clean state management before resubmitting the task. Understanding how this podcast episode retry flow works is essential for building reliable client integrations and debugging production failures.
Retry Endpoint Architecture
The retry functionality is implemented as a stateless HTTP endpoint that orchestrates cleanup and resubmission without relying on in-memory state.
Route Registration and Validation
The retry route is registered in api/routers/podcasts.py at lines 15-18:
@router.post("/episodes/{episode_id}/retry")
async def retry_episode(episode_id: str):
# Implementation handles validation and resubmission
When invoked, the handler first retrieves the persisted PodcastEpisode object via PodcastService.get_episode (defined in api/podcast_service.py lines 52-57). This ensures the retry operation works against the database record rather than any ephemeral cache.
State Verification
Before proceeding, the system validates that the episode is actually in a failed state. In api/routers/podcasts.py lines 21-27, the handler calls episode.get_job_detail() and checks that the job status is either "failed" or "error". If the episode is not in one of these terminal failure states, the API immediately returns a 400 Bad Request response, preventing accidental duplicate jobs for episodes that are still processing or completed successfully.
The Retry Workflow
Once validation passes, the retry mechanism executes a cleanup-and-resubmit pattern that ensures idempotent operations.
Cleaning Up Failed Artifacts
The handler extracts the original parameters—including profile names, episode name, and source content—from the stored episode record (lines 30-34 in api/routers/podcasts.py). It then performs critical cleanup operations:
-
File system cleanup: If an audio file exists on disk, the code removes it using
Path.unlink()(lines 41-48 inapi/routers/podcasts.py). This prevents orphaned media files from accumulating in storage. -
Database cleanup: The old episode record itself is deleted via
await episode.delete()(lines 50-52 inapi/routers/podcasts.py). This ensures the retry starts with a completely fresh slate.
Resubmitting the Generation Job
After cleanup, the same original parameters are passed to PodcastService.submit_generation_job (implemented in api/podcast_service.py lines 36-45 and 95-102). This method creates a new surreal-commands job via the submit_command function, which guarantees idempotent background processing and provides status tracking via get_command_status. The service returns a fresh job_id that the API includes in its response.
Error Handling and Safety Mechanisms
The retry endpoint implements specific HTTP status codes to provide clear client feedback. If any unexpected exception occurs during the retry process, the handler catches the error in lines 66-69 of api/routers/podcasts.py, logs the failure, and returns a generic 500 Internal Server Error.
The safety checks ensure that only episodes truly marked as failed can be retried, while the file system cleanup prevents storage leaks from partial generation attempts. Together, these mechanisms create a robust podcast episode retry system that maintains data integrity.
Implementation Example
To retry a failed episode from a client application:
import requests
episode_id = "12345"
url = f"http://localhost:5055/api/podcasts/episodes/{episode_id}/retry"
resp = requests.post(url)
if resp.status_code == 200:
data = resp.json()
print("Retry submitted, new job ID:", data["job_id"])
else:
print("Retry failed:", resp.status_code, resp.json())
After submitting the retry, poll the new job status using the returned ID:
job_id = "abcd-efgh"
status_url = f"http://localhost:5055/api/podcasts/jobs/{job_id}"
status = requests.get(status_url).json()
print("Job status:", status["status"])
Summary
- The retry endpoint (
POST /podcasts/episodes/{episode_id}/retry) inapi/routers/podcasts.pyprovides a dedicated route for recovering failed podcast generations. - State validation ensures only episodes with
"failed"or"error"status can be retried, returning400 Bad Requestfor invalid states. - Cleanup operations remove orphaned audio files via
Path.unlink()and delete the old episode record before creating a new one. - Job resubmission uses
PodcastService.submit_generation_jobto create a fresh surreal-commands background job with a newjob_id. - Error handling distinguishes between client errors (400) and server failures (500) with appropriate logging.
Frequently Asked Questions
What happens if I try to retry a podcast episode that hasn't failed?
The API returns a 400 Bad Request response. The handler checks episode.get_job_detail() and verifies the status is either "failed" or "error" before proceeding. This prevents accidental duplicate generation jobs for episodes that are still processing or have already completed successfully.
Does the retry endpoint delete the old audio file before creating a new one?
Yes. The implementation explicitly checks for existing audio files and removes them using Path.unlink() (lines 41-48 in api/routers/podcasts.py). It also deletes the old episode record via await episode.delete() before submitting the new generation job, ensuring no orphaned files remain in storage.
How does the retry mechanism ensure the new job uses the same parameters as the original?
The handler extracts the original profile names, episode name, and content from the stored episode record (lines 30-34 in api/routers/podcasts.py) before deleting it. These parameters are then passed unchanged to PodcastService.submit_generation_job, which creates the new surreal-commands job with identical configuration.
What error codes does the retry endpoint return?
The endpoint returns 400 Bad Request if the episode is not in a failed state, 200 OK with a new job_id on successful resubmission, and 500 Internal Server Error if any unexpected exception occurs during the cleanup or resubmission process.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →