Cancelling a task that is not running: upstream answers 500, we answer 204 #125

Closed
opened 2026-08-14 00:56:37 +02:00 by Claude · 1 comment
Collaborator

Measured against the reference while implementing #95:

DELETE /ScheduledTasks/Running/{id}   on an idle task
  upstream: 500 "Error processing request."
  ours:     204

ScheduledTaskWorker.Cancel() throws when the state is not Running, and nothing above it catches — so upstream's answer to "stop something that is not going" is a server error. Ours is silent success.

This is a decision to record, not obviously a bug to fix. The project has precedent both ways and the precedent is what should settle it: HomeSection and ItemDisplayPreferences are both cases where upstream's own behaviour was judged to have no value to agree with, and the divergence was written down with a reason and an ignore entry rather than reproduced. A 500 on a no-op looks like the same family — reproducing a crash to match a crash makes this server worse for every client that ever double-clicks Cancel.

What is needed is the decision made explicitly and recorded where a future diff will find it, rather than left as an undocumented difference somebody rediscovers. If the answer is "keep 204", the harness needs an entry naming it, with a reason and Never. If the answer is "match upstream", it needs to be 500 and a scenario has to carry it.

Note that #95 deliberately kept this request out of scenarios/tasks.json, so nothing currently reports the difference at all — which is the one outcome to avoid.

House rules

Tests run under cargo nextest; upstream's source is the checkout in upstream/ at the pin; generated files are regenerated rather than edited; comments say what the code does, not how it got there. crates/jellyfin-harness/README.md says what a diff means and what an ignore entry must carry. A server started off current main binds UDP 7359 — set [discovery] enabled = false in a scratch config when another one is running.

Measured against the reference while implementing #95: ``` DELETE /ScheduledTasks/Running/{id} on an idle task upstream: 500 "Error processing request." ours: 204 ``` `ScheduledTaskWorker.Cancel()` throws when the state is not `Running`, and nothing above it catches — so upstream's answer to "stop something that is not going" is a server error. Ours is silent success. **This is a decision to record, not obviously a bug to fix.** The project has precedent both ways and the precedent is what should settle it: `HomeSection` and `ItemDisplayPreferences` are both cases where upstream's own behaviour was judged to have no value to agree with, and the divergence was written down with a reason and an ignore entry rather than reproduced. A 500 on a no-op looks like the same family — reproducing a crash to match a crash makes this server worse for every client that ever double-clicks Cancel. What is needed is the decision made explicitly and recorded where a future diff will find it, rather than left as an undocumented difference somebody rediscovers. If the answer is "keep 204", the harness needs an entry naming it, with a reason and `Never`. If the answer is "match upstream", it needs to be 500 and a scenario has to carry it. Note that #95 deliberately kept this request out of `scenarios/tasks.json`, so nothing currently reports the difference at all — which is the one outcome to avoid. ### House rules Tests run under `cargo nextest`; upstream's source is the checkout in `upstream/` **at the pin**; generated files are regenerated rather than edited; comments say what the code does, not how it got there. `crates/jellyfin-harness/README.md` says what a diff means and what an ignore entry must carry. A server started off current `main` binds UDP 7359 — set `[discovery] enabled = false` in a scratch config when another one is running.
alex closed this issue 2026-08-16 09:28:23 +02:00
Author
Collaborator

Closed by de28417. The decision is "keep the 204", recorded as
DECISIONS.md D33.

Measured first: DELETE /ScheduledTasks/Running/{id} on an idle task is 500
text/plain upstream and 204 here, and an id nobody has is 404 on both.

The argument, in short: a cancel that finds nothing to cancel has done what it
was asked, the 500 is an unhandled exception rather than an answer upstream
chose, and nothing downstream can tell the two apart — the task's own state is
what a client reads next and both servers report it identically. Same family as
HomeSection and ItemDisplayPreferences.

What the issue asked for — that something reports it — took three pieces,
and the review sharpened all of them:

  • tasks.json asks for the cancel of an idle task. 12 responses, 0 differences.
  • One ignore-list entry names it, Never. The list grew a method field for it,
    because the same route starts a task on POST and both servers agree at 204
    there; the entry also names the fixture's task id rather than starring it, so
    refusals.json's DELETE on a task nobody has goes on being diffed.
  • That scenario request cannot fail, and the entry says so. Its whole
    observable surface is the three fields the entry hides. It is there to put the
    entry in a run's accounting — an entry nobody sees is an entry nobody retires —
    and the check that can fail is in jellyfin-http's routes.rs: the 204 for
    an idle task, the 404 for an unknown id, and that the task is left as it was
    found.
Closed by `de28417`. **The decision is "keep the `204`"**, recorded as `DECISIONS.md` D33. Measured first: `DELETE /ScheduledTasks/Running/{id}` on an idle task is `500` `text/plain` upstream and `204` here, and an id nobody has is `404` on both. The argument, in short: a cancel that finds nothing to cancel has done what it was asked, the `500` is an unhandled exception rather than an answer upstream chose, and nothing downstream can tell the two apart — the task's own state is what a client reads next and both servers report it identically. Same family as `HomeSection` and `ItemDisplayPreferences`. **What the issue asked for — that something reports it — took three pieces**, and the review sharpened all of them: - `tasks.json` asks for the cancel of an idle task. 12 responses, 0 differences. - One ignore-list entry names it, `Never`. The list grew a `method` field for it, because the same route starts a task on `POST` and both servers agree at `204` there; the entry also names the fixture's task id rather than starring it, so `refusals.json`'s `DELETE` on a task nobody has goes on being diffed. - **That scenario request cannot fail, and the entry says so.** Its whole observable surface is the three fields the entry hides. It is there to put the entry in a run's accounting — an entry nobody sees is an entry nobody retires — and the check that can fail is in `jellyfin-http`'s `routes.rs`: the `204` for an idle task, the `404` for an unknown id, and that the task is left as it was found.
Sign in to join this conversation.
No description provided.