Skip to content

Async Batch Pipeline - Add guardrails for SFMC result API - #3972

Open
sayan-das-in wants to merge 9 commits into
mainfrom
async-batch-pipeline-sfmc
Open

Async Batch Pipeline - Add guardrails for SFMC result API#3972
sayan-das-in wants to merge 9 commits into
mainfrom
async-batch-pipeline-sfmc

Conversation

@sayan-das-in

Copy link
Copy Markdown
Contributor

The SFMC result API can respond with empty status under load conditions. This change classifies those cases with retryable errors so that the orchestration layer can retry the status check again.

Testing

Testing completed successfully in Staging with a 500k audience size.

  • Added unit tests for new functionality
  • Tested end-to-end using the local server
  • [If destination is already live] Tested for backward compatibility of destination. Note: New required fields are a breaking change.
  • [Segmenters] Tested in the staging environment
  • [Segmenters] [If applicable for this change] Tested for regression with Hadron.

Security Review

Please ensure sensitive data is properly protected in your integration.

  • Reviewed all field definitions for sensitive data (API keys, tokens, passwords, client secrets) and confirmed they use type: 'password'

New Destination Checklist

  • Extracted all action API versions to verioning-info.ts file. example
@sayan-das-in
sayan-das-in requested a review from a team as a code owner August 21, 2026 08:01
Copilot AI lite review requested due to automatic review settings August 21, 2026 08:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds guardrails to the SFMC async poll flow so that inconsistent /status vs /results responses (empty/missing items) are treated as retryable, preventing misleading “FAILED with no details” outcomes and avoiding returning an empty multiStatusResponse.

Changes:

  • Treat /results responses with missing/empty items as RETRYABLE_ERROR and drop multiStatusResponse.
  • Ensure multiStatusResponse is removed when /results throws after it was initialized.
  • Add unit tests covering both empty and missing items cases.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/destination-actions/src/destinations/salesforce-marketing-cloud/asyncDataExtension/index.async.ts Adds retryable classification + cleanup of multiStatusResponse for empty/missing /results items and error paths.
packages/destination-actions/src/destinations/salesforce-marketing-cloud/tests/asyncDataExtension.async.test.ts Adds tests validating RETRYABLE_ERROR + absence of multiStatusResponse for inconsistent /status//results.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +251 to +258
// SFMC's /status can report hasErrors on a job whose /results page comes back with zero
// (or a missing) items array -- a status/results inconsistency, not proof the job
// actually failed. Since we have no per-record detail to act on, treat it as retryable
// rather than a hard FAILED with no explanation.
if (!resultsResponse.data.items || resultsResponse.data.items.length === 0) {
logger?.warn?.(`SFMC async /status reported errors for job ${payload.jobId} but /results returned no items`)
delete response.multiStatusResponse
response.jobStatus = 'RETRYABLE_ERROR'
// rather than a hard FAILED with no explanation.
if (!resultsResponse.data.items || resultsResponse.data.items.length === 0) {
logger?.warn?.(`SFMC async /status reported errors for job ${payload.jobId} but /results returned no items`)
delete response.multiStatusResponse
Copilot AI review requested due to automatic review settings August 21, 2026 08:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

Comment on lines +255 to +260
if (!resultsResponse.data.items || resultsResponse.data.items.length === 0) {
logger?.warn?.(`SFMC async /status reported errors for job ${payload.jobId} but /results returned no items`)
delete response.multiStatusResponse
response.jobStatus = 'RETRYABLE_ERROR'
return response
}
// rather than a hard FAILED with no explanation.
if (!resultsResponse.data.items || resultsResponse.data.items.length === 0) {
logger?.warn?.(`SFMC async /status reported errors for job ${payload.jobId} but /results returned no items`)
delete response.multiStatusResponse
// The /results call above may have thrown after response.multiStatusResponse was already
// initialized to an empty (but truthy) instance -- discard it so callers don't mistake
// "we never got any per-record data" for a real, if empty, multi-status result.
delete response.multiStatusResponse
expect(response.multiStatusResponse?.successCount).toBe(0)
})

it('should return RETRYABLE_ERROR (not FAILED) when /status reports errors but /results returns zero items', async () => {
expect(response.jobStatus).toBe('RETRYABLE_ERROR')
expect(response.multiStatusResponse).toBeUndefined()
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment