{"record":{"id":"b8b588189c82c863","repo":"can1357/oh-my-pi","slug":"out-error-save-failed-res-status","errorCode":null,"errorMessage":"out?.error ?? `save failed (${res.status})`","messagePattern":"out\\?\\.error \\?\\? `save failed \\((.+?)\\)`","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/metaharness/src/web/app.tsx","lineNumber":262,"sourceCode":"function shortTask(task: string): string {\n\tconst base = task.slice(task.lastIndexOf(\"/\") + 1);\n\tconst us = base.lastIndexOf(\"__\");\n\treturn us >= 0 ? base.slice(us + 2) : base;\n}\n\n/** PUT experiment metadata: goal and/or per-run label/note/role. */\nasync function putExperimentMeta(\n\tid: string,\n\tbody: { goal?: string; runs?: Record<string, { role?: RunRole; note?: string; label?: string }> },\n): Promise<void> {\n\tconst res = await fetch(`/api/experiments/${encodeURIComponent(id)}`, {\n\t\tmethod: \"PUT\",\n\t\theaders: { \"content-type\": \"application/json\" },\n\t\tbody: JSON.stringify(body),\n\t});\n\tif (!res.ok) {\n\t\tconst out = (await res.json().catch(() => null)) as { error?: string } | null;\n\t\tthrow new Error(out?.error ?? `save failed (${res.status})`);\n\t}\n}\n\nfunction RoleTag({ role }: { role: RunRole }) {\n\treturn (\n\t\t<span\n\t\t\tclassName={`ml-2 rounded-full border px-1.5 text-[10px] ${role === \"baseline\" ? \"border-sky-500 text-sky-400\" : \"border-emerald-600 text-emerald-400\"}`}\n\t\t>\n\t\t\t{role}\n\t\t</span>\n\t);\n}\n\n// ── arm table sorting ────────────────────────────────────────────────────────\n\ntype SortKey = \"arm\" | \"note\" | \"status\" | \"progress\" | \"eta\" | \"pass\" | \"cost\" | \"time\";\n\ninterface SortSpec {","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/metaharness/src/web/app.tsx#L244-L280","documentation":"putExperimentMeta PUTs experiment metadata to the metaharness server. If the response is not ok, it throws with the server's JSON `error` field when present, otherwise a generic \"save failed (<status>)\" message. This is the client-side surface for any server-side rejection of a metadata save.","triggerScenarios":"PUT to the experiment metadata endpoint returning non-OK: validation rejected by the server (no error body), 404 for a nonexistent experiment id, 409 conflict, or a crashed handler producing an empty error body.","commonSituations":"Editing metadata for an experiment that was deleted server-side; sending fields the server schema rejects; concurrent edits producing conflicts; server bug returning non-JSON error body.","solutions":["If the message contains a server error string, fix the metadata field it names","Verify the experiment id still exists on the server (404 in the fallback message)","Retry after refreshing, in case of a transient conflict or stale dashboard state","Check server logs if the fallback 'save failed (status)' text appears — it means no error body was returned"],"exampleFix":"// before\nawait putExperimentMeta(id, { status: \"bogus-status\" });\n// after: send a schema-valid value and handle failure\ntry {\n  await putExperimentMeta(id, { status: \"running\" });\n} catch (e) {\n  showToast(`Save failed: ${e.message}`);\n}","handlingStrategy":"try-catch","validationCode":"if (!validStatuses.has(body.status)) {\n  throw new Error(`Invalid status \"${body.status}\" — rejected server-side before the PUT`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await putExperimentMeta(id, body);\n} catch (err) {\n  showToast(/save failed \\(\\d+\\)/.test(err.message)\n    ? `Server rejected the save (${err.message}); refresh and retry`\n    : `Save failed: ${err.message}`);\n  await reloadExperiment(id); // resync with server state\n}","preventionTips":["Validate edited fields against the server schema in the form before PUT","Confirm the experiment still exists before editing (refresh list)","Resync UI state from the server after any failed save"],"tags":["http","web","put"],"backgroundTag":"save-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}