Skip to content

[Bug] Task editor gets stuck on "Saving..." when PATCH returns 400 {"error":"No updates provided"} #505

@cjpxyz

Description

@cjpxyz

Summary

When editing a task in Mission Control, the UI can get stuck showing "Saving..." if the frontend sends a task PATCH request that contains no actual changes.

The backend responds quickly with:

{"error":"No updates provided"}

but the frontend appears not to clear its saving state for this case, leaving the UI in a perpetual loading state.


Impact

This makes task editing look broken even though the backend is healthy and other writes are succeeding.

It creates the impression that:

  • the request is hanging
  • the task was not saved
  • Mission Control is unstable

In reality, this appears to be a frontend state-handling bug around no-op updates.


Reproduction

API-level reproduction

If a task is already in testing status, sending the same PATCH again returns:

PATCH /api/tasks/:id
Content-Type: application/json

{
"status": "testing"
}

Response:

400 Bad Request
{"error":"No updates provided"}

An empty patch body behaves the same way:

PATCH /api/tasks/:id
Content-Type: application/json

{}

Response:

{"error":"No updates provided"}

UI-level reproduction

  1. Open a task detail page
  2. Trigger save with no meaningful changes, or cause the frontend to build an empty/no-op patch
  3. Frontend sends PATCH /api/tasks/:id
  4. Backend returns 400 {"error":"No updates provided"}
  5. UI remains stuck on "Saving..."

Actual behavior

  • Backend returns quickly
  • Request does not hang
  • UI remains in saving/loading state

Expected behavior

Any of the following would be acceptable:

Option A: Do not send empty patches

If the computed patch is empty, the frontend should:

  • skip the request
  • immediately clear saving state
  • optionally show a subtle "No changes to save" message

Option B: Handle the backend response gracefully

If the backend returns:

{"error":"No updates provided"}

the frontend should:

  • clear saving state
  • avoid leaving the UI in a perpetual loading state
  • optionally show a non-fatal notice

Option C: Make backend no-op updates idempotent

The backend could return 200 OK for no-op task updates, e.g.:

{ "ok": true, "unchanged": true }

This would make CLI/UI/automation flows easier to handle consistently.


Evidence collected

Backend is healthy

Direct API calls to Mission Control succeed and respond quickly.

Observed behavior:

  • GET /api/tasks/:id200 OK
  • task reads return in ~20ms
  • authenticated writes succeed when there is a real change

Auth is working

Using:

Authorization: Bearer <MC_API_TOKEN>

the following operations succeeded:

  • create activity
  • create deliverable
  • patch task status (when status actually changes)

Critical error payload

For no-op task updates, the backend returns:

{"error":"No updates provided"}

This strongly suggests the frontend is not handling this response path correctly.


Likely root cause

High probability frontend state-management issue:

  1. UI sets saving = true
  2. frontend builds a patch object
  3. patch is empty or equivalent to current values
  4. request is still sent
  5. backend returns 400 {"error":"No updates provided"}
  6. error path does not properly reset saving state
  7. UI remains stuck on "Saving..."

Suggested fix

Frontend minimal fix

  1. Prevent empty PATCH requests
  • compare original task vs edited task
  • if diff is empty, do not send request
  1. Always clear saving state in finally
  • success, failure, and no-op cases should all exit loading state
  1. Treat No updates provided as a harmless no-op
  • do not leave the page in an error-loading limbo

Example approach:

const patch = buildTaskPatch(originalTask, editedTask)

if (Object.keys(patch).length === 0) {
setSaving(false)
return
}

try {
await updateTask(taskId, patch)
} catch (err) {
if (getErrorMessage(err).includes("No updates provided")) {
// treat as harmless no-op
} else {
throw err
}
} finally {
setSaving(false)
}

Optional backend improvement

Consider making no-op task updates idempotent instead of returning 400.

For example:

{ "ok": true, "unchanged": true }

This would improve:

  • automation ergonomics
  • CLI stability
  • frontend simplicity
  • consistency with agent-driven workflows

Acceptance criteria

  • Saving a task with no actual changes does not leave the UI stuck on "Saving..."
  • Empty/no-op patches are either:
  • not sent at all, or
  • handled gracefully after response
  • Saving state is always cleared
  • Repeated save clicks do not leave the UI in a broken loading state

Related observation

Deliverables currently appear to be append-only and non-idempotent:

  • repeated POSTs can create duplicate deliverable records
  • no delete route was discovered from the exposed API surface

That is separate from this bug, but it reinforces the need for more defensive frontend request handling around retries and duplicate submissions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions