{"record":{"id":"a66d3c715bb0c3ef","repo":"tinyhumansai/openhuman","slug":"post-deletion-was-not-accepted-by-the-backend","errorCode":null,"errorMessage":"Post deletion was not accepted by the backend","messagePattern":"Post deletion was not accepted by the backend","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/src/agentworld/pages/FeedSection.tsx","lineNumber":899,"sourceCode":"    }\n  };\n\n  // ── Delete post ────────────────────────────────────────────────────────────\n\n  // Open the in-app confirm modal; the actual delete runs in `confirmDeletePost`\n  // only after the user confirms (replaces the native window.confirm — #4197).\n  const handleDeletePost = (post: GqlPost) => {\n    setPostPendingDelete(post);\n  };\n\n  const confirmDeletePost = () => {\n    const post = postPendingDelete;\n    if (!post) return;\n    setDeletingPost(true);\n    void apiClient.feeds\n      .deletePost(post.postId)\n      .then(({ ok }) => {\n        if (!ok) throw new Error('Post deletion was not accepted by the backend');\n        // Return the refresh promise so its rejection reaches `.catch` (rather\n        // than resolving the delete as \"done\" before the feed is reloaded). A\n        // mutation invalidates offsets, so reset pagination to the first page.\n        return apiClient.graphql\n          .homeFeed({ limit: FEED_PAGE_SIZE, offset: 0, includeSelf: true })\n          .then(result => {\n            setFeedState(firstPageFeedState(result));\n          });\n      })\n      .catch(err => console.error('[FeedSection] delete post failed:', err))\n      .finally(() => {\n        setDeletingPost(false);\n        setPostPendingDelete(null);\n      });\n  };\n\n  // ── Refetch feed ───────────────────────────────────────────────────────────\n","sourceCodeStart":881,"sourceCodeEnd":917,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/agentworld/pages/FeedSection.tsx#L881-L917","documentation":"Thrown when the post-delete confirm flow calls apiClient.feeds.deletePost(post.postId) and it resolves with { ok: false } - the backend refused the deletion even though the request went through. Unlike the comment path, the happy path then chains a homeFeed refetch with pagination reset (offset: 0) because the mutation invalidates offsets; on ok:false that refetch never runs and the .catch only logs, so the post stays visible.","triggerScenarios":"User confirms post deletion in the feed and the backend answers ok:false - post already deleted elsewhere, actor not the author / lacking delete permission, or post.postId from a stale feed page that no longer exists server-side.","commonSituations":"Multiple sessions on the same feed; feed state stale after reconnect or thread switch; permission revocation on the backend between render and delete; soft-failure (200 + ok:false) backend convention hiding the refusal from the user.","solutions":["Reload the feed from the first page and retry only if the post is still present","Check that the user is the post author (or has moderation rights) for that feed","Inspect the deletePost response body in the network tab for a refusal reason","Mirror the comment path: on failure still trigger a feed refresh so the UI converges with the backend, and surface a toast"],"exampleFix":"// before\n.then(({ ok }) => {\n  if (!ok) throw new Error('Post deletion was not accepted by the backend');\n  return apiClient.graphql.homeFeed({ limit: FEED_PAGE_SIZE, offset: 0, includeSelf: true })...\n})\n.catch(err => console.error('[FeedSection] delete post failed:', err));\n\n// after - always resync after settling so a refused delete drops stale posts\n.catch(err => {\n  console.error('[FeedSection] delete post failed:', err);\n  setDeleteError(err instanceof Error ? err.message : String(err));\n  apiClient.graphql\n    .homeFeed({ limit: FEED_PAGE_SIZE, offset: 0, includeSelf: true })\n    .then(result => setFeedState(firstPageFeedState(result)))\n    .catch(refreshErr => console.error('[FeedSection] resync failed:', refreshErr));\n});","handlingStrategy":"try-catch","validationCode":"// Only offer delete for posts the user authored (or can moderate) and that\n// are still present in the current feed page.\nconst canDelete = post.author.handle === currentHandle && feedHasPost(post.postId);","typeGuard":null,"tryCatchPattern":"void apiClient.feeds.deletePost(post.postId)\n  .then(({ ok }) => {\n    if (!ok) throw new Error('Post deletion was not accepted by the backend');\n    return apiClient.graphql.homeFeed({ limit: FEED_PAGE_SIZE, offset: 0, includeSelf: true });\n  })\n  .then(result => setFeedState(firstPageFeedState(result)))\n  .catch(err => {\n    console.error('[FeedSection] delete post failed:', err);\n    setDeleteError(err instanceof Error ? err.message : String(err));\n    // still resync from page 0 - the mutation may have partially applied\n    apiClient.graphql.homeFeed({ limit: FEED_PAGE_SIZE, offset: 0, includeSelf: true })\n      .then(result => setFeedState(firstPageFeedState(result)))\n      .catch(() => {});\n  });","preventionTips":["Check the ok flag on deletePost - the promise resolving is not the deletion succeeding","Reset pagination to offset 0 after any feed mutation, success or failure","On failure, still refetch so the post list matches the backend instead of trusting local state"],"tags":["agentworld","feed","post","deletion","soft-failure","pagination"],"backgroundTag":"mutation-rejected-by-backend","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}