From c1cfd7ef2d28af046a381b4e71e58c6250129df5 Mon Sep 17 00:00:00 2001 From: A <258483684+la14-1@users.noreply.github.com> Date: Wed, 22 Apr 2026 00:16:58 -0700 Subject: [PATCH] fix(growth): x engagement approve now actually posts the reply (#3340) The xeng_approve and xeng_edit_submit handlers marked the reply as approved in state.db but never called postToX(). Replies were silently stuck in "ready to post on X" limbo forever. Both handlers now call postToX(replyText, sourceTweetId) so the reply goes out as an actual threaded reply on X, and the Slack card shows the live tweet URL. Mirrors the tweet_approve flow. Co-authored-by: Claude Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Ahmed Abushagur --- .claude/skills/setup-spa/main.ts | 95 +++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 27 deletions(-) diff --git a/.claude/skills/setup-spa/main.ts b/.claude/skills/setup-spa/main.ts index 2b343a6f..1f4edf76 100644 --- a/.claude/skills/setup-spa/main.ts +++ b/.claude/skills/setup-spa/main.ts @@ -1657,7 +1657,7 @@ app.action("tweet_skip", async ({ ack, body, client }) => { } }); -// --- xeng_approve: mark engagement reply as approved --- +// --- xeng_approve: post engagement reply to X --- app.action("xeng_approve", async ({ ack, body, client }) => { await ack(); const payload = toRecord("actions" in body && Array.isArray(body.actions) ? body.actions[0] : null); @@ -1668,19 +1668,39 @@ app.action("xeng_approve", async ({ ack, body, client }) => { const tweet = findTweet(db, engageId); if (!tweet || tweet.status !== "pending") return; - updateTweetStatus(db, engageId, { - status: "approved", - actionedBy: userId, - }); - logTweetDecision(tweet, "approved"); + const xResult = await postToX(tweet.tweetText, tweet.sourceTweetId ?? undefined); - if (tweet.slackChannel && tweet.slackTs) { - await replaceButtonsWithStatus( - client, - tweet.slackChannel, - tweet.slackTs, - `:white_check_mark: Reply approved by <@${userId}> — ready to post on X`, - ); + if (xResult.ok) { + updateTweetStatus(db, engageId, { + status: "posted", + actionedBy: userId, + postedText: tweet.tweetText, + }); + logTweetDecision(tweet, "approved"); + + if (tweet.slackChannel && tweet.slackTs) { + await replaceButtonsWithStatus( + client, + tweet.slackChannel, + tweet.slackTs, + `:white_check_mark: Reply posted by <@${userId}> <${xResult.tweetUrl}|view on X>`, + ); + } + } else { + updateTweetStatus(db, engageId, { + status: "error", + actionedBy: userId, + }); + + if (tweet.slackChannel && tweet.slackTs) { + await client.chat + .postMessage({ + channel: tweet.slackChannel, + thread_ts: tweet.slackTs, + text: `:x: Failed to post reply: ${xResult.error}`, + }) + .catch(() => {}); + } } }); @@ -1734,7 +1754,7 @@ app.action("xeng_edit", async ({ ack, body, client }) => { .catch(() => {}); }); -// --- xeng_edit_submit: modal submitted with edited reply --- +// --- xeng_edit_submit: modal submitted with edited reply, post to X --- app.view("xeng_edit_submit", async ({ ack, view, body, client }) => { await ack(); const engageId = view.private_metadata; @@ -1754,20 +1774,41 @@ app.view("xeng_edit_submit", async ({ ack, view, body, client }) => { engageId, ]); - updateTweetStatus(db, engageId, { - status: "approved", - actionedBy: userId, - postedText: editedText, - }); - logTweetDecision(tweet, "edited", editedText); + const xResult = await postToX(editedText, tweet.sourceTweetId ?? undefined); - if (tweet.slackChannel && tweet.slackTs) { - await replaceButtonsWithStatus( - client, - tweet.slackChannel, - tweet.slackTs, - `:white_check_mark: Reply edited & approved by <@${userId}> — ready to post on X`, - ); + if (xResult.ok) { + updateTweetStatus(db, engageId, { + status: "posted", + actionedBy: userId, + postedText: editedText, + }); + logTweetDecision(tweet, "edited", editedText); + + if (tweet.slackChannel && tweet.slackTs) { + await replaceButtonsWithStatus( + client, + tweet.slackChannel, + tweet.slackTs, + `:white_check_mark: Reply edited & posted by <@${userId}> <${xResult.tweetUrl}|view on X>`, + ); + } + } else { + updateTweetStatus(db, engageId, { + status: "error", + actionedBy: userId, + postedText: editedText, + }); + logTweetDecision(tweet, "edited", editedText); + + if (tweet.slackChannel && tweet.slackTs) { + await client.chat + .postMessage({ + channel: tweet.slackChannel, + thread_ts: tweet.slackTs, + text: `:x: Reply edited but failed to post: ${xResult.error}`, + }) + .catch(() => {}); + } } });