Mike Wacker
10/10/2025, 7:46 PMExecution failed for task ':app:test'.
> Cannot cast object 'org.gradle.internal.serialize.codecs.core.ClosureCodec$BrokenObject@46f481e2' with class 'org.gradle.internal.serialize.codecs.core.ClosureCodec$BrokenObject' to class 'com.avast.gradle.dockercompose.TasksConfigurator'
The issue includes a fairly simple MCVE.Vampire
10/11/2025, 12:19 PMtasks.test in the dockerCompose { ... }.Mike Wacker
10/13/2025, 2:32 AMdockerCompose mainly adds dependencies to tasks.test (or whatever task is provided to isRequiredBy() in the dockerCompose block): dependsOn and finalizedBy. Though it also does have a doFirst if the task is a JavaForkOptions or ProcessForkOptions.
See the relevant lines:
https://github.com/avast/gradle-docker-compose-plugin/blob/34a7d182c1a11dbfe617865[…]/groovy/com/avast/gradle/dockercompose/TasksConfigurator.groovyMike Wacker
10/13/2025, 2:42 AMtest task is both a JavaForkOptions and ProcessForkOptions.Vampire
10/13/2025, 3:08 AMTasksConfigurator at execution time of the task.
But at execution time this confguration-time class is no longer accessible with configuration cache.
It is similar to trying to access some field of the script object at execution time.
The owner of the closure is serialized / deserialized as ClosureCodec.BrokenObject.
As long as the closure does not try to access it, all is fine.
But if it tries to access it, it fails, and as the class is compiled with CompileStatic, with a cast exception.Mike Wacker
10/13/2025, 6:01 AMbuildFeatures.configurationCache.active.get() at configuration time? (Assuming you inject a BuildFeatures into the appropriate object.) It's a Provider<Boolean>, but does that mean it's lazy configuration, or something you wouldn't know at configuration time? It seems to work, but I don't know if there's edge cases where it wouldn't work.
Not exactly the most elegant solution to wrap those closures with an if block that checks if the configuration cache is disabled, but I suspect that many people don't use those environment variables and system properties anyway. (Fixing TaskConfigurator may be a much more involved and/or difficult fix.)Vampire
10/13/2025, 6:54 AM.get() on a provider at configuration time if you can in any way avoid it, as you introduce the same race conditions you earn for using afterEvaluate. In this specific case it might be ok, as that boolean should be impossible to change.
But it really seems to be a bad idea. Next user is relying on the behavior and is wondering why it sometimes works and sometimes not.
Besides that, if those actions are changing the task configuration, it is anyway a very bad idea and should be changed, even without configuration cache.Mikhail Lopatkin
10/13/2025, 11:47 AMowner, delegate and this) when serializing. It may go unnoticed if the caller of the closure sets up the delegate before invoking (so accessing task stuff in doLast still works, for example, even unqualified), but this isn't the case here, as we're relying on the owner.
The immediate workaround for the plugin could be as simple as capturing composeSettings in a variable inside the isRequiredByCore and using this variable in the closures, though I'm not sure if the whole thing is CC-serializable, and how dynamic the values are.
As a plugin user, you can likely copy the logic of isRequiredByCore to your build, do necessary adjustments there, and use the fixed version whenever needed. But I'm not very familiar with the plugin, just looked at the MCVE.
Besides that, if those actions are changing the task configuration, it is anyway a very bad idea and should be changed, even without configuration cache.Sometimes you have to, when trying to wire dynamic stuff there. Ideally,
ProcessForkOptions and JavaForkOptions should have lazy API to configure environment variables and system properties, but we're not there yet. The "great Provider migration" will help when it happens.
does that mean it's lazy configuration, or something you wouldn't know at configuration time?In general, if a provider has some lifecycle restrictions, it is documented as such. In this particular case the value is indeed immutable within a given build, and can be accessed at any time.
Vampire
10/13/2025, 11:52 AMSometimes you have to, when trying to wire dynamic stuff there. Ideally,Sometimes, yes, but for env variables and sys properties unless the lazification landed you should usually be able to use `CommandLineArgumentProvider`s where you then can also cleanly model task inputs for up-to-dateness and cache key calculation if it is not static values you need to configure.andProcessForkOptionsshould have lazy API to configure environment variables and system properties, but we're not there yet. The "great Provider migration" will help when it happens.JavaForkOptions
Mikhail Lopatkin
10/13/2025, 12:01 PM@Input behavior there.Vampire
10/13/2025, 12:09 PMCommandLineArgumentProvider where you have full control over what is or is not considered an input even if it sometimes can be a bit boiler-platey. :-)Mike Wacker
10/13/2025, 10:26 PMcomposeSettings in a local variable: composeSettings.tasksConfigurator is null when those doFirst actions run; see ComposeSettings.getServicesInfos().