gradle/gradle · error · IllegalStateException
Cannot configure this operation (%s) once it has started.
Error message
Cannot configure this operation (%s) once it has started.
What it means
Configuration methods such as setDescription/setShortDescription guard themselves with assertCanConfigure(), which throws IllegalStateException once the operation's state is no longer idle. After started(), the operation is immutable configuration-wise; mid-run updates must go through progress(status) instead of reconfiguring.
Source
Thrown at platforms/core-runtime/logging/src/main/java/org/gradle/internal/logging/progress/DefaultProgressLoggerFactory.java:277
throw new IllegalStateException(String.format("This operation (%s) has already been started.", this));
}
if (state == State.completed) {
throw new IllegalStateException(String.format("This operation (%s) has already completed.", this));
}
}
private void assertRunning() {
if (state == State.idle) {
throw new IllegalStateException(String.format("This operation (%s) has not been started.", this));
}
if (state == State.completed) {
throw new IllegalStateException(String.format("This operation (%s) has already been completed.", this));
}
}
private void assertCanConfigure() {
if (state != State.idle) {
throw new IllegalStateException(String.format("Cannot configure this operation (%s) once it has started.", this));
}
}
}
}
View on GitHub (pinned to 534f27719b)
Solutions
- Set description (and all other configuration) before started(); treat it as write-once
- For mid-run updates use op.progress("phase 2 of 3") — status changes are allowed after start
- Compute any dynamic text before creating/starting the operation, or spawn a child operation per phase with its own description
Example fix
// before
op.setDescription("resolving");
op.started();
...
op.setDescription("downloading"); // IllegalStateException
// after
op.setDescription("resolving dependencies");
op.started();
...
op.progress("downloading artifacts"); // status updates only Defensive patterns
Strategy: validation
Validate before calling
ProgressLogger configureThenStart(ProgressLoggerFactory factory, String description) {
ProgressLogger op = factory.newOperation(MyTask.class);
op.setDescription(description); // all configuration BEFORE started()
op.started();
return op;
} // afterwards only op.progress(status) is allowed Try / catch
try {
op.setDescription(newDescription);
} catch (IllegalStateException e) {
if (String.valueOf(e.getMessage()).contains("once it has started")) {
op.progress(newDescription); // status updates are the supported mid-run channel
} else { throw e; }
} Prevention
- Set description and short description immediately after newOperation, before any lifecycle call
- Use progress(status) for mid-run updates instead of reconfiguring
- Compute dynamic description text before starting the operation, or use one child operation per phase
When it happens
Trigger: Calling op.setDescription("new text") (or other setters) after op.started() — e.g. adjusting the description when a phase changes, or late initialization that runs after the lifecycle already started.
Common situations: Code that computes the description lazily but starts eagerly; phase-based reporting that tries to rewrite the description per phase; race where a background thread configures an operation another thread already started.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- A description must be specified before this operation is sta
- This operation (%s) has already been started.
- This operation (%s) has already completed.
- This operation (%s) has not been started.
- This operation (%s) has already been completed.
AI-assisted analysis of gradle/gradle@534f27719b (2026-08-22).
Data as JSON: /api/errors/8c1ca8fc9b8164f9.
Report an issue: GitHub.