From ddfad2c4ce475eeed9a246e0c78332647a0112e0 Mon Sep 17 00:00:00 2001 From: tonycpsu Date: Thu, 16 Apr 2026 02:00:39 -0400 Subject: [PATCH] Add distinct JSON-RPC error code for captcha rejection (#2021) * Add distinct JSON-RPC error code for captcha rejection Previously submitRateLimitChallenge mapped CaptchaRejectedException to the generic USER_ERROR code (-1), making it indistinguishable from any other user error (bad params, unknown command, etc.). Introduce CaptchaRejectedErrorException and wire it to a new error code (-6 / CAPTCHA_REJECTED_ERROR) throughout the JSON-RPC layer. Callers can now reliably distinguish a rejected captcha token (user must obtain a fresh token) from a network failure (transient, worth retrying) or a generic argument error. The CLI exit code for this path becomes 6, consistent with the existing per-error-type exit code convention. Co-Authored-By: Claude Sonnet 4.6 * Add exit code 6 to man page --------- Co-authored-by: Claude Sonnet 4.6 --- man/signal-cli.1.adoc | 1 + src/main/java/org/asamk/signal/Main.java | 2 ++ .../commands/SubmitRateLimitChallengeCommand.java | 6 +++--- .../exceptions/CaptchaRejectedErrorException.java | 10 ++++++++++ .../signal/commands/exceptions/CommandException.java | 2 +- .../signal/jsonrpc/SignalJsonRpcCommandHandler.java | 6 ++++++ 6 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/asamk/signal/commands/exceptions/CaptchaRejectedErrorException.java diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 1c7d1b66..e349a2ad 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -1168,6 +1168,7 @@ signal-cli -a ACCOUNT trust -a RECIPIENT * *3*: Server or IO error * *4*: Sending failed due to untrusted key * *5*: Server rate limiting error +* *6*: CAPTCHA was rejected == Files diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index 9219a1d6..568f58c6 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -22,6 +22,7 @@ import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.ArgumentParserException; import net.sourceforge.argparse4j.inf.Namespace; +import org.asamk.signal.commands.exceptions.CaptchaRejectedErrorException; import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.RateLimitErrorException; @@ -128,6 +129,7 @@ public class Main { case IOErrorException ioErrorException -> 3; case UntrustedKeyErrorException untrustedKeyErrorException -> 4; case RateLimitErrorException rateLimitErrorException -> 5; + case CaptchaRejectedErrorException captchaRejectedErrorException -> 6; case null -> 2; }; } diff --git a/src/main/java/org/asamk/signal/commands/SubmitRateLimitChallengeCommand.java b/src/main/java/org/asamk/signal/commands/SubmitRateLimitChallengeCommand.java index 91c6fc67..d84b7bc5 100644 --- a/src/main/java/org/asamk/signal/commands/SubmitRateLimitChallengeCommand.java +++ b/src/main/java/org/asamk/signal/commands/SubmitRateLimitChallengeCommand.java @@ -3,9 +3,9 @@ package org.asamk.signal.commands; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.commands.exceptions.CaptchaRejectedErrorException; import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.IOErrorException; -import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.CaptchaRejectedException; import org.asamk.signal.output.OutputWriter; @@ -41,8 +41,8 @@ public class SubmitRateLimitChallengeCommand implements JsonRpcLocalCommand { } catch (IOException e) { throw new IOErrorException("Submit challenge error: " + e.getMessage(), e); } catch (CaptchaRejectedException e) { - throw new UserErrorException( - "Captcha rejected, it may be outdated, already used or solved from a different IP address."); + throw new CaptchaRejectedErrorException( + "Captcha rejected, it may be outdated, already used or solved from a different IP address.", e); } } } diff --git a/src/main/java/org/asamk/signal/commands/exceptions/CaptchaRejectedErrorException.java b/src/main/java/org/asamk/signal/commands/exceptions/CaptchaRejectedErrorException.java new file mode 100644 index 00000000..1be5ada1 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/exceptions/CaptchaRejectedErrorException.java @@ -0,0 +1,10 @@ +package org.asamk.signal.commands.exceptions; + +import org.asamk.signal.manager.api.CaptchaRejectedException; + +public final class CaptchaRejectedErrorException extends CommandException { + + public CaptchaRejectedErrorException(final String message, final CaptchaRejectedException cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/asamk/signal/commands/exceptions/CommandException.java b/src/main/java/org/asamk/signal/commands/exceptions/CommandException.java index 739aee91..e1622019 100644 --- a/src/main/java/org/asamk/signal/commands/exceptions/CommandException.java +++ b/src/main/java/org/asamk/signal/commands/exceptions/CommandException.java @@ -1,6 +1,6 @@ package org.asamk.signal.commands.exceptions; -public sealed abstract class CommandException extends Exception permits IOErrorException, RateLimitErrorException, UnexpectedErrorException, UntrustedKeyErrorException, UserErrorException { +public sealed abstract class CommandException extends Exception permits CaptchaRejectedErrorException, IOErrorException, RateLimitErrorException, UnexpectedErrorException, UntrustedKeyErrorException, UserErrorException { public CommandException(final String message) { super(message); diff --git a/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcCommandHandler.java b/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcCommandHandler.java index 89114dfc..5ce6c6c1 100644 --- a/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcCommandHandler.java +++ b/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcCommandHandler.java @@ -12,6 +12,7 @@ import org.asamk.signal.commands.Command; import org.asamk.signal.commands.JsonRpcMultiCommand; import org.asamk.signal.commands.JsonRpcRegistrationCommand; import org.asamk.signal.commands.JsonRpcSingleCommand; +import org.asamk.signal.commands.exceptions.CaptchaRejectedErrorException; import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.RateLimitErrorException; @@ -39,6 +40,7 @@ public class SignalJsonRpcCommandHandler { private static final int IO_ERROR = -3; private static final int UNTRUSTED_KEY_ERROR = -4; private static final int RATELIMIT_ERROR = -5; + private static final int CAPTCHA_REJECTED_ERROR = -6; private final Manager m; private final MultiAccountManager c; @@ -258,6 +260,10 @@ public class SignalJsonRpcCommandHandler { case RateLimitErrorException e -> throw new JsonRpcException(new JsonRpcResponse.Error(RATELIMIT_ERROR, e.getMessage(), getErrorDataNode(objectMapper, result))); + case CaptchaRejectedErrorException e -> throw new JsonRpcException(new JsonRpcResponse.Error( + CAPTCHA_REJECTED_ERROR, + e.getMessage(), + getErrorDataNode(objectMapper, result))); case UnexpectedErrorException e -> { logger.error("Command execution failed with unexpected error", e); throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.INTERNAL_ERROR,