Skip to content

feat(agent): update a post's settings via a tool and the public API#1752

Merged
nevo-david merged 2 commits into
feat/list-posts-toolfrom
feat/agent-update-posts
Jul 23, 2026
Merged

feat(agent): update a post's settings via a tool and the public API#1752
nevo-david merged 2 commits into
feat/list-posts-toolfrom
feat/agent-update-posts

Conversation

@giladresisi

@giladresisi giladresisi commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Why

Original requirement: a user had scheduled dozens of TikTok posts with the wrong settings and wanted them batch-updated by an agent. With postsListTool (from the base PR) to list the posts and postSettingsTool (this PR) to update each one, an agent can satisfy a single user request that reads as a batch update ("fix the settings on all my TikTok posts") by updating them one at a time.

On the public API side, this aligns the existing endpoints with the new agent tools and adds the update capability as a dedicated endpoint where it was missing.

This narrows the previously-closed #1689 to just a settings update (per the "there should be only a settings update" feedback): the update tool is settings-only, and listing was split into the base PR.

⚠️ Stacked PR

Based on feat/list-posts-tool (#1751, the List Posts PR) — review and merge that first. This PR's own diff is only the settings-update files; the update tool needs postsListTool to obtain the post id it operates on, so the two ship together.

Changes

  • PostsService.updatePostSettings(orgId, postId, settings, method) — the one shared path. Loads the post by (id, org), merges the passed keys into the existing provider settings (only the passed keys change), re-runs the same validation as the dashboard / public create route, and writes in place. Content and publish date are untouched, so the running publish workflow is not restarted (type: 'update'). Rejects a comment id, a published/past post, and settings that fail validation.
  • postSettingsTool (agent / MCP): thin wrapper over the service method; surfaces errors back to the agent so it can retry.
  • PUT /public/v1/posts/:id/settings: same service method, so tool and endpoint share one path (Controller → Service → Repository).
  • keepGroup: an out-of-band settings update keeps the post's group id stable (removed comments are swept by id) so a client holding the old group (an open calendar) isn't invalidated. The dashboard keeps its rotate-and-sweep behavior.
  • Agent prompt: settings changes to an existing post go through postSettingsTool (find the post with postsListTool first); the agent must not open the populated modal for an existing post (that only creates a new post — a duplicate); there is no delete capability.

Public API impact

  • New endpoint PUT /public/v1/posts/:id/settings. Purely additive.

Testing

Fully tested:

  • Public API — via the updated CLI + skill.
  • Agent tools — via the internal web-app agent and Claude Code (via the remote MCP): list the posts, then update each one's settings.

Related changes in other repos

  • postiz-docs: new Update Post Settings endpoint page + nav entry, and postSettingsTool in the MCP "Available Tools" list. (PR link to be added.)
  • postiz-agent: (PR link to be added.)

🤖 Generated with Claude Code

Adds a settings-only update path for a not-yet-published post (scheduled
or draft), reachable both by the agent (web-app chat / external MCP) and
by the public API:

- PostsService.updatePostSettings(orgId, postId, settings, method):
  loads the post by (id, org), merges the passed keys into the existing
  provider settings (only the passed keys change), runs the same
  server-side validation as the dashboard / public create route, and
  writes it in place. Content and publish date are left untouched, so
  the running publish workflow is not restarted (type "update"). Refuses
  a comment id, a published/past post, and settings that fail validation.
- postSettingsTool (agent / MCP): thin wrapper that turns the agent's
  [{key,value}] settings into an object and calls the service, surfacing
  errors back to the agent so it can retry.
- PUT /public/v1/posts/:id/settings: same service method, so the tool
  and the endpoint share one path (Controller -> Service -> Repository).

keepGroup: the update keeps the post's group id stable instead of
rotating it (removed comments are swept by id), so a client holding the
old group (an open calendar) is not invalidated by an out-of-band
settings change. The dashboard keeps its rotate-and-sweep behavior.

Agent prompt: settings changes to an existing post are applied directly
with postSettingsTool; the agent must not open the populated modal for
an existing post (that only creates a new post, duplicating it), and
there is no delete capability.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@postiz-contribution postiz-contribution Bot added the contribution:approved Approved contributor label Jul 20, 2026
@postiz-contribution

Copy link
Copy Markdown

Contribution-checker quality warning
Heuristic score: 0/100 (low). This is a non-blocking warning surfaced by the project's quality settings.

Heuristics that flagged:

  • Wall-of-text PR body: 2936 chars (>2500)
  • Excessive inline code references: 15 inline refs (>3)
  • PR doesn't use the repo's PR template: 5 required checkbox items missing (max 1, match ≥80%)
  • Body adds too many extra headers beyond the template: 6 extra headers (>1)
  • AI watermark phrase: Matched: "Generated with Claude Code"
  • Commit message too long: Longest: 1642 chars
  • Excessive added comments: 19 added comment lines (ratio 0.07)

If this is a genuine contribution, please add detail to your PR description and tighten the diff scope before reviewers look at it.

@postiz-agent

postiz-agent Bot commented Jul 20, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@giladresisi

Copy link
Copy Markdown
Collaborator Author

📝 Docs PR: gitroomhq/postiz-docs#233 — documents the PUT /public/v1/posts/{id}/settings endpoint and adds postSettingsTool to the MCP docs. (Stacked on postiz-docs#232.)

@giladresisi

Copy link
Copy Markdown
Collaborator Author

CLI counterpart: gitroomhq/postiz-agent#13 adds the posts:settings command that calls this endpoint (PUT /public/v1/posts/:id/settings). Docs: gitroomhq/postiz-docs#233.

Comment on lines +967 to +977
);
}

if (
root.state === 'QUEUE' &&
dayjs.utc(root.publishDate).isBefore(dayjs.utc())
) {
throw new BadRequestException(
'The publish time of this post already passed, it cannot be updated'
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: A race condition in updatePostSettings allows updating settings for a post after it has been published, causing data inconsistency.
Severity: MEDIUM

Suggested Fix

To prevent the race condition, re-check the post's state immediately before the final upsert operation within the updatePostSettings function. Alternatively, wrap the state check and the update operation in a database transaction to ensure atomicity.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location:
libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts#L964-L977

Potential issue: The `updatePostSettings` function checks a post's state at the
beginning of its execution. However, it then performs several asynchronous operations
before finally writing the new settings to the database. A race condition can occur if a
separate workflow, `postWorkflowV105`, changes the post's state to `PUBLISHED` during
this time window. The final database `upsert` in `updatePostSettings` does not re-check
the state, leading to settings being updated on a post that has already been published.
This creates a logical inconsistency where the stored settings do not match the content
that was actually published.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real TOCTOU but leaving as-is. It only affects a QUEUE post at the instant it publishes (drafts never publish, past-due QUEUE is already rejected), and worst case is stored settings not matching published content — no double-post or data loss. The same race already exists in the normal edit path via the shared createOrUpdatePost write, so guarding only this method would be incomplete. A transaction isn't right either, since the gap spans validatePosts' provider network calls. If we harden it, the fix is an atomic guarded write (update ... where state in (QUEUE, DRAFT)) in the shared path, tracked separately.

@nevo-david
nevo-david merged commit ea7dace into feat/list-posts-tool Jul 23, 2026
9 checks passed
@nevo-david
nevo-david deleted the feat/agent-update-posts branch July 23, 2026 03:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contribution:approved Approved contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants