[SILO-717] fix: add ttl and different key for issue sync race condition #4887
This commit is contained in:
@@ -22,15 +22,16 @@ export type GithubCommentWebhookPayload = GithubWebhookPayload[
|
||||
};
|
||||
|
||||
export const handleIssueComment = async (store: Store, action: GithubCommentAction, data: unknown) => {
|
||||
// Check if this webhook was triggered by our own Plane->GitHub sync (loop prevention)
|
||||
// @ts-expect-error
|
||||
if (data && data.comment && data.comment.id) {
|
||||
// @ts-expect-error
|
||||
const exist = await store.get(`silo:comment:${data.comment.id}`);
|
||||
const exist = await store.get(`silo:comment:gh:${data.comment.id}`);
|
||||
if (exist) {
|
||||
logger.info(`[ISSUE-COMMENT] Event Processed Successfully, confirmed by target`);
|
||||
// Remove the webhook from the store
|
||||
logger.info(`[ISSUE-COMMENT] Event triggered by Plane->GitHub sync, skipping to prevent loop`);
|
||||
// Remove the key so future legitimate webhooks are not blocked
|
||||
// @ts-expect-error
|
||||
await store.del(`silo:comment:${data.comment.id}`);
|
||||
await store.del(`silo:comment:gh:${data.comment.id}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -169,7 +170,9 @@ export const syncCommentWithPlane = async (
|
||||
comment.id,
|
||||
planeComment
|
||||
);
|
||||
await store.set(`silo:comment:${comment.id}`, "true");
|
||||
// Set key with Plane comment ID so Plane->GitHub handler can detect and skip
|
||||
// Use 5 second TTL to allow the webhook loop back but expire quickly
|
||||
await store.set(`silo:comment:plane:${comment.id}`, "true", 5);
|
||||
} else {
|
||||
const createdComment = await planeClient.issueComment.create(
|
||||
workspaceConnection.workspace_slug,
|
||||
@@ -177,7 +180,9 @@ export const syncCommentWithPlane = async (
|
||||
issue.id,
|
||||
planeComment
|
||||
);
|
||||
await store.set(`silo:comment:${createdComment.id}`, "true");
|
||||
// Set key with Plane comment ID so Plane->GitHub handler can detect and skip
|
||||
// Use 5 second TTL to allow the webhook loop back but expire quickly
|
||||
await store.set(`silo:comment:plane:${createdComment.id}`, "true", 5);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -18,16 +18,17 @@ import type { Store } from "@/worker/base";
|
||||
const SYNC_LABEL = "plane";
|
||||
|
||||
export const handleIssueEvents = async (store: Store, action: string, data: unknown) => {
|
||||
// If the issue number exist inside the store, skip it
|
||||
// Check if this webhook was triggered by our own Plane->GitHub sync (loop prevention)
|
||||
// The Plane->GitHub handler sets a temporary key right after syncing to GitHub
|
||||
// @ts-expect-error
|
||||
if (data && data.issueNumber) {
|
||||
// @ts-expect-error
|
||||
const exist = await store.get(`silo:issue:${data.issueNumber}`);
|
||||
const exist = await store.get(`silo:issue:gh:${data.issueNumber}`);
|
||||
if (exist) {
|
||||
logger.info(`[GITHUB][ISSUE] Event Processed Successfully, confirmed by target`);
|
||||
// Remove the webhook from the store
|
||||
logger.info(`[GITHUB][ISSUE] Event triggered by Plane->GitHub sync, skipping to prevent loop`);
|
||||
// Remove the key so future legitimate webhooks are not blocked
|
||||
// @ts-expect-error
|
||||
await store.del(`silo:issue:${data.issueNumber}`);
|
||||
await store.del(`silo:issue:gh:${data.issueNumber}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -205,7 +206,9 @@ export const syncIssueWithPlane = async (store: Store, action: IssueWebhookActio
|
||||
issue.id,
|
||||
planeIssue
|
||||
);
|
||||
await store.set(`silo:issue:${issue.id}`, "true");
|
||||
// Set key with Plane issue ID so Plane->GitHub handler can detect and skip
|
||||
// Use 5 second TTL to allow the webhook loop back but expire quickly
|
||||
await store.set(`silo:issue:plane:${issue.id}`, "true", 5);
|
||||
} else {
|
||||
const createdIssue = await planeClient.issue.create(
|
||||
entityConnection.workspace_slug,
|
||||
@@ -241,7 +244,9 @@ export const syncIssueWithPlane = async (store: Store, action: IssueWebhookActio
|
||||
await ghService.createIssueComment(data.owner, data.repositoryName, Number(data.issueNumber), comment);
|
||||
};
|
||||
|
||||
await Promise.all([createLink(), createLinkBack(), store.set(`silo:issue:${createdIssue.id}`, "true")]);
|
||||
// Set key with Plane issue ID so Plane->GitHub handler can detect and skip
|
||||
// Use 5 second TTL to allow the webhook loop back but expire quickly
|
||||
await Promise.all([createLink(), createLinkBack(), store.set(`silo:issue:plane:${createdIssue.id}`, "true", 5)]);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error syncing issue with Plane", error);
|
||||
|
||||
@@ -23,14 +23,13 @@ export const handleIssueCommentWebhook = async (
|
||||
) => {
|
||||
const payloadId = payload?.id ?? "";
|
||||
|
||||
// Store a key associated with the data in the store
|
||||
// If the key is present, then we need to requeue all that task and process them again
|
||||
// Check if this webhook was triggered by our own GitHub->Plane sync (loop prevention)
|
||||
if (payloadId) {
|
||||
const exist = await store.get(`silo:comment:${payload.id}`);
|
||||
const exist = await store.get(`silo:comment:plane:${payload.id}`);
|
||||
if (exist) {
|
||||
logger.info("[PLANE][COMMENT] Event Processed Successfully, confirmed by target");
|
||||
// Remove the webhook from the store
|
||||
await store.del(`silo:comment:${payload.id}`);
|
||||
logger.info("[PLANE][COMMENT] Event triggered by GitHub->Plane sync, skipping to prevent loop");
|
||||
// Remove the key so future legitimate webhooks are not blocked
|
||||
await store.del(`silo:comment:plane:${payload.id}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -130,7 +129,9 @@ const handleCommentSync = async (store: Store, payload: PlaneWebhookPayload) =>
|
||||
credentials,
|
||||
ghIntegrationKey
|
||||
);
|
||||
await store.set(`silo:comment:${githubComment.data.id}`, "true");
|
||||
// Set key with GitHub comment ID so GitHub->Plane handler can detect and skip
|
||||
// Use 5 second TTL to allow the webhook loop back but expire quickly
|
||||
await store.set(`silo:comment:gh:${githubComment.data.id}`, "true", 5);
|
||||
|
||||
if (
|
||||
!comment.external_id ||
|
||||
|
||||
@@ -20,13 +20,14 @@ const apiClient = getAPIClient();
|
||||
export const imagePrefix = encodeURI(env.SILO_API_BASE_URL + env.SILO_BASE_PATH + "/api/assets/github/");
|
||||
|
||||
export const handleIssueWebhook = async (headers: TaskHeaders, mq: MQ, store: Store, payload: PlaneWebhookPayload) => {
|
||||
// Check for the key in the store, if the key is present, then the issue is already synced
|
||||
// Check if this webhook was triggered by our own GitHub->Plane sync (loop prevention)
|
||||
// The GitHub->Plane handler sets a temporary key with the Plane issue ID
|
||||
if (payload && payload.id) {
|
||||
const exist = await store.get(`silo:issue:${payload.id}`);
|
||||
const exist = await store.get(`silo:issue:plane:${payload.id}`);
|
||||
if (exist) {
|
||||
logger.info("[PLANE][ISSUE] Event Processed Successfully, confirmed by target");
|
||||
// Remove the webhook from the store
|
||||
await store.del(`silo:issue:${payload.id}`);
|
||||
logger.info("[PLANE][ISSUE] Event triggered by GitHub->Plane sync, skipping to prevent loop");
|
||||
// Remove the key so future legitimate webhooks are not blocked
|
||||
await store.del(`silo:issue:plane:${payload.id}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -127,8 +128,9 @@ const handleIssueSync = async (store: Store, payload: PlaneWebhookPayload) => {
|
||||
await Promise.all([addExternalId(), createLink()]);
|
||||
}
|
||||
|
||||
// Add the issue number to the store
|
||||
await store.set(`silo:issue:${githubIssue?.data.number}`, "true");
|
||||
// Set key with GitHub issue number so GitHub->Plane handler can detect and skip
|
||||
// Use 5 second TTL to allow the webhook loop back but expire quickly
|
||||
await store.set(`silo:issue:gh:${githubIssue?.data.number}`, "true", 5);
|
||||
} catch (error) {
|
||||
logger.error("[Plane][Github] Error handling issue create/update event", {
|
||||
error,
|
||||
|
||||
Reference in New Issue
Block a user