From 82b2187583f627e1d6ccbad804c663d826ed886b Mon Sep 17 00:00:00 2001 From: LawyZheng Date: Fri, 19 Sep 2025 10:08:25 +0800 Subject: [PATCH] better totp failure reason and error code (#3472) --- skyvern/core/totp.py | 14 +++++++++++-- skyvern/errors/errors.py | 19 +++++++++++++++++ skyvern/exceptions.py | 27 +++++++++++++++++++++++++ skyvern/forge/agent.py | 24 +++++++++++++++++++++- skyvern/webeye/actions/parse_actions.py | 9 ++++++++- 5 files changed, 89 insertions(+), 4 deletions(-) diff --git a/skyvern/core/totp.py b/skyvern/core/totp.py index ccd69bfc..69b1e64b 100644 --- a/skyvern/core/totp.py +++ b/skyvern/core/totp.py @@ -5,7 +5,7 @@ from datetime import datetime, timedelta import structlog from skyvern.config import settings -from skyvern.exceptions import NoTOTPVerificationCodeFound +from skyvern.exceptions import FailedToGetTOTPVerificationCode, NoTOTPVerificationCodeFound from skyvern.forge import app from skyvern.forge.sdk.core.aiohttp_helper import aiohttp_post from skyvern.forge.sdk.core.security import generate_skyvern_signature @@ -96,7 +96,17 @@ async def _get_verification_code_from_url( "x-skyvern-signature": signature, "Content-Type": "application/json", } - json_resp = await aiohttp_post(url=url, data=request_data, headers=headers, raise_exception=False) + try: + json_resp = await aiohttp_post(url=url, data=request_data, headers=headers, raise_exception=False) + except Exception as e: + LOG.error("Failed to get verification code from url", exc_info=True) + raise FailedToGetTOTPVerificationCode( + task_id=task_id, + workflow_run_id=workflow_run_id, + workflow_id=workflow_permanent_id, + totp_verification_url=url, + reason=str(e), + ) return json_resp.get("verification_code", None) diff --git a/skyvern/errors/errors.py b/skyvern/errors/errors.py index e1ac174c..a8d339a6 100644 --- a/skyvern/errors/errors.py +++ b/skyvern/errors/errors.py @@ -17,6 +17,9 @@ class SkyvernDefinedError(BaseModel): def __repr__(self) -> str: return f"{self.reasoning}(error_code={self.error_code})" + def to_user_defined_error(self) -> UserDefinedError: + return UserDefinedError(error_code=self.error_code, reasoning=self.reasoning, confidence_float=1.0) + class ReachMaxStepsError(SkyvernDefinedError): error_code: str = "REACH_MAX_STEPS" @@ -26,3 +29,19 @@ class ReachMaxStepsError(SkyvernDefinedError): class ReachMaxRetriesError(SkyvernDefinedError): error_code: str = "REACH_MAX_RETRIES" reasoning: str = "The agent has reached the maximum number of retries. It might be an issue with the agent. Please reach out to the Skyvern team for support." + + +class GetTOTPVerificationCodeError(SkyvernDefinedError): + error_code: str = "OTP_ERROR" + reasoning: str = ( + "Failed to get TOTP verification code. Please confirm the TOTP functionality is working correctly on your side." + ) + + def __init__(self, *, reason: str | None = None) -> None: + reasoning = f"Failed to get TOTP verification code. Reason: {reason}" if reason else self.reasoning + super().__init__(reasoning=reasoning) + + +class TimeoutGetTOTPVerificationCodeError(SkyvernDefinedError): + error_code: str = "OTP_TIMEOUT" + reasoning: str = "Timeout getting TOTP verification code." diff --git a/skyvern/exceptions.py b/skyvern/exceptions.py index a3711a23..d80bc782 100644 --- a/skyvern/exceptions.py +++ b/skyvern/exceptions.py @@ -695,6 +695,33 @@ class NoTOTPVerificationCodeFound(SkyvernHTTPException): super().__init__(msg) +class FailedToGetTOTPVerificationCode(SkyvernException): + reason: str | None = None + + def __init__( + self, + task_id: str | None = None, + workflow_run_id: str | None = None, + workflow_id: str | None = None, + totp_verification_url: str | None = None, + totp_identifier: str | None = None, + reason: str | None = None, + ) -> None: + self.reason = reason + msg = "Failed to get TOTP verification code." + if task_id: + msg += f" task_id={task_id}" + if workflow_run_id: + msg += f" workflow_run_id={workflow_run_id}" + if workflow_id: + msg += f" workflow_id={workflow_id}" + if totp_verification_url: + msg += f" totp_verification_url={totp_verification_url}" + if totp_identifier: + msg += f" totp_identifier={totp_identifier}" + super().__init__(f"Failed to get TOTP verification code. reason: {reason}") + + class SkyvernContextWindowExceededError(SkyvernException): def __init__(self) -> None: message = "Context window exceeded. Please contact support@skyvern.com for help." diff --git a/skyvern/forge/agent.py b/skyvern/forge/agent.py index 973a14bf..be30ce8b 100644 --- a/skyvern/forge/agent.py +++ b/skyvern/forge/agent.py @@ -27,12 +27,19 @@ from skyvern.constants import ( ScrapeType, ) from skyvern.core.totp import poll_verification_code -from skyvern.errors.errors import ReachMaxRetriesError, ReachMaxStepsError, UserDefinedError +from skyvern.errors.errors import ( + GetTOTPVerificationCodeError, + ReachMaxRetriesError, + ReachMaxStepsError, + TimeoutGetTOTPVerificationCodeError, + UserDefinedError, +) from skyvern.exceptions import ( BrowserSessionNotFound, BrowserStateMissingPage, DownloadFileMaxWaitingTime, EmptyScrapePage, + FailedToGetTOTPVerificationCode, FailedToNavigateToUrl, FailedToParseActionInstruction, FailedToSendWebhook, @@ -1012,6 +1019,21 @@ class ForgeAgent: action_order=0, reasoning="No TOTP verification code found. Going to terminate.", intention="No TOTP verification code found. Going to terminate.", + errors=[TimeoutGetTOTPVerificationCodeError().to_user_defined_error()], + ) + ] + except FailedToGetTOTPVerificationCode as e: + actions = [ + TerminateAction( + reasoning=f"Failed to get TOTP verification code. Going to terminate. Reason: {e.reason}", + intention=f"Failed to get TOTP verification code. Going to terminate. Reason: {e.reason}", + organization_id=task.organization_id, + workflow_run_id=task.workflow_run_id, + task_id=task.task_id, + step_id=step.step_id, + step_order=step.order, + action_order=0, + errors=[GetTOTPVerificationCodeError(reason=e.reason).to_user_defined_error()], ) ] diff --git a/skyvern/webeye/actions/parse_actions.py b/skyvern/webeye/actions/parse_actions.py index 40d156dc..b05f87a5 100644 --- a/skyvern/webeye/actions/parse_actions.py +++ b/skyvern/webeye/actions/parse_actions.py @@ -8,7 +8,7 @@ from pydantic import ValidationError from skyvern.constants import SCROLL_AMOUNT_MULTIPLIER from skyvern.core.totp import poll_verification_code -from skyvern.exceptions import NoTOTPVerificationCodeFound, UnsupportedActionType +from skyvern.exceptions import FailedToGetTOTPVerificationCode, NoTOTPVerificationCodeFound, UnsupportedActionType from skyvern.forge import app from skyvern.forge.prompts import prompt_engine from skyvern.forge.sdk.core import skyvern_context @@ -829,6 +829,13 @@ async def generate_cua_fallback_actions( reasoning=reasoning, intention=reasoning, ) + except FailedToGetTOTPVerificationCode as e: + reasoning_suffix = f"Failed to get verification code. Reason: {e.reason}" + reasoning = f"{reasoning}. {reasoning_suffix}" if reasoning else reasoning_suffix + action = TerminateAction( + reasoning=reasoning, + intention=reasoning, + ) else: action = TerminateAction( reasoning=reasoning,