Skip to content

Fix reports.register() failing with "Cannot call TaskOutputs.file() after task started execution"#1618

Draft
Copilot wants to merge 13 commits intomasterfrom
copilot/check-issue-1539
Draft

Fix reports.register() failing with "Cannot call TaskOutputs.file() after task started execution"#1618
Copilot wants to merge 13 commits intomasterfrom
copilot/check-issue-1539

Conversation

Copy link
Copy Markdown

Copilot AI commented Mar 9, 2026

Using reports.register("xml") fails at runtime because the NamedDomainObjectFactory was calling outputs.file() lazily — the factory is only invoked when the container is first iterated, which happens inside buildArguments() during task execution, after Gradle has locked task outputs.

// Previously failed with:
// "Cannot call TaskOutputs.file(Object) on task ':spotbugsMain' after task has started execution"
tasks.spotbugsMain {
    reports {
        register("xml")
        register("html")
    }
}

Changes

  • Remove outputs.file() from the factory — domain object creation no longer has any side-effects on task outputs, making register() safe at any point in the lifecycle.

  • Re-register outputs lazily via reports.matching { } — replaces the removed call with a ConfigurableFileCollection backed by an anonymous Callable:

    • Uses (outputs as org.gradle.api.tasks.TaskOutputs) explicit cast — without it Kotlin resolves AbstractTask.getOutputs() to the internal TaskOutputsInternal, which violates the ArchUnit "no Gradle internal API" rule.
    • Uses reports.matching { } (Gradle's API) rather than reports.filter { } (Kotlin's Iterable.filter) — filter only iterates already-realized items and silently skips items registered with register(), causing those reports to be omitted from output tracking. matching realizes pending items on iteration.
    • Uses an anonymous Callable object rather than a lambda, following the existing NamedDomainObjectFactory pattern for configuration-cache serialization safety.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • dl.google.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED -Xmx4g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant -cp /home/REDACTED/.gradle/wrapper/dists/gradle-8.14.4-bin/92wwslzcyst3phie3o264zltu/gradle-8.14.4/lib/gradle-daemon-main-8.14.4.jar (dns block)

If you need me to access, download, or install something from one of these locations, you can either:


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

… during execution

When using reports.register() for lazy report registration, the domain objects
are realized at iteration time. Previously, the factory function called
(outputs as TaskOutputs).file() which fails if called during task execution.

Fix: remove the outputs.file() call from the factory and instead register all
required reports' output files via a lazy provider in the init block. The
provider is evaluated at task-graph finalization (before execution), not during
the task action itself.

Also adds a functional test covering the exact scenario from issue #1539:
registering multiple reports with register() without explicit required=true.

Fixes #1539

Co-authored-by: hazendaz <975267+hazendaz@users.noreply.github.com>
Copilot AI changed the title [WIP] Investigate issue 1539 and explore potential fix Fix reports.register() failing with "Cannot call TaskOutputs.file() after task has started execution" Mar 9, 2026
The previous approach had two bugs caught by CI:

1. ArchUnit violation: outputs.files() without explicit cast caused Kotlin
   to infer AbstractTask.getOutputs() return type as TaskOutputsInternal
   (internal Gradle API). Fix: cast to org.gradle.api.tasks.TaskOutputs.

2. Lazy items excluded from output tracking: Kotlin's Collection.filter{}
   calls Iterable.iterator() which only returns already-realized items.
   Items registered with reports.register() stay pending/lazy and are
   invisible to iterator(), so the provider returned an empty file list.
   Fix: use reports.matching{} (Gradle's API) which realizes pending items
   when the resulting collection is iterated.

Also uses an anonymous Callable object instead of a lambda, following the
existing NamedDomainObjectFactory pattern for configuration-cache safety.

Fixes #1539

Co-authored-by: hazendaz <975267+hazendaz@users.noreply.github.com>
Copilot AI changed the title Fix reports.register() failing with "Cannot call TaskOutputs.file() after task has started execution" Fix reports.register() failing with "Cannot call TaskOutputs.file() after task started execution" Mar 16, 2026
Copilot AI requested a review from hazendaz March 16, 2026 01:59
@hazendaz
Copy link
Copy Markdown
Member

@copilot See the ci builds results on this, they are failing for a different reason now. There are also a number of warnings that possibly are unrelated but might as well fix those if possible.

Co-authored-by: hazendaz <975267+hazendaz@users.noreply.github.com>
@hazendaz
Copy link
Copy Markdown
Member

@copilot the build doesn't like commit message 'Changes before error encountered' as it does not follow proper convention, can you rebase and fix that to 'fix: ' prefix like others have?

dependabot bot and others added 8 commits March 26, 2026 21:39
Bumps  and [picomatch](https://github.com/micromatch/picomatch). These dependencies needed to be updated together.

Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](micromatch/picomatch@2.3.1...2.3.2)

Updates `picomatch` from 4.0.3 to 4.0.4
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](micromatch/picomatch@2.3.1...2.3.2)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
- dependency-name: picomatch
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: hazendaz <975267+hazendaz@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: hazendaz <975267+hazendaz@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: hazendaz <975267+hazendaz@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: hazendaz <975267+hazendaz@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: hazendaz <975267+hazendaz@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: hazendaz <975267+hazendaz@users.noreply.github.com>
Co-authored-by: hazendaz <975267+hazendaz@users.noreply.github.com>
…dificationException

When Gradle serializes the task state for the configuration cache, it
visits the reports container's internal pending map. If any reports were
registered lazily via reports.register(), the serializer encounters
ProviderInternal objects in that map; realizing those providers moves
entries out of the map while the serializer is still iterating it,
causing a ConcurrentModificationException.

Fix: after executing the user's configuration action inside reports{},
call reports.toList() to force-realize every pending item. This empties
the pending map before the configuration-cache serializer ever visits it,
so the ConcurrentModificationException no longer occurs.

Fixes #1539

Co-authored-by: hazendaz <975267+hazendaz@users.noreply.github.com>

Agent-Logs-Url: https://github.com/spotbugs/spotbugs-gradle-plugin/sessions/822233bf-a461-40e9-bda2-fe726b230db6
Copy link
Copy Markdown
Author

Copilot AI commented Mar 26, 2026

@copilot the build doesn't like commit message '[Changes before error encountered](02d1ca5...

I investigated the CI failures more carefully and found two issues:

  1. Config-cache ConcurrentModificationException (the actual build failure): Gradle's configuration-cache serializer visits reports's internal pendingMap while the output Callable simultaneously tries to realize items from it. Fixed in 17cabe6 by adding reports.toList() in the reports(Action) method — this pre-realizes all lazily-registered items before the serializer ever visits the pending map.

  2. Commit message 02d1ca5 "Changes before error encountered": I attempted a git rebase -i to fix this, but the push mechanism available to me (report_progress) doesn't support force push (required after history rewriting). When the push failed, report_progress fetched origin and rebased, which caused git to skip our fix commits as "already applied" (same patch content) — leaving the old bad commit still visible in the branch history.

To fix the commit message, you'll need to force-push manually:

git fetch origin copilot/check-issue-1539
git checkout copilot/check-issue-1539
git rebase -i HEAD~6  # mark 02d1ca5 as 'reword'
git push --force-with-lease origin copilot/check-issue-1539

Or use Squash and merge on GitHub, which will combine all commits under the PR title.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants