feat(core): add required field to prevent unconfigured steps from running#785
feat(core): add required field to prevent unconfigured steps from running#785
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a required field for steps, allowing users to specify environment variables that must be present for a step to execute. The implementation includes updates to the Pkl configuration, Rust logic for environment variable validation, and new integration tests. The feedback suggests adding a test case to ensure that environment variables defined within the configuration file itself (the env block) correctly satisfy the requirement.
| @test "required: step is skipped when env is missing" { | ||
| cat >hk.pkl <<EOF | ||
| amends "$PKL_PATH/Config.pkl" | ||
|
|
||
| display_skip_reasons = List("missing-required-env") | ||
|
|
||
| hooks = new { | ||
| ["check"] { | ||
| steps = new { | ||
| ["test-required"] { | ||
| required = List("MANDATORY_VAR") | ||
| check = "echo 'Success'" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| EOF | ||
|
|
||
| run hk check --all | ||
| assert_success | ||
| assert_output --partial "skipped: missing required environment variable(s): MANDATORY_VAR" | ||
| } | ||
|
|
||
| @test "required: step runs when env is provided" { | ||
| cat >hk.pkl <<EOF | ||
| amends "$PKL_PATH/Config.pkl" | ||
|
|
||
| hooks = new { | ||
| ["check"] { | ||
| steps = new { | ||
| ["test-required"] { | ||
| required = List("MANDATORY_VAR") | ||
| check = "echo 'Success'" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| EOF | ||
|
|
||
| MANDATORY_VAR=1 run hk check --all | ||
| assert_success | ||
| assert_output --partial "test-required" | ||
| refute_output --partial "skipped" | ||
| } | ||
|
|
||
| @test "required: skip message can be hidden" { | ||
| cat >hk.pkl <<EOF | ||
| amends "$PKL_PATH/Config.pkl" | ||
|
|
||
| display_skip_reasons = List() // Hide all | ||
|
|
||
| hooks = new { | ||
| ["check"] { | ||
| steps = new { | ||
| ["test-required"] { | ||
| required = List("MANDATORY_VAR") | ||
| check = "echo 'Success'" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| EOF | ||
|
|
||
| run hk check --all | ||
| assert_success | ||
| refute_output --partial "skipped: missing required environment variable(s)" | ||
| } |
There was a problem hiding this comment.
The new tests cover cases where the environment variable is missing or provided via the process environment. It would be beneficial to also add a test case where the required environment variable is defined within hk.pkl itself, for example in the step's env block. This would ensure all methods of satisfying the requirement are covered by tests.
Here's a suggestion for such a test:
@test "required: step runs when env is provided in hk.pkl" {
cat >hk.pkl <<EOF
amends "$PKL_PATH/Config.pkl"
hooks = new {
["check"] {
steps = new {
["test-required"] {
required = List("MANDATORY_VAR")
check = "echo 'Success'"
env = new Mapping {
["MANDATORY_VAR"] = "1"
}
}
}
}
}
EOF
run hk check --all
assert_success
assert_output --partial "test-required"
refute_output --partial "skipped"
}0980022 to
e186134
Compare
Greptile SummaryThis PR introduces a Key changes:
One prior review concern remains open: Confidence Score: 4/5Safe to merge for the common case; one pre-existing P1 from a prior review thread (var().is_err() non-Unicode false-positive) is still unresolved. No new P0/P1 issues were found in this review pass. The sole new finding is a redundant strum annotation (P2). However, the src/step/job_builder.rs — the Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[build_step_jobs called] --> B{Step in skip_steps?}
B -- Yes --> C[Return job with existing SkipReason]
B -- No --> D{has_command_for run_type?}
D -- No --> E[Return job with NoCommandForRunType]
D -- Yes --> F{required is non-empty?}
F -- No --> I[filter_files]
F -- Yes --> G["Check each var:\nstd::env::var(e).is_err()\n&& !self.env.contains_key(e)"]
G --> H{Any missing?}
H -- Yes --> J[Return job with MissingRequiredEnv]
H -- No --> I
I --> K{files empty\n& has_filters?}
K -- Yes --> L[Return job with NoFilesToProcess]
K -- No --> M[Create workspace/batch jobs]
subgraph "env satisfaction sources"
N[process env\nincl. global env block\nvia std::env::set_var]
O[step.env\nincl. hook-level env\nmerged by hook.init]
end
G -.->|std::env::var| N
G -.->|self.env.contains_key| O
Reviews (2): Last reviewed commit: "Merge branch 'main' into feat/core/requi..." | Re-trigger Greptile |
| let missing: Vec<String> = self | ||
| .required | ||
| .iter() | ||
| .filter(|e| std::env::var(e).is_err() && !self.env.contains_key(*e)) |
There was a problem hiding this comment.
Use
var_os().is_none() instead of var().is_err() to detect unset variables
std::env::var(e).is_err() returns true for two distinct cases:
- The variable is not set (
VarError::NotPresent) — correct, should skip. - The variable is set but its value contains non-Unicode bytes (
VarError::NotUnicode) — incorrect, step should run.
Using std::env::var_os(e).is_none() correctly checks only for the "not set" case, since var_os accepts non-Unicode bytes and returns Some(OsString) whenever the key exists.
| .filter(|e| std::env::var(e).is_err() && !self.env.contains_key(*e)) | |
| .filter(|e| std::env::var_os(e).is_none() && !self.env.contains_key(*e)) |
| class Step { | ||
| /// List of environment variables that must be set for this step to run. | ||
| /// A variable is considered satisfied if it is present in the process environment, | ||
| /// the global `env` block in hk.pkl, or the step's own `env` block. | ||
| /// If any are missing, the step will be skipped with a clear message. |
There was a problem hiding this comment.
Doc omits hook-level
env block as a valid source
At runtime, hook.init() merges the hook-level env map into every step's env map before build_step_jobs is called (see src/hook.rs lines 304–319). As a result, variables declared in the hook's env block also satisfy the required check via self.env.contains_key(e), but the doc comment only mentions "the step's own env block".
Consider updating the doc to reflect all sources that satisfy the check:
| class Step { | |
| /// List of environment variables that must be set for this step to run. | |
| /// A variable is considered satisfied if it is present in the process environment, | |
| /// the global `env` block in hk.pkl, or the step's own `env` block. | |
| /// If any are missing, the step will be skipped with a clear message. | |
| /// List of environment variables that must be set for this step to run. | |
| /// A variable is considered satisfied if it is present in the process environment, | |
| /// the global `env` block in hk.pkl, the hook's `env` block, | |
| /// or the step's own `env` block. | |
| /// If any are missing, the step will be skipped with a clear message. | |
| required: List<String> = List() |
| assert_output --partial "test-required" | ||
| refute_output --partial "skipped" | ||
| } |
There was a problem hiding this comment.
Missing test coverage for global
env block and hook-level env block
The PR description and the Config.pkl doc both state that a required variable is satisfied when it appears in the global env block of hk.pkl. This path is exercised via std::env::set_var in config.init(), but there is no bats test confirming it. Likewise, the hook-level env block (merged into step.env during hook.init()) is also an accepted source but has no test.
Consider adding tests similar to the third test but using:
- A top-level
env { ["MANDATORY_VAR"] = "1" }block in the config (global env). - An
env { ["MANDATORY_VAR"] = "1" }block on the hook rather than the step (hook env).
This would guard against regressions in the merging logic that makes these sources work.
### 🚀 Features - **(betterleaks)** add betterleaks config to hk builtin config by [@hituzi-no-sippo](https://github.com/hituzi-no-sippo) in [#750](#750) - **(builtins)** add google-java-format to builtins by [@timothysparg](https://github.com/timothysparg) in [#777](#777) - **(builtins)** add dclint to builtins by [@timothysparg](https://github.com/timothysparg) in [#779](#779) - **(config)** set default value for exclude to List() by [@timothysparg](https://github.com/timothysparg) in [#781](#781) - **(core)** add required field to prevent unconfigured steps from running by [@timothysparg](https://github.com/timothysparg) in [#785](#785) - **(gitleaks)** add gitleaks config to hk builtin config by [@hituzi-no-sippo](https://github.com/hituzi-no-sippo) in [#749](#749) - **(mdschema)** add mdschema config to hk builtin config by [@hituzi-no-sippo](https://github.com/hituzi-no-sippo) in [#748](#748) - **(pkl)** add pklr as opt-in pkl backend by [@jdx](https://github.com/jdx) in [#769](#769) - add pklr as opt-in pkl backend by [@jdx](https://github.com/jdx) in [#768](#768) ### 🐛 Bug Fixes - **(docs)** replace invalid /latest/ pkl package URIs with versioned format by [@jdx](https://github.com/jdx) in [#770](#770) - **(stage)** do not stage pre-existing untracked files by [@jdx](https://github.com/jdx) in [#788](#788) ### 📚 Documentation - add benchmarks page and reproducible benchmark suite by [@jdx](https://github.com/jdx) in [#766](#766) - add recommended setup section to mise integration by [@timothysparg](https://github.com/timothysparg) in [#780](#780) ### 📦️ Dependency Updates - lock file maintenance by [@renovate[bot]](https://github.com/renovate[bot]) in [#762](#762) - update rust crate pklr to 0.4 by [@renovate[bot]](https://github.com/renovate[bot]) in [#776](#776) - update apple-actions/import-codesign-certs digest to fe74d46 by [@renovate[bot]](https://github.com/renovate[bot]) in [#774](#774) - update anthropics/claude-code-action digest to 094bd24 by [@renovate[bot]](https://github.com/renovate[bot]) in [#773](#773) - update taiki-e/upload-rust-binary-action digest to 0e34102 by [@renovate[bot]](https://github.com/renovate[bot]) in [#775](#775) - bump usage to 3.2.0 and pkl to 0.31.1, add windows platforms by [@jdx](https://github.com/jdx) in [#787](#787) - lock file maintenance by [@renovate[bot]](https://github.com/renovate[bot]) in [#786](#786) ### New Contributors - @timothysparg made their first contribution in [#781](#781) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Primarily a version/documentation bump, but it also updates the Rust dependency lockfile (e.g., `hyper` and `windows-sys`), which could introduce build/runtime regressions. > > **Overview** > Bumps hk to **v1.40.0** and publishes the corresponding release notes in `CHANGELOG.md`. > > Updates generated CLI/docs and all Pkl package URL references in docs/examples to point at `v1.40.0`, and refreshes `Cargo.lock` with dependency updates/removals consistent with the new release. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit da00ab8. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: mise-en-dev <123107610+mise-en-dev@users.noreply.github.com>
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [hk](https://github.com/jdx/hk) | minor | `1.39.0` → `1.40.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>jdx/hk (hk)</summary> ### [`v1.40.0`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1400---2026-04-01) [Compare Source](jdx/hk@v1.39.0...v1.40.0) ##### 🚀 Features - **(betterleaks)** add betterleaks config to hk builtin config by [@​hituzi-no-sippo](https://github.com/hituzi-no-sippo) in [#​750](jdx/hk#750) - **(builtins)** add google-java-format to builtins by [@​timothysparg](https://github.com/timothysparg) in [#​777](jdx/hk#777) - **(builtins)** add dclint to builtins by [@​timothysparg](https://github.com/timothysparg) in [#​779](jdx/hk#779) - **(config)** set default value for exclude to List() by [@​timothysparg](https://github.com/timothysparg) in [#​781](jdx/hk#781) - **(core)** add required field to prevent unconfigured steps from running by [@​timothysparg](https://github.com/timothysparg) in [#​785](jdx/hk#785) - **(gitleaks)** add gitleaks config to hk builtin config by [@​hituzi-no-sippo](https://github.com/hituzi-no-sippo) in [#​749](jdx/hk#749) - **(mdschema)** add mdschema config to hk builtin config by [@​hituzi-no-sippo](https://github.com/hituzi-no-sippo) in [#​748](jdx/hk#748) - **(pkl)** add pklr as opt-in pkl backend by [@​jdx](https://github.com/jdx) in [#​769](jdx/hk#769) - add pklr as opt-in pkl backend by [@​jdx](https://github.com/jdx) in [#​768](jdx/hk#768) ##### 🐛 Bug Fixes - **(docs)** replace invalid /latest/ pkl package URIs with versioned format by [@​jdx](https://github.com/jdx) in [#​770](jdx/hk#770) - **(stage)** do not stage pre-existing untracked files by [@​jdx](https://github.com/jdx) in [#​788](jdx/hk#788) ##### 📚 Documentation - add benchmarks page and reproducible benchmark suite by [@​jdx](https://github.com/jdx) in [#​766](jdx/hk#766) - add recommended setup section to mise integration by [@​timothysparg](https://github.com/timothysparg) in [#​780](jdx/hk#780) ##### 📦️ Dependency Updates - lock file maintenance by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​762](jdx/hk#762) - update rust crate pklr to 0.4 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​776](jdx/hk#776) - update apple-actions/import-codesign-certs digest to [`fe74d46`](jdx/hk@fe74d46) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​774](jdx/hk#774) - update anthropics/claude-code-action digest to [`094bd24`](jdx/hk@094bd24) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​773](jdx/hk#773) - update taiki-e/upload-rust-binary-action digest to [`0e34102`](jdx/hk@0e34102) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​775](jdx/hk#775) - bump usage to 3.2.0 and pkl to 0.31.1, add windows platforms by [@​jdx](https://github.com/jdx) in [#​787](jdx/hk#787) - lock file maintenance by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​786](jdx/hk#786) ##### New Contributors - [@​timothysparg](https://github.com/timothysparg) made their first contribution in [#​781](jdx/hk#781) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDIuMTAiLCJ1cGRhdGVkSW5WZXIiOiI0My4xMDIuMTAiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbIlJlbm92YXRlIEJvdCIsImF1dG9tYXRpb246Ym90LWF1dGhvcmVkIiwiZGVwZW5kZW5jeS10eXBlOjptaW5vciJdfQ==-->
Some linters require user-provided configuration to be useful. I ran into this when I wanted to add addLicense as a builtin linter.
With this configuration addLicense will default to an
Apache license, withGoogle LLCas the copyright holder. Which for the vast majority of people would be a misconfiguration.If we add this as a Builtin then in the hk.pkl users would end up with something like
Which doesn't really feel like much of a win.
I came across
step_conditionwhich allowed me to do the followingbut I'm not convinced that this is really what
step_conditionwas designed for. I think it could work in a pinch, but that syntax is a but meh from a usability perspective.So it seems that we are currently have the following options
Proposal
Add a required field to Step that declares which environment variables must be set for the step
to run. If any are missing, the step is gracefully skipped with a clear message rather than
running incorrectly.
["addlicense"] {
required = List("LICENSE_FILE")
check = "addlicense --check -f $LICENSE_FILE {{files}}"
fix = "addlicense -f $LICENSE_FILE {{files}}"
}
A variable is considered satisfied if it is present in any of:
The skip message is surfaced via display_skip_reasons:
display_skip_reasons = List("missing-required-env")