{"record":{"id":"df7f4827ec2b02aa","repo":"floci-io/floci","slug":"instancerefreshinprogress","errorCode":"InstanceRefreshInProgress","errorMessage":"An active instance refresh already exists for Auto Scaling group '{asgName}'.","messagePattern":"An active instance refresh already exists for Auto Scaling group '(.+?)'\\.","errorType":"exception","errorClass":"AwsException","httpStatus":400,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/services/autoscaling/AutoScalingService.java","lineNumber":434,"sourceCode":"                .findFirst()\n                .ifPresent(i -> i.setLifecycleState(\"Terminating\"));\n        if (decrementDesiredCapacity) {\n            int newDesired = Math.max(asg.getMinSize(), asg.getDesiredCapacity() - 1);\n            asg.setDesiredCapacity(newDesired);\n        }\n        groups.put(asgKey(region, asg.getAutoScalingGroupName()), asg);\n    }\n\n    // ── Instance refreshes ────────────────────────────────────────────────────\n\n    public InstanceRefresh startInstanceRefresh(String region, String asgName, InstanceRefresh requestedRefresh) {\n        AutoScalingGroup asg = requireGroup(region, asgName);\n        boolean activeRefresh = instanceRefreshes.values().stream()\n                .filter(r -> region.equals(r.getRegion()))\n                .filter(r -> asgName.equals(r.getAutoScalingGroupName()))\n                .anyMatch(r -> isActiveRefreshStatus(r.getStatus()));\n        if (activeRefresh) {\n            throw new AwsException(\"InstanceRefreshInProgress\",\n                    \"An active instance refresh already exists for Auto Scaling group '\" + asgName + \"'.\", 400);\n        }\n\n        Instant now = Instant.now();\n        InstanceRefresh refresh = new InstanceRefresh();\n        refresh.setInstanceRefreshId(UUID.randomUUID().toString());\n        refresh.setAutoScalingGroupName(asgName);\n        refresh.setStrategy(normalizeRefreshStrategy(requestedRefresh.getStrategy()));\n        refresh.setStartTime(now);\n        refresh.setRegion(region);\n        copyDesiredConfiguration(requestedRefresh, refresh);\n        copyPreferences(requestedRefresh, refresh);\n\n        applyDesiredConfiguration(asg, refresh);\n        List<String> instanceIds = markInstancesForRefresh(asg, refresh);\n        if (instanceIds.isEmpty()) {\n            refresh.setStatus(\"Successful\");\n            refresh.setStatusReason(\"Instance refresh completed.\");","sourceCodeStart":416,"sourceCodeEnd":452,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/services/autoscaling/AutoScalingService.java#L416-L452","documentation":"Thrown by startInstanceRefresh when an instance refresh in an active status (pending/in-progress/cancelling) already exists for the group. AWS allows only one active refresh per Auto Scaling group, so StartInstanceRefresh conflicts until the previous refresh completes, fails, or is cancelled. Floci returns InstanceRefreshInProgress (HTTP 400), matching the AWS error code.","triggerScenarios":"Calling StartInstanceRefresh twice without the first refresh reaching a terminal status (Successful/Failed/Cancelled); rapid re-invocations from a retrying script or deployment pipeline before the rolling refresh finishes.","commonSituations":"CI/CD pipelines that trigger refreshes on every commit without gating on refresh status; retry logic that re-sends after a timeout even though the first call succeeded; automation that assumes refreshes are instant in the emulator.","solutions":["Call DescribeInstanceRefreshes and wait for the active refresh to reach a terminal status before starting a new one","If the stale refresh should be abandoned, call CancelInstanceRefresh, wait for it to show Cancelled, then start again","Make the start operation idempotent in your automation by checking for an active refresh first"],"exampleFix":"// before\nautoscaling.startInstanceRefresh(StartInstanceRefreshRequest.builder()\n    .autoScalingGroupName(asgName).build());\n\n// after\nboolean active = autoscaling.describeInstanceRefreshes(DescribeInstanceRefreshesRequest.builder()\n        .autoScalingGroupName(asgName).build())\n    .instanceRefreshes().stream()\n    .anyMatch(r -> r.status() == Status.pending || r.status() == Status.inProgress);\nif (!active) {\n    autoscaling.startInstanceRefresh(StartInstanceRefreshRequest.builder()\n        .autoScalingGroupName(asgName).build());\n}","handlingStrategy":"try-catch","validationCode":"boolean active = autoscaling.describeInstanceRefreshes(DescribeInstanceRefreshesRequest.builder()\n        .autoScalingGroupName(asgName).build())\n    .instanceRefreshes().stream()\n    .anyMatch(r -> r.status().equals(\"Pending\") || r.status().equals(\"InProgress\") || r.status().equals(\"Cancelling\"));\nif (active) return existingRefreshId;","typeGuard":null,"tryCatchPattern":"catch (InstanceRefreshInProgressException e) { poll DescribeInstanceRefreshes until terminal, then either reuse the finished result or start a new refresh once; }","preventionTips":["Gate refresh-triggering pipelines on 'no active refresh' checks","Treat StartInstanceRefresh as non-idempotent and never blind-retry it"],"tags":["autoscaling","instance-refresh","conflict","aws-emulator"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}