signalapp/Signal-Server · error · IllegalArgumentException
Invalid action:
Error message
Invalid action:
What it means
DeviceCheckController.Action.fromString converts a request-supplied string into the Action enum by case-insensitive name comparison; if no enum constant matches it throws IllegalArgumentException("Invalid action: " + action). It is a strict input-validation guard for the action query/body parameter.
Solutions
- Send one of the exact supported action values (case-insensitive match on the enum names, e.g. 'attest' or 'assert')
- Trim and normalize the action string on the client before sending
- Check the server's DeviceCheckController.Action enum for the list of valid values for your server version
- Upgrade the client or server if a newly added action is missing from one side
Example fix
// before
client.deviceCheckAction("attestation"); // IllegalArgumentException: Invalid action: attestation
// after
client.deviceCheckAction("attest"); // matches Action.ATTEST via fromString Defensive patterns
Strategy: validation
Validate before calling
final Set<String> valid = Arrays.stream(Action.values()).map(a -> a.name().toLowerCase(Locale.ROOT)).collect(Collectors.toSet());
if (!valid.contains(action.trim().toLowerCase(Locale.ROOT))) {
throw new IllegalArgumentException("action must be one of " + valid);
} Try / catch
try {
client.deviceCheckAction(action);
} catch (WebApplicationException | IllegalArgumentException e) {
if (String.valueOf(e).contains("Invalid action")) {
// fall back to a supported action or surface a config error
} else throw e;
} Prevention
- Use enum constants client-side, never hand-typed strings
- Trim/lowercase action values before sending
- Keep client enum in sync with server enum version
When it happens
Trigger: Calling the devicecheck endpoint with an action parameter that is not exactly one of the Action enum names (case-insensitive), e.g. 'attestation' instead of 'attest', a typo, trailing whitespace, or an empty string.
Common situations: API consumers guessing action values instead of using documented enum names; version drift where the client sends an action the server build does not define; URL encoding or whitespace corruption of the parameter.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Only primary devices may register attestations
- interrupted during delivery
- delivery cancelled
- failure during delivery
- Multi-recipient messages must be addressed to ACI service…
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/7e0fd9424e79c20e.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceCheckController.java:273
// The request assertion was validated, execute it
switch (request.assertionRequest().action()) {
case BACKUP -> backupAuthManager.extendBackupVoucher(
account,
new Account.BackupVoucher(BackupLevel.PAID.getValue(), clock.instant().plus(backupRedemptionDuration)));
}
}
public enum Action {
BACKUP;
@JsonCreator
public static Action fromString(final String action) {
for (final Action a : Action.values()) {
if (a.name().toLowerCase(Locale.ROOT).equals(action)) {
return a;
}
}
throw new IllegalArgumentException("Invalid action: " + action);
}
}
public record AssertionRequest(
@Schema(description = "The challenge retrieved at `GET /v1/devicecheck/assert`")
String challenge,
@Schema(description = "The type of action you'd like to perform with this assert",
allowableValues = {"backup"}, implementation = String.class)
Action action) {}
/*
* Parses the base64 encoded AssertionRequest, but preserves the rawJson as well
*/
public record AssertionRequestWrapper(AssertionRequest assertionRequest, byte[] rawJson) {
public static AssertionRequestWrapper fromString(String requestBase64) throws IOException {
final byte[] requestJson = Base64.getUrlDecoder().decode(requestBase64);
final AssertionRequest requestData = SystemMapper.jsonMapper().readValue(requestJson, AssertionRequest.class);View on GitHub (pinned to 100ab61c82)