{"record":{"id":"9e5f3b3048f7e6bd","repo":"gitroomhq/postiz-app","slug":"only-scheduled-posts-that-were-not-published-yet","errorCode":null,"errorMessage":"Only scheduled posts that were not published yet (or drafts) can be updated","messagePattern":"Only scheduled posts that were not published yet \\(or drafts\\) can be updated","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts","lineNumber":998,"sourceCode":"    settings: Record<string, any>,\n    creationMethod: CreationMethod\n  ): Promise<{ postId: string; publishDate: string }> {\n    // Ordered as post -> comments, root includes integration and tags.\n    const ordered = await this.getPostsRecursively(postId, true, orgId, true);\n\n    const [root] = ordered;\n    if (!root) {\n      throw new NotFoundException('Post not found');\n    }\n\n    if (root.parentPostId) {\n      throw new BadRequestException(\n        'This id belongs to a comment, pass the id of the main post'\n      );\n    }\n\n    if (root.state !== 'QUEUE' && root.state !== 'DRAFT') {\n      throw new BadRequestException(\n        'Only scheduled posts that were not published yet (or drafts) can be updated'\n      );\n    }\n\n    if (\n      root.state === 'QUEUE' &&\n      dayjs.utc(root.publishDate).isBefore(dayjs.utc())\n    ) {\n      throw new BadRequestException(\n        'The publish time of this post already passed, it cannot be updated'\n      );\n    }\n\n    const integration = (root as any).integration;\n\n    let existingSettings: Record<string, any>;\n    try {\n      existingSettings = JSON.parse(root.settings || '{}');","sourceCodeStart":980,"sourceCodeEnd":1016,"githubUrl":"https://github.com/gitroomhq/postiz-app/blob/0f1647f7491a217d43eb5ae7a480484bdf0aff3e/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts#L980-L1016","documentation":"Only posts in state QUEUE (scheduled, not yet published) or DRAFT can be updated. Once a post transitions to PUBLISHED (or another terminal state) the update path refuses it, preventing edits to already-delivered content.","triggerScenarios":"Calling updatePost on a post whose state is PUBLISHED, ERROR, or any state other than QUEUE/DRAFT — e.g. editing a post that has just gone out while the calendar was still open.","commonSituations":"Race between scheduler publishing the post and the user clicking save; stale calendar state after a websocket refresh; attempting to 'edit history' of published posts via the API.","solutions":["Refresh the post state; if it is PUBLISHED, create a new post instead of updating","For queue/draft posts, retry the update after confirming state via getPostById","If editing published content is truly needed, clone the post and schedule it anew","Subscribe to post state change events so the UI locks editing when state leaves QUEUE/DRAFT"],"exampleFix":"// before\nawait updatePost(orgId, postId, body);\n// after\nconst post = await getPostById(orgId, postId);\nif (post.state === 'QUEUE' || post.state === 'DRAFT') {\n  await updatePost(orgId, postId, body);\n} else {\n  await createPost(orgId, { ...body, type: 'schedule' }); // new post\n}","handlingStrategy":"validation","validationCode":"const post = await getPostById(orgId, postId);\nconst editable = post?.state === 'QUEUE' || post?.state === 'DRAFT';\nif (editable) await updatePost(orgId, postId, body);","typeGuard":"const isEditableState = (s: string): s is 'QUEUE' | 'DRAFT' => s === 'QUEUE' || s === 'DRAFT';","tryCatchPattern":null,"preventionTips":["Lock the editor when the post's state leaves QUEUE/DRAFT","Refetch state right before save on long-lived editor sessions"],"tags":["posts","state-machine","published","update"],"backgroundTag":"invalid-state-transition","analyzedSha":"0f1647f7491a217d43eb5ae7a480484bdf0aff3e","analyzedAt":"2026-08-27T12:09:55.020Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}