feat(evo-flow): surface the CRM rejection reason on a 422, not a generic error (EVO-2203) - #111
Merged
Conversation
…ric error (EVO-2203)
The pipeline nodes already turn a CRM failure into a visible node error via
createErrorResult(error), but a 422 threw BadRequestException(body) whose
message is generic — so an archived-pipeline refusal reached the journey run
as "Bad Request Exception". The 422 handler now lifts the CRM envelope's
error.message to the exception message, so the run shows the reason
("Pipeline is archived and cannot receive conversations") while getResponse()
keeps error.code for callers that branch on it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reviewer's GuideAdjusts CRM 422 error handling to surface the CRM-provided rejection reason in the BadRequestException while preserving the structured error code, and adds a focused test to validate the new behavior. Sequence diagram for updated CRM 422 error handlingsequenceDiagram
actor Caller
participant CrmClientService
participant CRM
Caller->>CrmClientService: post(path, body)
CrmClientService->>CRM: fetch(path, body)
CRM-->>CrmClientService: 422 response
alt response.json succeeds
CrmClientService->>CrmClientService: response.json()
CrmClientService->>CrmClientService: BadRequestException({ ...errorBody, message: errorBody.error.message })
else response.json throws
CrmClientService->>CrmClientService: response.text()
CrmClientService->>CrmClientService: BadRequestException(reason)
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
… actually use (EVO-2203) Code review of #111. The 422 message lift landed in requestGeneric, whose only consumer is contacts-client. The three pipeline nodes call addToPipeline / moveToPipelineStage / createPipelineTask, which go through executeRequest — its 422 branch was untouched, so the journey run kept showing the raw JSON envelope ("CRM Validation error: {\"success\":false,\"error\":{...}}"). describeCrm422 now folds code and reason into that string. The "CRM Validation error" prefix stays: executeRequest matches on it to not retry a refusal. Spreading the envelope also overwrote a top-level `message` with the placeholder, so a 422 from render_record_invalid's fallback lost its reason ("Email is invalid" became "CRM rejected the request"). crmRejectionReason now prefers error.message, then a top-level message, then the raw body. Tests: the refusal is covered where the nodes read it (client level, both endpoints, no retry) and end to end on assign-to-pipeline with a real client — the node specs mock the CRM client away, which is why the gap went unseen.
gomessguii
approved these changes
Jul 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The three pipeline nodes (
assign-to-pipeline,move-to-pipeline-stage,create-pipeline-task) already turn a CRM failure into a visible node error viacreateErrorResult(error)— the EVO-1740 pattern. But a 422 threwBadRequestException(body), whose.messageis generic, so an archived-pipeline refusal reached the journey run as "Bad Request Exception". The 422 handler now lifts the CRM envelope'serror.messageto the exception message, so the run shows the reason ("Pipeline is archived and cannot receive conversations") whilegetResponse()keepserror.codefor callers that branch on it.Test plan
npx jest src/shared/crm-client/crm-client.service.spec.ts— 16 passed, incl. the new "surfaces the CRM error message on a 422 envelope, keeping the code"npx jest src/shared/crm-client/crm-client.hardening.spec.ts— 11 passed (no regression on the shared 422 path)npx eslinton the touched file — no new error vs baseline;npx tsc --noEmitcleanChanged Files
src/shared/crm-client/crm-client.service.tssrc/shared/crm-client/crm-client.service.spec.tsRelated PRs
PIPELINE_ARCHIVEDrejection this surfaces.Linked Issue
Summary by Sourcery
Surface CRM 422 rejection reasons as readable messages in BadRequestException responses while preserving the underlying error code for callers.
Enhancements:
Tests: