Skip to content

Propagate JAVA_TOOL_OPTIONS to the KtLint Worker Daemon process - #1112

Closed
geirh-nav wants to merge 1 commit into
JLLeitschuh:mainfrom
geirh-nav:fix/1110-propagate-java-tool-options
Closed

Propagate JAVA_TOOL_OPTIONS to the KtLint Worker Daemon process#1112
geirh-nav wants to merge 1 commit into
JLLeitschuh:mainfrom
geirh-nav:fix/1110-propagate-java-tool-options

Conversation

@geirh-nav

Copy link
Copy Markdown

Summary

Fixes #1110

Gradle does not forward the JAVA_TOOL_OPTIONS environment variable to forked worker processes by default, unlike other forked JVMs such as Test task executors. This breaks tooling that relies on JVM flags injected via JAVA_TOOL_OPTIONS — for example, sandboxing/monitoring tools that require flags like -Djava.net.preferIPv4Stack=true to be active in every forked JVM, not just the main Gradle process or Test workers.

This PR re-propagates JAVA_TOOL_OPTIONS explicitly into the KtLint Worker Daemon's process environment when it is present in the environment running Gradle, matching the behavior of Test task executors. This mirrors the workaround suggested in the issue and verified there by the reporter.

Changes

  • BaseKtLintCheckTask: forward JAVA_TOOL_OPTIONS from the Gradle process environment into the Worker Daemon's forkOptions when set.
  • README.md: document the new automatic behavior.
  • CHANGELOG.md: add an entry under [Unreleased].
  • KtlintPluginTest: add a functional test that sets JAVA_TOOL_OPTIONS via GradleRunner.withEnvironment and asserts the JVM's "Picked up JAVA_TOOL_OPTIONS" startup diagnostic appears in the forked worker's output, confirming propagation reaches the Worker Daemon (not just the Gradle daemon).

Testing

  • :plugin:compileKotlin / :plugin:compileTestKotlin succeed.
  • The new functional test was run locally and printed Picked up JAVA_TOOL_OPTIONS: -Dktlint.gradle.test.marker=true from the forked KtLint Worker Daemon process, confirming the fix works as intended.
  • Note: in my local sandboxed environment, loopback TCP sockets are blocked, which prevents Gradle's Worker Daemon IPC from completing (this reproduces identically on unmodified main, so it is unrelated to this change). Full test suite execution should be verified via this repository's CI.

Gradle does not forward the JAVA_TOOL_OPTIONS environment variable to
forked worker processes by default, unlike other forked JVMs such as
Test task executors. This breaks tooling that relies on JVM flags
injected via JAVA_TOOL_OPTIONS (e.g. sandboxing/monitoring tools that
require -Djava.net.preferIPv4Stack=true to be active in every forked
JVM).

Re-propagate JAVA_TOOL_OPTIONS explicitly into the Worker Daemon
process environment when it is present in the environment running
Gradle, matching the behavior of Test task executors.
Adds a functional test asserting the option reaches the forked worker
process, and documents the behavior in the README.

Fixes JLLeitschuh#1110
@JLLeitschuh

Copy link
Copy Markdown
Owner

This seems like a bug in Gradle, not in downstream plugins. Do you have an issue open against http://github.com/gradle/gradle?

markus-photoncycle pushed a commit to markus-photoncycle/cplt that referenced this pull request Aug 14, 2026
…dboxed builds (navikt#170)

## Problem

Reported by user: `./gradlew ktlintFormat` fails under cplt even with
`allow_localhost_any = true`:

```
org.gradle.internal.remote.internal.ConnectException: Could not connect to server [... port:55788, addresses:[/127.0.0.1]]
```

cplt injects `-Djava.net.preferIPv4Stack=true` via `JAVA_TOOL_OPTIONS`
(a2199a9) so JVM connections stay pure IPv4 and match SBPL's
`localhost:*` filter (which cannot match IPv4-mapped
`::ffff:127.0.0.1`). But Gradle plugin workers — WorkerExecutor process
isolation, e.g. ktlint-gradle — fork JVMs with explicit `forkOptions {}`
and do not reliably propagate the environment. Verified against
[ktlint-gradle
source](https://github.com/JLLeitschuh/ktlint-gradle/blob/main/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/tasks/BaseKtLintCheckTask.kt#L260):
the fork sets `maxHeapSize`/`jvmArgs` but never touches `environment`.
The flag is lost, the worker's dual-stack socket produces
`::ffff:127.0.0.1`, SBPL denies the connect.

SBPL has no primitive for IPv4-mapped matching — this cannot be fixed in
the sandbox profile.

## Fix

Install a **guarded Gradle init script** at
`~/.gradle/init.d/cplt-sandbox.gradle` on sandbox launch (macOS, both
agent and exec/shell paths):

```groovy
if (System.getenv("__CPLT_WRAPPED") != null) {
    System.setProperty("java.net.preferIPv4Stack", "true")
    allprojects {
        tasks.withType(JavaExec).configureEach { jvmArgs "-Djava.net.preferIPv4Stack=true" }
        tasks.withType(Test).configureEach { jvmArgs "-Djava.net.preferIPv4Stack=true" }
    }
}
```

- **Guarded by `__CPLT_WRAPPED`** — inert in unsandboxed builds, safe to
leave installed
- **Idempotent** — rewrites only when content changes; no-op when
`~/.gradle` doesn't exist
- Covers the daemon itself plus `Test`/`JavaExec` forks

## Honest limitation

`WorkerExecutor` process-isolation forks (the exact ktlint-gradle case)
have **no public configuration hook** — an init script cannot reach
them. Those still require the upstream plugin fix:
[JLLeitschuh/ktlint-gradle#1110](JLLeitschuh/ktlint-gradle#1110)
/ PR [#1112](JLLeitschuh/ktlint-gradle#1112)
(`forkOptions { environment("JAVA_TOOL_OPTIONS", ...) }`). This PR
narrows the gap (daemon + task forks) and documents the rest; it does
not fully replace the plugin fix.

## Tests

3 new unit tests in `src/gradle_init.rs`: env-marker guard, no-op
without `~/.gradle`, idempotent write + stale-content refresh. `mise run
check` green: fmt, clippy, 260 unit + 698 lib tests.

Docs: `known-impacts.md` updated with the failure mode, the init-script
mitigation, and the plugin-side fix pattern. (Supersedes the docs-only
PR navikt#169.)
@geirh-nav

Copy link
Copy Markdown
Author

Good question — no, there wasn't one yet. Filed it now: gradle/gradle#38836

@geirh-nav

Copy link
Copy Markdown
Author

I actually prefer the fix-it-in-gradle idea, so I'll make a PR for gradle to sort this instead. If they don't want my change I might have to add another PR for this.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Propagate environment config

2 participants