Skip to content

fix(icypeas): surface submit-time validationErrors as a descriptive error#5142

Closed
TheodoreSpeaks wants to merge 3 commits into
stagingfrom
fix/icypeas
Closed

fix(icypeas): surface submit-time validationErrors as a descriptive error#5142
TheodoreSpeaks wants to merge 3 commits into
stagingfrom
fix/icypeas

Conversation

@TheodoreSpeaks

@TheodoreSpeaks TheodoreSpeaks commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • The canary failure (Icypeas email-verification did not return an item _id on support@stripe.com) was misdiagnosed at first. Reproducing the live call shows Icypeas returns HTTP 200 { success: false, validationErrors: [{ message: "insufficient_credits", humanReadableMessage: "Insufficient credits to run the search", ... }] } — the hosted account is out of credits, not an invalid/role-based email.
  • transformResponse only handled the { success: true, item: { _id } } shape, so any submit-time validationErrors body threw the cryptic missing-_id error.
  • Now both verify_email.ts and find_email.ts detect a non-empty validationErrors array and throw the human-readable reason (Icypeas ... rejected the request: Insufficient credits to run the search). A submit rejection is never a deliverability verdict, so it surfaces as a real, legible error instead of a fabricated valid: false / empty result.

⚠️ Operational follow-up (not fixed by this PR)

  • The underlying canary break is that the hosted ICYPEAS_API_KEY account has no credits. Top it up — code only makes the failure legible.

Type of Change

  • Bug fix

Testing

  • Reproduced the live support@stripe.com submit response to confirm the exact validationErrors shape.
  • Added/updated tests: throws the human-readable reason (incl. insufficient_credits), falls back to message, still throws on a success: true body missing _id. 16/16 pass.
  • bun run lint:check, bun run check:api-validation:strict pass.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jun 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 19, 2026 9:43pm

Request Review

@cursor

cursor Bot commented Jun 19, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Localized error-handling change in two integration tools with new unit tests; no auth, billing, or polling logic changes.

Overview
Icypeas find-email and verify-email now treat HTTP 200 bodies with a non-empty validationErrors array as submit failures (e.g. insufficient credits, bad input) instead of falling through to the missing item._id path.

transformResponse in both tools throws an error that prefers humanReadableMessage, then message, so operators see reasons like Insufficient credits to run the search rather than a cryptic _id error. Submit-time rejections are not treated as search/deliverability outcomes.

Tests cover verify/find on validationErrors, message fallback, and unchanged behavior when success: true but _id is missing.

Reviewed by Cursor Bugbot for commit 139f85b. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread apps/sim/tools/icypeas/verify_email.ts
@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a cryptic error surfaced when the hosted Icypeas account runs out of credits. Icypeas returns an HTTP 200 { success: false, validationErrors: [{ message: "insufficient_credits", humanReadableMessage: "..." }] } body on submit-time failures; previously transformResponse fell straight through to the _id guard and threw an opaque missing-_id error.

  • Both verify_email.ts and find_email.ts now check Array.isArray(validationErrors) && validationErrors.length > 0 before the _id guard and throw Icypeas … rejected the request: <humanReadableMessage> (falling back to message, then 'validation error').
  • Four tests are added covering the main validationErrors path for both tools, the humanReadableMessagemessage fallback, and the unchanged guard that throws on a success:true body with no _id.
  • The postProcess ordering concern from earlier rounds is fully resolved: transformResponse now throws on any submit-time failure, so postProcess is never called with a null searchId.

Confidence Score: 5/5

Safe to merge — the guard is tight, the happy path is unchanged, and prior postProcess ordering concerns are fully eliminated by throwing instead of returning a sentinel value.

The change is a well-scoped early-return guard in transformResponse that only activates on a non-empty validationErrors array. The happy path (success: true, non-null _id) is untouched, and the previous postProcess ordering issue is moot because transformResponse now throws on all submit-time failures rather than returning a null-searchId result.

No files require special attention. The only gap is missing symmetric edge-case tests for find_email in the test file.

Important Files Changed

Filename Overview
apps/sim/tools/icypeas/verify_email.ts Adds a validationErrors guard in transformResponse before the _id check; throws a descriptive error with the human-readable reason instead of crashing with a cryptic missing-_id message.
apps/sim/tools/icypeas/find_email.ts Mirrors the same validationErrors guard added to verify_email.ts; same logic, same early-throw pattern, symmetric fix.
apps/sim/tools/icypeas-hosting.test.ts Adds four new test cases covering: both tools throw on a validationErrors 200 body; verify-email falls back to message; verify-email still throws on a success body with no _id. Missing symmetric coverage for find-email fallback and no-_id cases.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant transformResponse
    participant postProcess
    participant IcypeasAPI

    Client->>IcypeasAPI: POST /api/email-verification (or /api/email-search)
    IcypeasAPI-->>transformResponse: "HTTP 200 { success: false, validationErrors: [...] }"
    transformResponse->>transformResponse: "Array.isArray(validationErrors) && length > 0"
    transformResponse-->>Client: throw Error(Icypeas rejected: Insufficient credits)

    Client->>IcypeasAPI: POST /api/email-verification (happy path)
    IcypeasAPI-->>transformResponse: "HTTP 200 { success: true, item: { _id, status: NONE } }"
    transformResponse->>transformResponse: "searchId = item._id (non-null guard)"
    transformResponse-->>postProcess: "{ success: true, output: { searchId, status } }"
    loop Poll until terminal
        postProcess->>IcypeasAPI: "POST /api/bulk-single-searchs/read { id: searchId }"
        IcypeasAPI-->>postProcess: "{ item: { status: FOUND|... } }"
    end
    postProcess-->>Client: "{ success: true, output: mapItem(item) }"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant transformResponse
    participant postProcess
    participant IcypeasAPI

    Client->>IcypeasAPI: POST /api/email-verification (or /api/email-search)
    IcypeasAPI-->>transformResponse: "HTTP 200 { success: false, validationErrors: [...] }"
    transformResponse->>transformResponse: "Array.isArray(validationErrors) && length > 0"
    transformResponse-->>Client: throw Error(Icypeas rejected: Insufficient credits)

    Client->>IcypeasAPI: POST /api/email-verification (happy path)
    IcypeasAPI-->>transformResponse: "HTTP 200 { success: true, item: { _id, status: NONE } }"
    transformResponse->>transformResponse: "searchId = item._id (non-null guard)"
    transformResponse-->>postProcess: "{ success: true, output: { searchId, status } }"
    loop Poll until terminal
        postProcess->>IcypeasAPI: "POST /api/bulk-single-searchs/read { id: searchId }"
        IcypeasAPI-->>postProcess: "{ item: { status: FOUND|... } }"
    end
    postProcess-->>Client: "{ success: true, output: mapItem(item) }"
Loading

Reviews (7): Last reviewed commit: "fix(icypeas): surface submit validationE..." | Re-trigger Greptile

Comment thread apps/sim/tools/icypeas/find_email.ts Outdated
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@BugBot review

Comment thread apps/sim/tools/icypeas/find_email.ts
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

1 similar comment
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@BugBot review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 580ccde. Configure here.

@TheodoreSpeaks TheodoreSpeaks changed the title fix(icypeas): handle 200 validationErrors body as a BAD_INPUT verdict fix(icypeas): surface submit-time validationErrors as a descriptive error Jun 19, 2026
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

Reworked the approach after reproducing the live call. The earlier commits mapped a 200 validationErrors body to a valid: false / BAD_INPUT verdict — but the actual response for support@stripe.com is { success: false, validationErrors: [{ message: 'insufficient_credits', humanReadableMessage: 'Insufficient credits to run the search' }] }. The account is out of credits; the email is fine. Masking that as valid: false would write false 'invalid' verdicts for every email while credits are exhausted (and charge $0, hiding the problem) — exactly the API-level masking Greptile flagged. transformResponse now throws the human-readable reason instead. Re-review please @greptile @BugBot.

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

1 similar comment
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@BugBot review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 139f85b. Configure here.

@waleedlatif1 waleedlatif1 deleted the fix/icypeas branch June 20, 2026 22:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant