{"record":{"id":"cdfc63722170c2ee","repo":"HKUDS/DeepTutor","slug":"no-fields-to-update","errorCode":null,"errorMessage":"No fields to update","messagePattern":"No fields to update","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"deeptutor/api/routers/notebook.py","lineNumber":402,"sourceCode":"        raise\n    except NotebookCorruptedError as exc:\n        raise _unreadable(exc)\n    except Exception as e:\n        raise HTTPException(status_code=500, detail=str(e))\n\n\n@router.put(\"/{notebook_id}/records/{record_id}\")\nasync def update_record(notebook_id: str, record_id: str, request: UpdateRecordRequest):\n    \"\"\"Update an existing notebook record in place.\"\"\"\n    try:\n        # Forward only what the client actually sent. Passing every field\n        # unconditionally would hand `kb_name=None` to the service on every\n        # request and clear the record's knowledge-base link as a side effect\n        # of renaming it; the service's sentinel default only works if an\n        # omitted field never reaches it.\n        changes = request.model_dump(exclude_unset=True)\n        if not changes:\n            raise HTTPException(status_code=400, detail=\"No fields to update\")\n        updated = notebook_manager.update_record(\n            notebook_id=notebook_id,\n            record_id=record_id,\n            **changes,\n        )\n        if not updated:\n            raise HTTPException(status_code=404, detail=\"Record not found\")\n        return {\"success\": True, \"record\": updated}\n    except HTTPException:\n        raise\n    except NotebookCorruptedError as exc:\n        raise _unreadable(exc)\n    except Exception as e:\n        raise HTTPException(status_code=500, detail=str(e))\n\n\n@router.post(\"/{notebook_id}/records/{record_id}/copy\")\nasync def copy_record(notebook_id: str, record_id: str, request: MoveRecordRequest):","sourceCodeStart":384,"sourceCodeEnd":420,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/api/routers/notebook.py#L384-L420","documentation":"400 raised when UpdateRecordRequest.model_dump(exclude_unset=True) yields no fields — the client sent a PUT body with no keys (or only fields explicitly unset). The endpoint deliberately requires at least one changed field because the service uses sentinel defaults for omitted fields.","triggerScenarios":"PUT /{notebook_id}/records/{record_id} with an empty JSON object {} or a body where every field is unset.","commonSituations":"Frontend building the update payload from a diff that turns out empty; passing a pre-filled request model with exclude_unset semantics; schema change removing a field that the client still relies on to trigger updates.","solutions":["Include at least one field you actually intend to change in the request body","Skip the PUT call entirely when the client-side diff is empty","Check the request model definition to confirm field names"],"exampleFix":"// before\nawait client.put(f\"/notebook/{nb}/records/{rid}\", json={})\n// after\nawait client.put(f\"/notebook/{nb}/records/{rid}\", json={\"title\": \"New title\"})","handlingStrategy":"validation","validationCode":"changes = {k: v for k, v in diff.items() if v is not None}\nif not changes:\n    return  # nothing to update, skip PUT","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Short-circuit empty diffs in the client before calling the API","Send only fields that actually changed"],"tags":["notebook","http-400","validation","fastapi","pydantic"],"backgroundTag":"request-body-validation-failed","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}