From dd4f763ac39a203b96bf942ec6cc4d36b9a91109 Mon Sep 17 00:00:00 2001 From: "fangyaozheng@bytedance.com" Date: Thu, 23 Jul 2026 11:24:04 +0800 Subject: [PATCH] fix(studio): surface provisioning errors --- README.md | 4 +- docs/content/docs/framework/frontend.en.mdx | 5 ++ tests/cli/test_studio_deploy_target.py | 91 +++++++++++++++++++++ veadk/cli/cli_frontend.py | 36 +++++++- 4 files changed, 133 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 78d2bbf2..6a24036b 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,9 @@ VeADK provides several useful command line tools for faster deployment and optim deployed client skips the second OAuth consent confirmation after login; two dedicated AgentKit CodeEnv Tools are created automatically for temporary chats and Skill creation unless their IDs are supplied with - `--sandbox-chat-codex-tool-id` and `--sandbox-skill-creator-tool-id` + `--sandbox-chat-codex-tool-id` and `--sandbox-skill-creator-tool-id`; Tool or + credential-relay provisioning failures print the underlying error verbatim + after credential values are redacted - `veadk studio update --vefaas-app-name `: build the frontend from a local VeADK source checkout and release it through the existing VeFaaS Application and Function. Omit `--region` and `--project` to search Beijing, diff --git a/docs/content/docs/framework/frontend.en.mdx b/docs/content/docs/framework/frontend.en.mdx index 578d44e1..0f92c968 100644 --- a/docs/content/docs/framework/frontend.en.mdx +++ b/docs/content/docs/framework/frontend.en.mdx @@ -230,6 +230,11 @@ selected with `--sandbox-chat-codex-tool-id` and credential in KMS and binds only the relay URL and revocable ticket to both Tools. The VeFaaS Function receives only Tool IDs, never model credentials: +If Tool or credential-relay provisioning fails, the CLI prints the underlying +error verbatim, including its original line breaks, after redacting credential +values. Use the service action, error code, request ID, and request details to +identify the missing permission or account resource. + ```bash veadk studio deploy \ --user-pool-id \ diff --git a/tests/cli/test_studio_deploy_target.py b/tests/cli/test_studio_deploy_target.py index 14084565..b710bc8f 100644 --- a/tests/cli/test_studio_deploy_target.py +++ b/tests/cli/test_studio_deploy_target.py @@ -15,6 +15,7 @@ from pathlib import Path from types import SimpleNamespace from unittest.mock import Mock +from uuid import uuid4 import pytest from click.testing import CliRunner @@ -44,6 +45,96 @@ def _skip_serverless_role_setup(monkeypatch: pytest.MonkeyPatch) -> None: ) +@pytest.mark.parametrize( + ("stage", "expected_prefix"), + [ + ("tool", "Failed to provision the AgentKit chat CodeEnv Tool"), + ( + "relay", + "Failed to provision the AgentKit chat model credential relay", + ), + ], +) +def test_studio_deploy_surfaces_redacted_provisioning_error_chain( + monkeypatch: pytest.MonkeyPatch, + stage: str, + expected_prefix: str, +) -> None: + access_key = uuid4().hex + bearer_value = uuid4().hex + model_key = uuid4().hex + secret_key = uuid4().hex + + def _fail(**_: object) -> str: + try: + raise ValueError(f"Authorization: Bearer {bearer_value}") + except ValueError as cause: + raise RuntimeError( + "CreateApiKeyCredentialProvider: AccessDenied\n" + f"Request: api_key={model_key}\n" + f"RequestId=req-123 {access_key}" + ) from cause + + monkeypatch.setattr( + "veadk.cli.cli_frontend._resolve_studio_identity_region", + lambda **kwargs: kwargs["deployment_region"], + ) + if stage == "tool": + monkeypatch.setattr( + "veadk.cli.studio_sandbox_tools.ensure_studio_code_env_tool", + _fail, + ) + tool_args: list[str] = [] + else: + monkeypatch.setattr( + "veadk.cli.frontend_skill_creator.ensure_skill_creator_model_credential", + _fail, + ) + tool_args = [ + "--sandbox-chat-codex-tool-id", + "chat-tool-id", + "--sandbox-skill-creator-tool-id", + "skill-tool-id", + ] + + result = CliRunner().invoke( + studio, + [ + "deploy", + "--user-pool-id", + "pool-id", + "--allowed-client-id", + "client-id", + "--vefaas-app-name", + "studio-app", + "--iam-role", + "trn:iam::role/test", + "--gateway-name", + "gateway", + "--volcengine-access-key", + access_key, + "--volcengine-secret-key", + secret_key, + *tool_args, + ], + ) + + assert result.exit_code == 1 + assert expected_prefix in result.output + assert ( + "Underlying error:\nCreateApiKeyCredentialProvider: AccessDenied" + in result.output + ) + assert "Request: api_key=***" in result.output + assert "RequestId=req-123" in result.output + assert "Caused by:\nAuthorization: Bearer ***" in result.output + assert access_key not in result.output + assert bearer_value not in result.output + assert model_key not in result.output + assert secret_key not in result.output + assert "***" in result.output + + @pytest.mark.parametrize( ("target_args", "expected_region", "expected_identity_region", "expected_project"), [ diff --git a/veadk/cli/cli_frontend.py b/veadk/cli/cli_frontend.py index dbf0ea2e..408896df 100644 --- a/veadk/cli/cli_frontend.py +++ b/veadk/cli/cli_frontend.py @@ -125,6 +125,30 @@ def _redact_debug_text(text: str) -> str: ) +def _safe_exception_detail( + error: BaseException, + *, + secrets: Iterable[str | None] = (), +) -> str: + """Return exception messages verbatim except for credential redaction.""" + parts: list[str] = [] + seen: set[int] = set() + current: BaseException | None = error + while current is not None and id(current) not in seen: + seen.add(id(current)) + message = _ANSI_ESCAPE_RE.sub("", str(current)).strip() + for secret in secrets: + if secret: + message = message.replace(secret, "***") + message = _redact_debug_text(message) + if not message: + message = type(current).__name__ + if message not in parts: + parts.append(message) + current = current.__cause__ or current.__context__ + return "\nCaused by:\n".join(parts) + + def _claims_from_forwarded_jwt(authorization: str | None) -> dict | None: """Decode the JWT an upstream API gateway forwarded in the Authorization header, WITHOUT re-verifying its signature. @@ -3695,9 +3719,13 @@ def frontend_deploy( session_token=session_token or "", ) except Exception as error: + detail = _safe_exception_detail( + error, + secrets=(ak, sk, session_token), + ) raise click.ClickException( f"Failed to provision the AgentKit {purpose} CodeEnv Tool. " - "Verify account permissions and AgentKit service status." + f"Underlying error:\n{detail}" ) from error click.echo(f"AgentKit {purpose} CodeEnv Tool is ready.") @@ -3720,9 +3748,13 @@ def frontend_deploy( session_token=session_token, ) except Exception as error: + detail = _safe_exception_detail( + error, + secrets=(ak, sk, session_token), + ) raise click.ClickException( f"Failed to provision the AgentKit {purpose} model credential relay. " - "Verify the Tool ID, account permissions, and AgentKit service status." + f"Underlying error:\n{detail}" ) from error click.echo(f"AgentKit {purpose} model credential relay is ready.")