Skip to content

Treat a missing k8s config scope as an empty one - #7452

Open
pditommaso wants to merge 1 commit into
masterfrom
k8s-missing-config-scope
Open

Treat a missing k8s config scope as an empty one#7452
pditommaso wants to merge 1 commit into
masterfrom
k8s-missing-config-scope

Conversation

@pditommaso

Copy link
Copy Markdown
Member

A pipeline that selects the Kubernetes executor without declaring a k8s block aborts before running anything:

Cannot invoke "java.util.Map.get(Object)" because "opts" is null
	at nextflow.k8s.K8sConfig.<init>(K8sConfig.groovy:220)
	at nextflow.k8s.K8sExecutor.memoizedMethodPriv$getK8sConfig(K8sExecutor.groovy:66)

K8sExecutor.getK8sConfig passes session.config.k8s straight to K8sConfig(Map); an absent scope navigates to null, and the constructor dereferences it on its first line.

Why this is a bug, not a missing requirement

Every k8s option either has a default or is legitimately unset, so a config with no k8s block is a valid configuration. The codebase already says so in two places:

  • K8sConfig() (no-arg) delegates to this(Collections.emptyMap());
  • K8sDriverLauncher:286 guards the same construction with config.k8s instanceof Map ? new K8sConfig(config.k8s as Map) : new K8sConfig().

The executor path is the one caller that does neither.

Fix

Tolerate the null in the constructor rather than at the call site, so the contract holds for every caller instead of being re-established at each one:

K8sConfig(Map options) {
    final opts = options ?: Collections.emptyMap()
    ...

Tests

Two specs, one per layer:

  • K8sConfigTestnew K8sConfig(null) behaves as the no-arg form, and defaults that do not come from the map (clientRefreshInterval) still apply.
  • K8sExecutorTestgetK8sConfig() with a session whose config has no k8s key, which is the reported path.

Both fail with the original NPE when the fix is reverted. Full nf-k8s suite: 216 tests, 0 failures.

🤖 Generated with Claude Code

A pipeline that selects the Kubernetes executor without declaring a `k8s` block
aborts with `Cannot invoke "java.util.Map.get(Object)" because "opts" is null`.
`K8sExecutor.getK8sConfig` passes `session.config.k8s` straight to
`K8sConfig(Map)`, and an absent scope navigates to null, which the constructor
dereferences on its first line.

Every `k8s` option has a default or is legitimately unset, so a config with no
`k8s` block is a valid configuration, not an error -- the no-arg constructor
already states as much by delegating to an empty map, and `K8sDriverLauncher`
guards the same call with an `instanceof Map` check. Only the executor path
does not, which is why the failure needs no unusual setup to reproduce: an
executor declaration and nothing else.

Fixed in the constructor rather than at the call site, so the contract holds for
every caller instead of being re-established at each one.

Both specs fail with the NPE when the fix is reverted.

Assisted-by: Claude Opus 5 (1M context) via Claude Code
Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
@netlify

netlify Bot commented Aug 6, 2026

Copy link
Copy Markdown

Deploy Preview for nextflow-docs canceled.

Name Link
🔨 Latest commit c836caa
🔍 Latest deploy log https://app.netlify.com/projects/nextflow-docs/deploys/6a74f98b12456d0009e866fe

@bentsherman

Copy link
Copy Markdown
Member

The established pattern for config classes is to guard at the call site (see for example DefaultObserverFactory). So I would rather stay consistent with that.

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.

2 participants