{"record":{"id":"6023bdee58975efd","repo":"gradle/gradle","slug":"cannot-add-s-to-s-as-it-is-a-filtered-collec-6023bd","errorCode":null,"errorMessage":"Cannot add '%s' to '%s' as it is a filtered collection","messagePattern":"Cannot add '(.+?)' to '(.+?)' as it is a filtered collection","errorType":"validation","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"platforms/core-configuration/domain-object-collections/src/main/java/org/gradle/api/internal/collections/FilteredIndexedElementSource.java","lineNumber":30,"sourceCode":" * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.gradle.api.internal.collections;\n\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.ListIterator;\n\n// TODO make this work with pending elements\npublic class FilteredIndexedElementSource<T, S extends T> extends FilteredElementSource<T, S> implements IndexedElementSource<S> {\n    public FilteredIndexedElementSource(ElementSource<T> collection, CollectionFilter<S> filter) {\n        super(collection, filter);\n    }\n\n    @Override\n    public void add(int index, S element) {\n        throw new UnsupportedOperationException(String.format(\"Cannot add '%s' to '%s' as it is a filtered collection\", element, this));\n    }\n\n    @Override\n    public S get(int index) {\n        int nextIndex = 0;\n        for (T t : collection) {\n            S s = filter.filter(t);\n            if (s != null) {\n                if (nextIndex == index) {\n                    return s;\n                }\n                nextIndex++;\n            }\n        }\n        throw new IndexOutOfBoundsException();\n    }\n\n    @Override","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/gradle/gradle/blob/534f27719b66953f95cc907aae7f2c1b12f5482d/platforms/core-configuration/domain-object-collections/src/main/java/org/gradle/api/internal/collections/FilteredIndexedElementSource.java#L12-L48","documentation":"Resolution must happen while the owning project's state is exclusively locked. resolveGraphIfRequired checks hasMutableState() on the project model and throws IllegalResolutionException when resolution is attempted without that lock, because the graph could be mutated concurrently underneath the resolver.","triggerScenarios":"Resolving another project's configuration from a task or thread that does not own that project's lock: project(':lib').configurations.runtimeClasspath.resolve() inside a task of project A; resolving in background executors or worker threads; cross-project model queries during task execution.","commonSituations":"Custom tasks that reach into subproject configurations directly; migration to parallel execution or the configuration cache exposing previously hidden unsafe resolution; build logic resolving during doLast.","solutions":["Register the configuration as a task input (inputs.files(...)) so Gradle resolves it with proper locking and task dependencies","Resolve only configurations of the project whose code is executing","For cross-project data, consume artifacts/publications or task outputs instead of the other project's model"],"exampleFix":"// before\ntasks.register('aggregate') {\n    doLast {\n        def files = project(':lib').configurations.runtimeClasspath.resolve() // IllegalResolutionException\n    }\n}\n\n// after\ntasks.register('aggregate') {\n    def libCp = project(':lib').configurations.runtimeClasspath\n    inputs.files(libCp) // Gradle resolves up-front, with the lock and task dependencies\n    doLast { /* use the snapshot input files */ }\n}","handlingStrategy":"validation","validationCode":"void safeResolve(Project current, Configuration c) {\n    if (!current.configurations.names.contains(c.name)) {\n        throw new GradleException('Cross-project resolution is unsafe; register the configuration as a task input instead')\n    }\n    c.resolve()\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never call .resolve()/.files on another project's configuration","Wire cross-project inputs via inputs.files so Gradle owns locking and task dependencies","Keep resolution out of worker threads and doLast blocks"],"tags":["gradle","resolution","project-lock","parallel-build","configuration-cache"],"backgroundTag":"cross-project-resolution-lock","analyzedSha":"534f27719b66953f95cc907aae7f2c1b12f5482d","analyzedAt":"2026-08-22T08:09:12.375Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}