fix(batches): results survive input-file deletion, 410 once output/error files are deleted - #1741
Conversation
Deploying control-layer with
|
| Latest commit: |
087f252
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://8b1e367b.control-layer.pages.dev |
| Branch Preview URL: | https://fix-batch-results-deleted-in.control-layer.pages.dev |
There was a problem hiding this comment.
🟡 Changes recommended
The newly added 410 response message formatting includes unintended indentation whitespace due to a \-continued string literal, which should be corrected before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR updates the GET /ai/v1/batches/{batch_id}/results handler to return 410 Gone when a batch’s input file has been deleted (leaving file_id = NULL), avoiding an incorrect 500 for a permanent, caller-caused state and documenting the behavior in the OpenAPI annotation.
Changes:
- Add an early
batch.file_id.is_none()guard in the batch results handler that returnsError::Gone(HTTP 410) with guidance to useoutput_file_id/error_file_id. - Extend the OpenAPI endpoint documentation to include a 410 response.
- Add a regression test ensuring both streaming and paginated results paths return 410 after the batch input file is deleted, while
GET /batches/{id}still returns 200.
File summaries
| File | Description |
|---|---|
| dwctl/src/api/handlers/batches.rs | Adds a 410 Gone handler short-circuit for missing batch file_id, updates OpenAPI responses, and adds a focused test covering the new behavior. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| message: format!( | ||
| "Results for batch {} are no longer available because its input file was deleted. \ | ||
| Download `output_file_id` or `error_file_id` instead.", | ||
| batch_id_str | ||
| ), |
There was a problem hiding this comment.
Good catch — this was addressed by rewriting the message construction to a single-line format! string (no \\-continuation, so no embedded indentation spaces can leak into the 410 detail). We also updated the related comment in delete_file (files.rs) to accurately describe the soft-delete behavior: non-terminal batches are cancelled and unlinked, output_file_id/error_file_id are NULLed, and the file is retained for audit, so existing batch results survive file deletion.
Deleting an input file intentionally keeps the batch but unlinks it
(file_id = NULL) and lets the orphan-purge daemon remove its request
templates. The results view on GET /ai/v1/batches/{id}/results required the
file to walk templates in line order, so it failed with a 500 ("Batch has no
associated file_id") even though every request row is self-contained and
the output/error file streams keep working.
Make the results stream tolerate a missing file: with a file, walk templates
in line order as before; without one, walk the batch's requests in creation
order and left-join whatever template still exists. input_body is null once
the template is gone; custom_id, model, status, response and error are
unaffected. Same search/status filters and keyset pagination on both paths.
48622a7 to
84a7716
Compare
The output and error files are virtual and created with the batch, so a missing output_file_id or error_file_id can only mean the user deleted that file. Deleting a results file is the user's statement that those results should be gone; the results endpoint used to serve the same request rows anyway. Answer 410 Gone naming the deleted file instead, on both the paginated and streaming paths. A deleted input file is unchanged: results still stream with input_body null. Handler tests that hand-insert batches now attach the virtual files the way create_batch does.
…ng error The batches PR made get_batch_results_stream walk a batch's requests directly when file_id is NULL, so results remain streamable after the input file row is deleted. Rewrite the stale test that pinned the old 'Batch has no associated file_id' error to assert the new behavior: results stream before and after deleting the input file, while output/ error-file deletion still yields 410.
…ile with upstream merge by pjb157)
🤖 I have created a release *beep* *boop* --- ## [11.11.0](v11.10.2...v11.11.0) (2026-09-15) ### Features * **onwards:** fail over streamed requests whose first token stalls ([#1780](#1780)) ([2418bf8](2418bf8)) ### Bug Fixes * **batches:** results survive input-file deletion, 410 once output/error files are deleted ([#1741](#1741)) ([144c1e4](144c1e4)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
GET /ai/v1/batches/{batch_id}/resultsmishandled deleted files in two directions.Input file deleted → was a 500, now served. Deleting an input file intentionally keeps the batch but unlinks it (
file_id = NULL) and lets the orphan-purge daemon remove its request templates. The results view required the file so it could walk templates in line order, and failed withBatch has no associated file_id, even though every request row is self-contained. Now:LIMIT 1probe, same asget_request_detail).input_bodyisnullonce the template is gone;custom_id,model,status,response_bodyanderrorare unaffected.Search/status filters, keyset pagination and the live + archive union apply on both paths.
BatchResultItem.input_bodykeeps its type; its doc comment now says it can benull.Output or error file deleted → was served anyway, now 410. Those files are virtual and created with the batch, so a missing
output_file_id/error_file_idcan only mean the user deleted it. Deleting a results file is a statement that those results should be gone, and the endpoint used to hand back the same rows regardless. It now returns 410 Gone naming the deleted file(s), on both the paginated and streaming paths. The batch itself stays readable.Test plan
test_batch_results_served_after_input_file_deleted:file_id = NULLwith danglingtemplate_ids returns 200 on/resultsand/results?limit=10, requests in creation order withinput_body: null, completed/failed fields intact,X-Last-Linecorrect,?status=failedstill filters.test_batch_results_gone_after_results_file_deleted: output file, error file, or both deleted returns 410 on both paths with a message naming the file;GET /batches/{id}still 200.create_batchdoes. All 67api::handlers::batchestests pass.test_batch_results_stream_serves_archived_batchesand thetemplate_generation_migrationintegration tests pass.cargo clippy -p fusillade-arsenal -p dwctl --testsclean for the changed code..sqlxmetadata updated for the changedbatcheslookup. Locallycargo sqlx prepare --checkalso flags an unrelated, untouched query file (nullability drift against my local DB); leaving that to CI's fresh database.🤖 Generated with Claude Code