When you call onlyIf multiple times on a task, how are the predicates combined, and how can you make a skip reason visible in the logs?
answer
- onlyIf appends → AND
- setOnlyIf replaces
- OR must live inside one predicate
- reason overload since 7.6
- reason shown under --info
basics
~20 sMultiple onlyIf predicates are ANDed: the task runs only if all return true. To surface why it skipped, use the onlyIf(reason) { } overload (Gradle 7.6+); the reason appears in --info/--debug output when the task is skipped.
solid answer
~50 sEach `onlyIf {}` call **adds** a predicate rather than replacing the previous one, and Gradle ANDs them — the task runs only when **every** predicate is true; the first that returns false causes a `SKIPPED` outcome. There's also `setOnlyIf {}` which **replaces** the entire set with a single predicate (useful to clear inherited ones). For observability, Gradle 7.6 added overloads that take a human-readable reason: `onlyIf("reason") { ... }` and `setOnlyIf("reason") { ... }`. When the predicate evaluates false, the reason is recorded and shown in `--info`/`--debug` logs (e.g. 'Skipping task ... as task onlyIf is false: <reason>'), which is invaluable for debugging why a CI task was skipped. Without the reason overload, you only see `SKIPPED` with no explanation. Predicates receive the task instance, so they can branch on task state when ANDing several conditions.
code
kotlin · 5 linestasks.register("release") {
onlyIf("on main branch") { branch() == "main" }
onlyIf("CI build") { System.getenv("CI") != null }
// runs only if main AND CI; --info shows which reason failed
}go deeper
Know that adding more onlyIf makes the task harder to run (all must pass).
Explain AND semantics and the existence of a reason overload for logging.
Distinguish onlyIf (append/AND) from setOnlyIf (replace), express OR correctly, and use reasons for observability.
Mandate reason strings in shared build logic and govern when overriding plugin-contributed predicates with setOnlyIf is acceptable.
## Adding vs replacing predicates `Task.onlyIf(Spec)` **appends** a predicate. Internally Gradle keeps an AND-combined spec; calling it N times produces a logical AND of all N predicates. So: ```kotlin tasks.register("x") { onlyIf { a() } // predicate 1 onlyIf { b() } // predicate 2 — ANDed with 1 } // runs only if a() && b() ``` If any predicate returns false, the task is `SKIPPED`; Gradle does not necessarily evaluate the rest once one is false (short-circuit semantics for the run/skip decision). `Task.setOnlyIf(Spec)` **replaces** the whole accumulated spec with a single predicate. Use it to discard predicates added by a plugin or a convention you don't want. ## Making the skip reason visible Before Gradle 7.6 a skipped task just printed `> Task :x SKIPPED` with no explanation, which made debugging painful. Gradle 7.6 added reason-carrying overloads: ```kotlin tasks.register("deploy") { onlyIf("DEPLOY_ENV must be 'prod'") { System.getenv("DEPLOY_ENV") == "prod" } } ``` When the predicate is false and you run with `--info`, Gradle logs something like: ``` Skipping task ':deploy' as task onlyIf is false: DEPLOY_ENV must be 'prod' ``` This turns an opaque SKIPPED into a self-documenting decision, which is the recommended practice for non-obvious gates in shared build logic. ## Practical guidance - Use multiple `onlyIf` calls (or one combined predicate) to compose conditions; remember it's AND, not OR — for OR, combine inside one predicate. - Always attach a **reason** for non-trivial gates so CI logs explain skips. - Use `setOnlyIf` deliberately when you must override predicates contributed elsewhere; otherwise prefer additive `onlyIf` to avoid clobbering a plugin's logic.
- You want the task to run if either of two conditions holds. How do you express OR?Put the OR inside a single onlyIf predicate: onlyIf { condA || condB }. Separate onlyIf calls AND together, not OR.
- A plugin added an onlyIf you must override entirely. What do you call?setOnlyIf { ... } — it replaces the accumulated predicate rather than ANDing a new one.
- Where do you see the onlyIf reason string?In --info/--debug logs when the task is skipped (e.g. 'Skipping task ... as task onlyIf is false: <reason>').
saying these in an interview costs you the question
- Claiming multiple onlyIf calls OR together — they AND.
- Thinking a second onlyIf replaces the first — it appends; only setOnlyIf replaces.
- Assuming the reason shows at default log level — it appears at --info/--debug.