Skip to content

Commit f1a72f4

Browse files
committed
fix(webhooks): match Ashby's real not-found envelope on repeat delete
The already-removed branch tested `/webhook_not_found/` against the extracted message, but `ashbyErrorMessage` returns `errorInfo.message` first and that reads "Webhook not found" — Ashby carries the machine code on `errorInfo.code` and in the deprecated `errors` array, both of which lose to the message. So the one envelope this branch exists for, a repeat delete of an id Ashby has already dropped, fell through to the failure path: a spurious warn today and a strict-mode throw on the undeploy cleanup path. Read the codes directly and keep a prose fallback for the message-only form. Caught by Cursor Bugbot.
1 parent aa5ed04 commit f1a72f4

2 files changed

Lines changed: 54 additions & 1 deletion

File tree

apps/sim/lib/webhooks/providers/ashby.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,27 @@ describe('ashbyHandler', () => {
238238
await expect(ashbyHandler.deleteSubscription?.(ctx(true))).resolves.toBeUndefined()
239239
})
240240

241+
it('recognizes the not-found envelope Ashby actually sends for a repeat delete', async () => {
242+
// errorInfo.message wins over the code array in the extractor, so it reads
243+
// 'Webhook not found' - matching that against `webhook_not_found` would
244+
// turn idempotent cleanup into a strict-mode throw.
245+
respondWith({
246+
success: false,
247+
errors: ['webhook_not_found'],
248+
errorInfo: {
249+
code: 'webhook_not_found',
250+
message: 'Webhook not found',
251+
requestId: '01JSJ8FEK5ZN4XQBZP7DBKK7ZC',
252+
},
253+
})
254+
await expect(ashbyHandler.deleteSubscription?.(ctx(true))).resolves.toBeUndefined()
255+
})
256+
257+
it('recognizes a not-found reported only as prose', async () => {
258+
respondWith({ success: false, errorInfo: { message: 'Webhook not found' } })
259+
await expect(ashbyHandler.deleteSubscription?.(ctx(true))).resolves.toBeUndefined()
260+
})
261+
241262
it('accepts a successful delete', async () => {
242263
respondWith({ success: true, results: { webhookId: 'ext-1' } })
243264
await expect(ashbyHandler.deleteSubscription?.(ctx(true))).resolves.toBeUndefined()

apps/sim/lib/webhooks/providers/ashby.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,35 @@ function ashbyErrorMessage(data: unknown, fallback: string): string {
6262
return fallback
6363
}
6464

65+
/**
66+
* Whether an Ashby error response means the webhook id no longer exists.
67+
*
68+
* Ashby signals this as the machine code `webhook_not_found`, carried on
69+
* `errorInfo.code` and/or as an `errors` entry — but the same envelope's
70+
* `errorInfo.message` reads `Webhook not found`, and that is what
71+
* `ashbyErrorMessage` returns, since message wins over the deprecated code
72+
* array. Matching the extracted message against the code therefore misses the
73+
* envelope Ashby actually sends for a repeat delete, and idempotent cleanup
74+
* would be reported as a real failure. Read the codes directly, and keep a
75+
* prose fallback for the message-only form.
76+
*/
77+
function isAshbyWebhookNotFound(data: Record<string, unknown>, message: string): boolean {
78+
const info = data.errorInfo as Record<string, unknown> | undefined
79+
if (typeof info?.code === 'string' && /webhook_not_found/i.test(info.code)) return true
80+
81+
if (Array.isArray(data.errors)) {
82+
for (const entry of data.errors) {
83+
if (typeof entry === 'string' && /webhook_not_found/i.test(entry)) return true
84+
if (entry && typeof entry === 'object') {
85+
const entryMessage = (entry as Record<string, unknown>).message
86+
if (typeof entryMessage === 'string' && /webhook_not_found/i.test(entryMessage)) return true
87+
}
88+
}
89+
}
90+
91+
return /webhook[\s_]not[\s_]found/i.test(message)
92+
}
93+
6594
const logger = createLogger('WebhookProvider:Ashby')
6695

6796
function validateAshbySignature(secretToken: string, signature: string, body: string): boolean {
@@ -362,7 +391,10 @@ export const ashbyHandler: WebhookProviderHandler = {
362391
logger.info(
363392
`[${ctx.requestId}] Successfully deleted Ashby webhook subscription ${externalId}`
364393
)
365-
} else if (ashbyResponse.status === 404 || /webhook_not_found/i.test(errorMessage)) {
394+
} else if (
395+
ashbyResponse.status === 404 ||
396+
isAshbyWebhookNotFound(responseBody, errorMessage)
397+
) {
366398
logger.info(
367399
`[${ctx.requestId}] Ashby webhook ${externalId} not found during deletion (already removed)`
368400
)

0 commit comments

Comments
 (0)