{"record":{"id":"3d6310ca00635d93","repo":"paperclipai/paperclip","slug":"we-couldn-t-confirm-whether-this-comment-was-saved-review","errorCode":null,"errorMessage":"We couldn’t confirm whether this comment was saved. Review the conversation before starting another draft.","messagePattern":"We couldn’t confirm whether this comment was saved\\. Review the conversation before starting another draft\\.","errorType":"exception","errorClass":"CommentSubmissionUnknownError","httpStatus":null,"severity":"warning","filePath":"ui/src/api/issues.ts","lineNumber":60,"sourceCode":"  if (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n  const row = value as Record<string, unknown>;\n  const uuid = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i;\n  return (\n    typeof row.id === \"string\" &&\n    uuid.test(row.id) &&\n    typeof row.issueId === \"string\" &&\n    uuid.test(row.issueId) &&\n    typeof row.body === \"string\"\n  );\n}\n\nasync function confirmedCommentResponse<T>(\n  request: Promise<T>,\n  valid: (response: T) => boolean,\n): Promise<T> {\n  try {\n    const response = await request;\n    if (!valid(response)) throw new CommentSubmissionUnknownError();\n    return response;\n  } catch (error) {\n    // A rejected request is distinct from a lost/invalid receipt. A server 5xx\n    // may also happen after commit; it cannot establish that nothing was saved.\n    if (error instanceof ApiError && error.status < 500) throw error;\n    throw new CommentSubmissionUnknownError();\n  }\n}\n\nexport type IssueUpdateResponse = Issue & {\n  comment?: IssueComment | null;\n  changes: IssueChanges;\n  blockedByIssueIds?: string[];\n};\n\nexport type ResolveRecoveryActionResponse = {\n  issue: Issue;\n  recoveryAction: IssueRecoveryAction;","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/ui/src/api/issues.ts#L42-L78","documentation":"confirmedCommentResponse wraps comment-submission promises; if the response doesn't pass the validity check (e.g. no comment object in the payload), it throws CommentSubmissionUnknownError, whose message warns the user the save cannot be confirmed. This is the 'lost receipt' path: the request may have committed server-side but the response was unusable.","triggerScenarios":"A comment POST completes with an OK response whose body fails the valid() predicate — e.g. the update response contains no comment field, or a transformed payload is missing the expected issue/comment shape.","commonSituations":"Network proxy stripped or truncated the response body after the write committed; server returned 200 with an unexpected payload shape after a partial failure; serialization drift between server and UI after an upgrade.","solutions":["Open the issue conversation and visually verify whether the comment actually saved before re-submitting (the message's instruction) to avoid duplicates.","Log the raw response body when valid() fails to identify the shape mismatch.","Reconcile client state with a follow-up GET of the issue's comments instead of resubmitting blindly.","Fix the server response contract if valid()'s predicate no longer matches the payload."],"exampleFix":"// before\nif (!valid(response)) throw new CommentSubmissionUnknownError();\n// after\nif (!valid(response)) {\n  console.warn(\"comment response failed validity check\", response);\n  throw new CommentSubmissionUnknownError();\n}","handlingStrategy":"fallback","validationCode":"// after an unknown-outcome comment submit, reconcile before retrying\nconst comments = await issuesApi.getComments(issueId);\nconst saved = comments.some(c => c.clientId === pendingClientId);","typeGuard":"function isUnknownCommentOutcome(e: unknown): e is CommentSubmissionUnknownError {\n  return e instanceof CommentSubmissionUnknownError;\n}","tryCatchPattern":"try {\n  await submitComment(payload);\n} catch (e) {\n  if (isUnknownCommentOutcome(e)) {\n    const saved = await issuesApi.getComments(issueId).catch(() => []);\n    if (!saved.some(c => c.clientId === payload.clientId)) {\n      showResubmitPrompt(payload); // safe to re-enter, nothing was saved\n    }\n    return;\n  }\n  throw e;\n}","preventionTips":["Attach a client-generated id to comments so save-state can be verified after unknown outcomes.","Always check the conversation before re-submitting an uncertain comment to avoid duplicates.","Add a contract test asserting the comment endpoint response always includes the comment object."],"tags":["ui","comments","idempotency"],"backgroundTag":"unexpected-response-shape","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}