{"record":{"id":"270437235b64983f","repo":"apereo/cas","slug":"no-transaction-found-for-s","errorCode":null,"errorMessage":"No transaction found for [%s]","messagePattern":"No transaction found for \\[(.+?)\\]","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/offer/OidcVerifiableCredentialDefaultOfferService.java","lineNumber":33,"sourceCode":" * @since 8.0.0\n */\n@RequiredArgsConstructor\n@Slf4j\npublic class OidcVerifiableCredentialDefaultOfferService implements OidcVerifiableCredentialOfferService {\n    private final OidcConfigurationContext configurationContext;\n    private final OidcVerifiableCredentialTransactionService transactionService;\n\n    @Override\n    public OidcVerifiableCredentialOffer create(final String clientId, final String principalId, final List<String> credentialConfigurationIds) {\n        val transaction = (TransientSessionTicket) transactionService.issue(clientId, principalId, credentialConfigurationIds);\n        return buildCredentialOffer(Objects.requireNonNull(transaction));\n    }\n\n    @Override\n    public OidcVerifiableCredentialOffer fetch(final String transactionId) {\n        val transaction = (TransientSessionTicket) transactionService.fetch(transactionId);\n        if (transaction == null) {\n            throw new IllegalArgumentException(String.format(\"No transaction found for [%s]\", transactionId));\n        }\n        return buildCredentialOffer(transaction);\n    }\n\n    private @NonNull OidcVerifiableCredentialOffer buildCredentialOffer(final TransientSessionTicket transaction) {\n        val credentialConfigurationIds = transaction.getProperty(\"credentialConfigurationIds\", List.class);\n        val issuer = configurationContext.getCasProperties().getAuthn().getOidc().getCore().getIssuer();\n\n        val grant = new OidcVerifiableCredentialOffer.Grants.PreAuthorizedCodeGrant();\n        grant.setTransactionCode(\n            OidcVerifiableCredentialOffer.Grants.TransactionCode\n                .builder()\n                .value(Objects.requireNonNull(transaction).getId())\n                .length(Objects.requireNonNull(transaction).getId().length())\n                .build()\n        );\n        grant.setPreAuthorizedCode(transaction.getPropertyAsString(\"preAuthorizedCode\"));\n        grant.setIssuerState(transaction.getPropertyAsString(\"issuerState\"));","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/offer/OidcVerifiableCredentialDefaultOfferService.java#L15-L51","documentation":"OidcVerifiableCredentialDefaultOfferService.fetch looks up a TransientSessionTicket by transactionId in the ticket registry and throws IllegalArgumentException when no such transaction ticket exists. It guards the credential-offer flow against unknown, expired, or consumed transaction IDs.","triggerScenarios":"Calling fetch(transactionId) with an ID that was never issued, one whose TransientSessionTicket expired (transient tickets have a short TTL), one already consumed/removed, or a ticket registry that lost the entry (restart, non-shared registry across nodes).","commonSituations":"User bookmarks/refreshes an offer URL after the transient ticket expired; load-balanced CAS nodes without a shared ticket registry; client reusing an old transactionId from a previous session.","solutions":["Restart the credential-offer flow to obtain a fresh transactionId","Check the TransientSessionTicket timeout configuration and raise it if users legitimately take longer","Verify all CAS nodes share the same ticket registry (Redis/JDBC/Hazelcast) so the ticket is visible cluster-wide"],"exampleFix":"// before\nOidcVerifiableCredentialOffer offer = offerService.fetch(oldTransactionId);\n// after\nString txId = startNewCredentialOfferTransaction();\nOidcVerifiableCredentialOffer offer = offerService.fetch(txId); // fresh, unexpired transaction","handlingStrategy":"try-catch","validationCode":"TransientSessionTicket t = (TransientSessionTicket) transactionService.fetch(txId);\nif (t == null) { restartOfferFlow(); }","typeGuard":null,"tryCatchPattern":"try { return offerService.fetch(txId); }\ncatch (IllegalArgumentException e) { // retry with a new transaction\n  String newTx = startNewTransaction(); return offerService.fetch(newTx); }","preventionTips":["Never cache transactionIds beyond the ticket TTL","Use a shared ticket registry in clustered deployments","Detect expiry client-side and restart the offer flow"],"tags":["oidc","vc","ticket","not-found"],"backgroundTag":"resource-not-found","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}