ARCPRuntime.revokeWithRetry at lib/src/main/kotlin/dev/arcp/runtime/ARCPRuntime.kt:384 retries provisioner.revoke up to three times immediately back-to-back, with no delay between attempts, so a transient provider failure burns all retries inside a few milliseconds. The catch block at lib/src/main/kotlin/dev/arcp/runtime/ARCPRuntime.kt:393 also only handles IOException; any other exception type a provisioner might raise — ARCPException.Unavailable, IllegalStateException, the upstream HTTP library's own exception types — propagates out of the loop. Because revokeWithRetry is launched from the init block at lib/src/main/kotlin/dev/arcp/runtime/ARCPRuntime.kt:115 and from terminalCleanup, an uncaught exception bubbles up to the SupervisorJob and is swallowed silently, so the operator sees nothing.
Fix prompt: Add an exponential backoff between attempts (for example delay(initialDelay * 2.0.pow(attempt)) capped at a few seconds) and use withTimeout to bound total time spent on a single revocation. Broaden the catch to Exception (re-throwing CancellationException), log every failure with the credential id at warn level, and on final failure either move the credential to a "manual cleanup" channel or let the provisioner's pendingRevocations() reflect the retry intent. Add a CredentialProvisionerTest case that throws a non-IO exception and asserts the runtime logs but does not crash.
ARCPRuntime.revokeWithRetryatlib/src/main/kotlin/dev/arcp/runtime/ARCPRuntime.kt:384retriesprovisioner.revokeup to three times immediately back-to-back, with nodelaybetween attempts, so a transient provider failure burns all retries inside a few milliseconds. The catch block atlib/src/main/kotlin/dev/arcp/runtime/ARCPRuntime.kt:393also only handlesIOException; any other exception type a provisioner might raise —ARCPException.Unavailable,IllegalStateException, the upstream HTTP library's own exception types — propagates out of the loop. BecauserevokeWithRetryis launched from theinitblock atlib/src/main/kotlin/dev/arcp/runtime/ARCPRuntime.kt:115and fromterminalCleanup, an uncaught exception bubbles up to theSupervisorJoband is swallowed silently, so the operator sees nothing.Fix prompt: Add an exponential backoff between attempts (for example
delay(initialDelay * 2.0.pow(attempt))capped at a few seconds) and usewithTimeoutto bound total time spent on a single revocation. Broaden the catch toException(re-throwingCancellationException), log every failure with the credential id at warn level, and on final failure either move the credential to a "manual cleanup" channel or let the provisioner'spendingRevocations()reflect the retry intent. Add aCredentialProvisionerTestcase that throws a non-IO exception and asserts the runtime logs but does not crash.