floci-io/floci · error · AwsException

InstanceRefreshInProgress

InstanceRefreshInProgress

Error message

An active instance refresh already exists for Auto Scaling group '{asgName}'.

What it means

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.

Source

Thrown at src/main/java/io/github/hectorvent/floci/services/autoscaling/AutoScalingService.java:434

                .findFirst()
                .ifPresent(i -> i.setLifecycleState("Terminating"));
        if (decrementDesiredCapacity) {
            int newDesired = Math.max(asg.getMinSize(), asg.getDesiredCapacity() - 1);
            asg.setDesiredCapacity(newDesired);
        }
        groups.put(asgKey(region, asg.getAutoScalingGroupName()), asg);
    }

    // ── Instance refreshes ────────────────────────────────────────────────────

    public InstanceRefresh startInstanceRefresh(String region, String asgName, InstanceRefresh requestedRefresh) {
        AutoScalingGroup asg = requireGroup(region, asgName);
        boolean activeRefresh = instanceRefreshes.values().stream()
                .filter(r -> region.equals(r.getRegion()))
                .filter(r -> asgName.equals(r.getAutoScalingGroupName()))
                .anyMatch(r -> isActiveRefreshStatus(r.getStatus()));
        if (activeRefresh) {
            throw new AwsException("InstanceRefreshInProgress",
                    "An active instance refresh already exists for Auto Scaling group '" + asgName + "'.", 400);
        }

        Instant now = Instant.now();
        InstanceRefresh refresh = new InstanceRefresh();
        refresh.setInstanceRefreshId(UUID.randomUUID().toString());
        refresh.setAutoScalingGroupName(asgName);
        refresh.setStrategy(normalizeRefreshStrategy(requestedRefresh.getStrategy()));
        refresh.setStartTime(now);
        refresh.setRegion(region);
        copyDesiredConfiguration(requestedRefresh, refresh);
        copyPreferences(requestedRefresh, refresh);

        applyDesiredConfiguration(asg, refresh);
        List<String> instanceIds = markInstancesForRefresh(asg, refresh);
        if (instanceIds.isEmpty()) {
            refresh.setStatus("Successful");
            refresh.setStatusReason("Instance refresh completed.");

View on GitHub (pinned to 62ff490619)

Solutions

  1. Call DescribeInstanceRefreshes and wait for the active refresh to reach a terminal status before starting a new one
  2. If the stale refresh should be abandoned, call CancelInstanceRefresh, wait for it to show Cancelled, then start again
  3. Make the start operation idempotent in your automation by checking for an active refresh first

Example fix

// before
autoscaling.startInstanceRefresh(StartInstanceRefreshRequest.builder()
    .autoScalingGroupName(asgName).build());

// after
boolean active = autoscaling.describeInstanceRefreshes(DescribeInstanceRefreshesRequest.builder()
        .autoScalingGroupName(asgName).build())
    .instanceRefreshes().stream()
    .anyMatch(r -> r.status() == Status.pending || r.status() == Status.inProgress);
if (!active) {
    autoscaling.startInstanceRefresh(StartInstanceRefreshRequest.builder()
        .autoScalingGroupName(asgName).build());
}
Defensive patterns

Strategy: try-catch

Validate before calling

boolean active = autoscaling.describeInstanceRefreshes(DescribeInstanceRefreshesRequest.builder()
        .autoScalingGroupName(asgName).build())
    .instanceRefreshes().stream()
    .anyMatch(r -> r.status().equals("Pending") || r.status().equals("InProgress") || r.status().equals("Cancelling"));
if (active) return existingRefreshId;

Try / catch

catch (InstanceRefreshInProgressException e) { poll DescribeInstanceRefreshes until terminal, then either reuse the finished result or start a new refresh once; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14). Data as JSON: /api/errors/df7f4827ec2b02aa. Report an issue: GitHub.