From 052e2cc313163f1b589bba0b567ac85bf4e76504 Mon Sep 17 00:00:00 2001 From: Shihan Pan Date: Tue, 6 Jan 2026 16:48:21 -0800 Subject: [PATCH 1/3] Improve 401 Unauthorized error message with permission guidance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Clarify that 401 errors can be due to invalid/expired API token OR insufficient permissions - Add direct link to Workato API client management documentation - Restructure error message to show both possible causes and resolution steps - Improve actionability of error message for users debugging auth issues 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../cli/utils/exception_handler.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/workato_platform_cli/cli/utils/exception_handler.py b/src/workato_platform_cli/cli/utils/exception_handler.py index 9b5b1b8..0c8b11d 100644 --- a/src/workato_platform_cli/cli/utils/exception_handler.py +++ b/src/workato_platform_cli/cli/utils/exception_handler.py @@ -246,11 +246,19 @@ def _handle_auth_error(e: UnauthorizedException) -> None: return click.echo("❌ Authentication failed") - click.echo(" Your API token may be invalid") - click.echo("💡 Please check your authentication:") + click.echo(" Your API token may be invalid or lack sufficient permissions") + click.echo() + click.echo("💡 This could be due to:") + click.echo(" • Invalid or expired API token") + click.echo(" • API client lacking required permissions for this operation") + click.echo() + click.echo("🔧 To resolve:") click.echo(" • Verify your API token is correct") click.echo(" • Run 'workato profiles list' to check your profile") click.echo(" • Run 'workato profiles use' to update your credentials") + click.echo() + click.echo("📚 Learn more about permissions required for API client") + click.echo(" https://docs.workato.com/en/platform-cli.html#authentication") def _handle_forbidden_error(e: ForbiddenException) -> None: From 6c85640aed2b6899e3de463b7ebefcd1b4c655b1 Mon Sep 17 00:00:00 2001 From: Shihan Pan Date: Thu, 15 Jan 2026 17:06:33 -0800 Subject: [PATCH 2/3] Added specific permissions required as hints for each commands --- .../cli/utils/exception_handler.py | 90 ++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/src/workato_platform_cli/cli/utils/exception_handler.py b/src/workato_platform_cli/cli/utils/exception_handler.py index 0c8b11d..6243a58 100644 --- a/src/workato_platform_cli/cli/utils/exception_handler.py +++ b/src/workato_platform_cli/cli/utils/exception_handler.py @@ -200,6 +200,78 @@ def _get_output_mode() -> str: return "table" +def _get_required_permissions(ctx: click.Context) -> list[str]: + """Get required Workato permissions for a CLI command. + + Args: + ctx: Click context object + + Returns: + List of required permission strings + """ + # Map CLI command names to required Workato permissions + permission_map = { + "init": [ + "Projects → Projects & folders", + "Projects → Connections", + "Projects → Recipes", + "Projects → Recipe Versions", + "Projects → Recipe lifecycle management", + "Projects → Export manifests", + "Tools → Collections & endpoints", + "Admin → Workspace details", + ], + "recipes": [ + "Projects → Export manifests", + "Projects → Recipes", + ], + "pull": [ + "Projects → Recipe lifecycle management", + "Projects → Export manifests", + ], + "push": [ + "Projects → Recipe lifecycle management", + ], + "api-clients": [ + "Tools → Clients & access profiles", + ], + "api-collections": [ + "Tools → Collections & endpoints", + ], + "assets": [ + "Projects → Export manifests", + ], + "connections": [ + "Projects → Connections", + ], + "connectors": [ + "Tools → Connector SDKs", + "Tools → Connectors", + ], + "data-tables": [ + "Tools → Data tables", + ], + "properties": [ + "Tools → Environment properties", + ], + "workspace": [ + "Admin → Workspace details", + ], + } + + # Get command name from context + # For nested commands like "workato recipes list", check parent context + command_name = ctx.info_name + if ctx.parent and ctx.parent.info_name and ctx.parent.info_name not in ("workato",): + # If parent is not root, use parent's name (the command group) + command_name = ctx.parent.info_name + + if not command_name: + return [] + + return permission_map.get(command_name, []) + + def _handle_client_error( e: BadRequestException | UnprocessableEntityException, ) -> None: @@ -235,6 +307,12 @@ def _handle_client_error( def _handle_auth_error(e: UnauthorizedException) -> None: """Handle 401 Unauthorized errors.""" output_mode = _get_output_mode() + ctx = click.get_current_context(silent=True) + + # Get required permissions for this command + required_permissions = [] + if ctx: + required_permissions = _get_required_permissions(ctx) if output_mode == "json": error_data = { @@ -245,9 +323,19 @@ def _handle_auth_error(e: UnauthorizedException) -> None: click.echo(json.dumps(error_data)) return - click.echo("❌ Authentication failed") + command_info = f" (command: {ctx.command_path})" if ctx else "" + click.echo(f"❌ Authentication failed{command_info}") click.echo(" Your API token may be invalid or lack sufficient permissions") click.echo() + + # Show required permissions if available + if required_permissions: + click.echo("🔐 Required permissions for this command:") + for permission in required_permissions: + click.echo(f" • {permission}") + click.echo("Ensure your API client has the permissions listed above") + click.echo() + click.echo("💡 This could be due to:") click.echo(" • Invalid or expired API token") click.echo(" • API client lacking required permissions for this operation") From 96d7527656b9726275a9fe9606663f850b78cc65 Mon Sep 17 00:00:00 2001 From: Shihan Pan Date: Tue, 27 Jan 2026 12:44:40 -0800 Subject: [PATCH 3/3] separate the authz and authn message for all commands except in workato init as there is no way to reliably differentiate between them at that step --- .../cli/utils/exception_handler.py | 106 ++++++++++++++---- 1 file changed, 83 insertions(+), 23 deletions(-) diff --git a/src/workato_platform_cli/cli/utils/exception_handler.py b/src/workato_platform_cli/cli/utils/exception_handler.py index 6243a58..a21488f 100644 --- a/src/workato_platform_cli/cli/utils/exception_handler.py +++ b/src/workato_platform_cli/cli/utils/exception_handler.py @@ -305,48 +305,108 @@ def _handle_client_error( def _handle_auth_error(e: UnauthorizedException) -> None: - """Handle 401 Unauthorized errors.""" + """Handle 401 Unauthorized errors. + + For 'workato init' command: Shows both authentication and authorization + possibilities since we cannot distinguish between them at this stage. + + For other commands: Shows only authorization error, assuming the token + was already validated during initialization. A 401 error is treated as + missing permissions. + + TODO: This is a temporary solution. The API should return distinct error + codes for authentication vs authorization failures. Track progress at: + https://github.com/workato-devs/workato-platform-cli-issues/issues/106 + """ output_mode = _get_output_mode() ctx = click.get_current_context(silent=True) + # Check if this is the init command (top-level init, not subcommands) + is_init_command = False + if ctx and ctx.command: + is_init_command = ( + ctx.command.name == "init" + and ctx.parent + and ctx.parent.info_name == "workato" + ) + # Get required permissions for this command required_permissions = [] if ctx: required_permissions = _get_required_permissions(ctx) if output_mode == "json": + if is_init_command: + error_msg = ( + "Authentication failed - invalid or missing API token " + "or insufficient permissions" + ) + else: + error_msg = "Authorization failed - insufficient permissions" + error_data = { "status": "error", - "error": "Authentication failed - invalid or missing API token", + "error": error_msg, "error_code": "UNAUTHORIZED", } click.echo(json.dumps(error_data)) return command_info = f" (command: {ctx.command_path})" if ctx else "" - click.echo(f"❌ Authentication failed{command_info}") - click.echo(" Your API token may be invalid or lack sufficient permissions") - click.echo() - - # Show required permissions if available - if required_permissions: - click.echo("🔐 Required permissions for this command:") - for permission in required_permissions: - click.echo(f" • {permission}") - click.echo("Ensure your API client has the permissions listed above") + + if is_init_command: + # For init command: Show both possibilities since we can't distinguish + click.echo(f"❌ Authentication failed{command_info}") + click.echo(" This could be due to:") + click.echo(" • Invalid or expired API token") + click.echo(" • API client lacking required permissions") + click.echo() + + # Show required permissions if available + if required_permissions: + click.echo("🔐 Required permissions for this command:") + for permission in required_permissions: + click.echo(f" • {permission}") + click.echo() + + click.echo("🔧 To resolve:") + click.echo(" • Verify your API token is correct") + click.echo( + " • Ensure your API client has all required permissions in Workato" + ) + click.echo(" • Run 'workato profiles list' to check your profile") + click.echo(" • Run 'workato profiles use' to update your credentials") + click.echo() + click.echo("📚 Learn more about authentication and permissions") + click.echo(" https://docs.workato.com/en/platform-cli.html#authentication") + else: + # For non-init commands: Show only authorization error + # Assumption: Token validity was already verified during init when + # the profile was created. If they're using an existing profile and + # getting 401, it's an authorization issue (missing permissions). + click.echo(f"❌ Authorization failed{command_info}") + click.echo( + " Your API client lacks the required permissions for this operation" + ) click.echo() - click.echo("💡 This could be due to:") - click.echo(" • Invalid or expired API token") - click.echo(" • API client lacking required permissions for this operation") - click.echo() - click.echo("🔧 To resolve:") - click.echo(" • Verify your API token is correct") - click.echo(" • Run 'workato profiles list' to check your profile") - click.echo(" • Run 'workato profiles use' to update your credentials") - click.echo() - click.echo("📚 Learn more about permissions required for API client") - click.echo(" https://docs.workato.com/en/platform-cli.html#authentication") + # Show required permissions if available + if required_permissions: + click.echo("🔐 Required permissions for this command:") + for permission in required_permissions: + click.echo(f" • {permission}") + click.echo() + + click.echo("🔧 To resolve:") + click.echo(" • Update your API client permissions in Workato") + click.echo(" • Ensure the permissions listed above are enabled") + click.echo( + " • Run 'workato profiles use' if you need to switch to " + "a different API client" + ) + click.echo() + click.echo("📚 Learn more about permissions required for API client") + click.echo(" https://docs.workato.com/en/platform-cli.html#authentication") def _handle_forbidden_error(e: ForbiddenException) -> None: