Skip to content

fix: skip subhelmfiles when selectors conflict with CLI selectors#2545

Merged
yxxhero merged 6 commits into
mainfrom
fix-2544-subhelmfile-selector-skip
Apr 25, 2026
Merged

fix: skip subhelmfiles when selectors conflict with CLI selectors#2545
yxxhero merged 6 commits into
mainfrom
fix-2544-subhelmfile-selector-skip

Conversation

@yxxhero

@yxxhero yxxhero commented Apr 19, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #2544

When CLI selectors are provided (e.g. helmfile template -l name=b), subhelmfiles whose explicit selectors are provably incompatible with those CLI selectors are now skipped entirely — avoiding unnecessary YAML loading, Go template rendering, and release processing.

Problem

Previously, every subhelmfile was fully evaluated regardless of whether its selectors could match the CLI selectors. For projects with many subhelmfiles (each partitioned by selector), this caused significant unnecessary work:

helmfiles:
- path: subhelmfile-a.yaml
  selectors: [name=a]
- path: subhelmfile-b.yaml
  selectors: [name=b]

Running helmfile template -l name=b would fully load and render all subhelmfiles, only filtering releases after the expensive template execution.

Solution

Added a pre-filtering check in processNestedHelmfiles that detects when a subhelmfile's selectors are provably incompatible with the parent selectors, and skips the subhelmfile entirely before any file I/O or template rendering.

Compatibility check (SelectorsAreCompatible)

Two selector sets are incompatible when every pair of individual selectors (one from each set) has a positive label conflict: same key with different values. Only positive labels (key=value) are compared; negative labels (key!=value) are ignored for simplicity and conservatism.

Examples:

  • CLI name=b vs subhelmfile name=aincompatible → skip
  • CLI name=b vs subhelmfile name=bcompatible → process
  • CLI name=b vs subhelmfile env=prodcompatible (different keys) → process
  • CLI name=b vs subhelmfile name=a, name=bcompatible (one pair matches) → process

The check only applies when both CLI selectors and subhelmfile selectors are present and the subhelmfile does not inherit selectors.

Changes

File Change
pkg/state/release_filters.go Added SelectorsAreCompatible, parseLabelFilters, positiveLabelsCompatibleWith
pkg/app/app.go Added subhelmfileSelectorsConflict guard in processNestedHelmfiles
pkg/state/release_filters_test.go 12 table-driven test cases for SelectorsAreCompatible
pkg/app/app_test.go Updated expectations for subhelmfile selector filtering
test/integration/test-cases/issue-2544.* Integration test with 3 scenarios

Test plan

  • Unit tests: go test ./pkg/state/... ./pkg/app/... -race -p=1 — all pass
  • Lint: make check — clean
  • Integration test: test/integration/test-cases/issue-2544.sh (requires minikube)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes Helmfile’s nested-helmfile processing by skipping subhelmfiles early when their explicit selectors cannot possibly match the selectors provided to the parent (notably CLI -l), addressing unnecessary YAML/template work reported in #2544.

Changes:

  • Add selector compatibility logic (SelectorsAreCompatible) to detect provable label conflicts (positive k=v only).
  • Short-circuit processNestedHelmfiles to skip incompatible subhelmfiles before loading/rendering.
  • Add unit + integration coverage for the new skipping behavior (issue-2544).

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pkg/state/release_filters.go Introduces selector compatibility helper functions for conflict detection.
pkg/app/app.go Adds an early skip guard in processNestedHelmfiles based on selector compatibility.
pkg/state/release_filters_test.go Adds table-driven tests for SelectorsAreCompatible.
pkg/app/app_test.go Updates selector-filtering expectations due to earlier skipping of incompatible subhelmfiles.
test/integration/test-cases/issue-2544/input/helmfile.yaml Adds integration fixture defining three subhelmfiles partitioned by selector.
test/integration/test-cases/issue-2544/input/subhelmfile-a.yaml Adds integration fixture release a.
test/integration/test-cases/issue-2544/input/subhelmfile-b.yaml Adds integration fixture release b.
test/integration/test-cases/issue-2544/input/subhelmfile-c.yaml Adds integration fixture release c.
test/integration/test-cases/issue-2544.sh Adds integration test scenarios validating skip behavior with/without -l.
test/integration/run.sh Registers the new integration test script in the suite.

Comment thread pkg/app/app.go Outdated
Comment on lines +1043 to +1048
func subhelmfileSelectorsConflict(parentSelectors []string, m state.SubHelmfileSpec) bool {
if len(parentSelectors) == 0 || len(m.Selectors) == 0 || m.SelectorsInherited {
return false
}
compatible, _ := state.SelectorsAreCompatible(parentSelectors, m.Selectors)
return !compatible

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subhelmfileSelectorsConflict compares against parentSelectors (i.e., opts.Selectors), which for nested states can be a selector coming from an explicit selector in a parent helmfile entry—not necessarily the original CLI -l selectors. That means nested subhelmfiles can now be skipped even when the user didn’t pass any CLI selector, purely due to intermediate explicit selectors. If the intent is “CLI selectors only”, consider threading the original CLI selectors separately and using those here.

Copilot uses AI. Check for mistakes.
Comment on lines +71 to +158
func TestSelectorsAreCompatible(t *testing.T) {
tests := []struct {
name string
selectorsA []string
selectorsB []string
compatible bool
}{
{
name: "same key different value",
selectorsA: []string{"name=b"},
selectorsB: []string{"name=a"},
compatible: false,
},
{
name: "same key same value",
selectorsA: []string{"name=b"},
selectorsB: []string{"name=b"},
compatible: true,
},
{
name: "different keys no conflict",
selectorsA: []string{"name=b"},
selectorsB: []string{"env=prod"},
compatible: true,
},
{
name: "one compatible pair among multiple selectors",
selectorsA: []string{"name=b"},
selectorsB: []string{"name=a", "name=b"},
compatible: true,
},
{
name: "all pairs conflict",
selectorsA: []string{"name=b"},
selectorsB: []string{"name=a", "name=c"},
compatible: false,
},
{
name: "one compatible pair with same key same value",
selectorsA: []string{"name=b", "env=prod"},
selectorsB: []string{"name=b"},
compatible: true,
},
{
name: "empty selectorsA always compatible",
selectorsA: []string{},
selectorsB: []string{"name=a"},
compatible: true,
},
{
name: "empty selectorsB always compatible",
selectorsA: []string{"name=a"},
selectorsB: []string{},
compatible: true,
},
{
name: "negative labels not compared treated as compatible",
selectorsA: []string{"name!=a"},
selectorsB: []string{"name=a"},
compatible: true,
},
{
name: "compound selector with conflicting key",
selectorsA: []string{"name=a,env=prod"},
selectorsB: []string{"name=a,env=staging"},
compatible: false,
},
{
name: "compound selector with matching keys",
selectorsA: []string{"name=a,env=prod"},
selectorsB: []string{"name=a,env=prod"},
compatible: true,
},
{
name: "compound selector partial overlap different key",
selectorsA: []string{"name=a,env=prod"},
selectorsB: []string{"name=a"},
compatible: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := SelectorsAreCompatible(tt.selectorsA, tt.selectorsB)
assert.NoError(t, err)
assert.Equal(t, tt.compatible, got)
})
}

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SelectorsAreCompatible has an explicit behavior on parse error (conservative true + error), but the new tests don’t cover this path. Adding a case with a malformed selector (e.g. "invalid_label") would lock in the intended contract and prevent future refactors from accidentally changing whether malformed input can cause subhelmfile skipping.

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +68
// SelectorsAreCompatible checks whether any pair of selectors from two sets could
// potentially match the same release. It compares only positive labels (key=value):
// two selectors conflict if they require the same key to have different values.
// Returns true if at least one pair is compatible, false only if all pairs conflict.
// On parse error, returns true (conservative: never skip on malformed input).
func SelectorsAreCompatible(selectorsA, selectorsB []string) (bool, error) {
if len(selectorsA) == 0 || len(selectorsB) == 0 {
return true, nil
}

filtersA, err := parseLabelFilters(selectorsA)
if err != nil {
return true, err
}

filtersB, err := parseLabelFilters(selectorsB)
if err != nil {
return true, err
}

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment says "On parse error, returns true" but the implementation returns (true, err). Either update the comment to reflect the actual contract (conservative result + non-nil error), or change the function to return a nil error when choosing to be conservative, so callers don’t accidentally treat parse errors as fatal for this optimization helper.

Copilot uses AI. Check for mistakes.
Comment thread pkg/app/app.go Outdated
Comment on lines +1010 to +1012
if subhelmfileSelectorsConflict(opts.Selectors, m) {
continue
}

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new early-skip changes selector semantics: subhelmfiles with explicit selectors: can now be skipped based on the parent/CLI selectors, but docs currently state that when a selector is specified “only this selector applies and the parents or CLI selectors are ignored” (docs/index.md around the helmfiles.selectors section). If this behavior change is intended, the user docs should be updated; if not intended, this guard should be restricted to true CLI selectors only (not opts.Selectors that may come from an explicit selector higher in the nesting).

Copilot uses AI. Check for mistakes.
@yxxhero

yxxhero commented Apr 19, 2026

Copy link
Copy Markdown
Member Author

@copilot fix comments

Copilot AI commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

@copilot fix comments

Fixed all 4 review comments in commit 1f79a18:

  1. Comments 1 & 4 (use CLI selectors only): Changed subhelmfileSelectorsConflict to use a.Selectors (the original CLI selectors) instead of opts.Selectors (which could be inherited parent selectors from nested helmfiles). This ensures the optimization only applies when the user explicitly provided -l selectors on the command line.

  2. Comment 3 (doc comment mismatch): Updated SelectorsAreCompatible doc comment to accurately describe the (true, err) return contract on parse error.

  3. Comment 2 (missing malformed selector test): Added a "malformed selector returns conservative true with error" test case that verifies both the conservative true result and the non-nil error on malformed input.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comment thread pkg/state/release_filters.go Outdated
Comment on lines +98 to +104
otherPos := make(map[string]string, len(other.positiveLabels))
for _, kv := range other.positiveLabels {
otherPos[kv[0]] = kv[1]
}
for _, kv := range l.positiveLabels {
if v, ok := otherPos[kv[0]]; ok && v != kv[1] {
return false

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

positiveLabelsCompatibleWith allocates a new map for every filter-pair comparison. Since SelectorsAreCompatible can compare many pairs (and may be invoked repeatedly per subhelmfile), consider avoiding per-comparison allocations (e.g., compare slices directly given they’re usually small, or precompute a key→value map once per parsed selector).

Suggested change
otherPos := make(map[string]string, len(other.positiveLabels))
for _, kv := range other.positiveLabels {
otherPos[kv[0]] = kv[1]
}
for _, kv := range l.positiveLabels {
if v, ok := otherPos[kv[0]]; ok && v != kv[1] {
return false
for _, kv := range l.positiveLabels {
for _, otherKV := range other.positiveLabels {
if kv[0] == otherKV[0] && kv[1] != otherKV[1] {
return false
}

Copilot uses AI. Check for mistakes.
yxxhero added a commit that referenced this pull request Apr 19, 2026
Compare positive label slices directly instead of allocating a map per
comparison, as label counts are typically small (1-3 entries).

Addresses Copilot review comment on PR #2545.

Signed-off-by: yxxhero <[email protected]>
@yxxhero yxxhero requested a review from Copilot April 19, 2026 04:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment thread docs/index.md Outdated
Comment on lines +1525 to +1526
* When a selector is specified, only this selector applies and the parents or CLI selectors are ignored.
* When CLI selectors are provided (e.g. `helmfile -l name=b sync`) and a subhelmfile has explicit selectors that are provably incompatible (same key, different value), that subhelmfile is **skipped entirely** without loading or rendering it. For example, with `-l name=b`, a subhelmfile with `selectors: [name=a]` will be skipped since no release could match both. This optimization does not apply when `selectorsInherited: true` is set or when no CLI selectors are provided. Use `--debug` to see log messages about skipped subhelmfiles.

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The preceding bullet says explicit subhelmfile selectors ignore parent/CLI selectors, but the new bullet introduces a case where CLI selectors cause an explicit-selector subhelmfile to be skipped. Consider rewriting the first bullet (or merging the two) to clarify that while explicit selectors still determine release filtering, subhelmfiles with provably incompatible explicit selectors may be skipped when CLI selectors are set, so CLI selectors are no longer always "ignored" at the subhelmfile level.

Suggested change
* When a selector is specified, only this selector applies and the parents or CLI selectors are ignored.
* When CLI selectors are provided (e.g. `helmfile -l name=b sync`) and a subhelmfile has explicit selectors that are provably incompatible (same key, different value), that subhelmfile is **skipped entirely** without loading or rendering it. For example, with `-l name=b`, a subhelmfile with `selectors: [name=a]` will be skipped since no release could match both. This optimization does not apply when `selectorsInherited: true` is set or when no CLI selectors are provided. Use `--debug` to see log messages about skipped subhelmfiles.
* When a subhelmfile has explicit `selectors`, those selectors determine which releases from that subhelmfile are considered; parent and CLI selectors are not combined with them for release filtering.
* When CLI selectors are provided (e.g. `helmfile -l name=b sync`) and a subhelmfile has explicit selectors that are provably incompatible with them (same key, different value), that subhelmfile may still be **skipped entirely** without loading or rendering it. For example, with `-l name=b`, a subhelmfile with `selectors: [name=a]` will be skipped since no release could match both. This optimization does not apply when `selectorsInherited: true` is set or when no CLI selectors are provided. Use `--debug` to see log messages about skipped subhelmfiles.

Copilot uses AI. Check for mistakes.

@yxxhero yxxhero left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 6 Copilot review comments have been addressed:

  1. CLI selectors only (app.go:1048): Fixed in 1f79a18 — uses a.Selectors instead of opts.Selectors.
  2. Malformed selector test (release_filters_test.go:158): Fixed in 1f79a18 — added test case for conservative true + error.
  3. Doc comment update (release_filters.go:68): Fixed in 1f79a18 — comment now reflects (true, err) contract.
  4. User docs (app.go:1012): Fixed in dda80926 — updated docs/index.md to document the optimization.
  5. Map allocation (release_filters.go:104): Fixed in d7594464 — replaced with direct slice comparison.
  6. Docs contradiction (index.md:1526): Fixed in dda80926 — reworded to clarify filtering vs skipping.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Comment thread pkg/app/app.go Outdated
Comment on lines +1051 to +1052
compatible, _ := state.SelectorsAreCompatible(cliSelectors, m.Selectors)
return !compatible

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subhelmfileSelectorsConflict ignores the error returned by state.SelectorsAreCompatible. While the compatibility result is conservative on parse errors, dropping the error makes it hard to debug unexpected selector formats and conflicts with the function’s contract comment (which says callers can log/handle parse failures). Consider capturing the error and logging it at debug/warn (while still treating it as compatible to avoid skipping).

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +91
filtersA, err := parseLabelFilters(selectorsA)
if err != nil {
return true, err
}

filtersB, err := parseLabelFilters(selectorsB)
if err != nil {
return true, err
}

for _, a := range filtersA {
for _, b := range filtersB {
if a.positiveLabelsCompatibleWith(b) {
return true, nil
}
}
}

return false, nil
}

func parseLabelFilters(selectors []string) ([]LabelFilter, error) {
filters := make([]LabelFilter, 0, len(selectors))
for _, s := range selectors {
f, err := ParseLabels(s)
if err != nil {
return nil, err
}
filters = append(filters, f)
}

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SelectorsAreCompatible calls ParseLabels for each selector, and ParseLabels recompiles its regexes on every invocation. With this new pre-filter running for each nested helmfile, that repeated regex compilation becomes more expensive than necessary. Consider hoisting the regexes to package-level vars (compiled once) or implementing a lightweight parse for positive labels for this fast-path check.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +13
cleanup_issue_2544() {
rm -rf "${issue_2544_tmp_dir}"
}
trap cleanup_issue_2544 EXIT

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script is sourced by test/integration/run.sh, so setting an EXIT trap here overrides the runner’s global cleanup trap. If the test fails (calls fail/exit), the runner cleanup won’t run. Also, the trap - EXIT at the end clears the runner’s trap permanently for subsequent test cases. Prefer a RETURN trap (runs when the sourced file finishes) or save/restore the previous EXIT trap, and ensure cleanup runs deterministically.

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +77
trap - EXIT
test_pass "issue-2544: subhelmfiles skipped when selectors conflict with CLI"

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trap - EXIT in a sourced test-case script clears the parent runner’s EXIT trap (set in test/integration/run.sh), so the integration harness cleanup may never run after this test case. Use a RETURN trap for per-test cleanup, or restore the previous EXIT trap after the test completes.

Copilot uses AI. Check for mistakes.

@yxxhero yxxhero left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 4 new review comments addressed in commit 8f904e5:

  1. Log parse errors (app.go:1052): Now captures and logs the error from SelectorsAreCompatible at debug level while still treating it as compatible.

  2. Hoist regex compilation (release_filters.go:91): Moved reLabelMismatch and reLabelMatch to package-level var blocks so they compile once.

  3. EXIT trap override (issue-2544.sh:13): Replaced trap ... EXIT with explicit cleanup_issue_2544 calls before each fail and at test end.

  4. trap - EXIT clears parent trap (issue-2544.sh:77): Removed entirely — cleanup is now explicit, no trap manipulation.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

@yxxhero

yxxhero commented Apr 21, 2026

Copy link
Copy Markdown
Member Author

@copilot deeply review to check if this PR solve that issue.

@yxxhero

yxxhero commented Apr 21, 2026

Copy link
Copy Markdown
Member Author

@copilot deeply review to check if this PR solve that issue.

Copilot AI commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

@copilot deeply review to check if this PR solve that issue.

Deep review complete. The PR correctly solves issue #2544. Here's my verification:

Core mechanism: subhelmfileSelectorsConflict is called before visitStatesWithContext in processNestedHelmfiles, so the subhelmfile is skipped entirely (no file I/O, no YAML loading, no template rendering) when CLI selectors provably conflict with all subhelmfile selectors. ✅

Selector source is correct: Uses a.Selectors (set from conf.Selectors() at app initialization, never mutated) — not opts.Selectors which can be overridden by intermediate explicit subhelmfile selectors during nesting. ✅

Conservative logic:

  • Skips only when ALL (CLI selector, subhelmfile selector) pairs conflict
  • Does NOT skip when either set is empty (len(cliSelectors) == 0 || len(m.Selectors) == 0)
  • Does NOT skip when selectorsInherited: true
  • Does NOT skip on parse errors (returns compatible=true + logs at debug level)
  • Negative labels (!=) are treated as compatible (no false skips) ✅

Behavior change is intentional: Previously, subhelmfile explicit selectors completely overrode CLI selectors for release filtering (e.g., -l name=doesnotexists would still return releases from a subhelmfile with selectors: [name=prometheus, name=zipkin]). The new behavior skips such subhelmfiles entirely. This is the intended optimization and is correctly documented in docs/index.md. ✅

Test accuracy: The updated test expectations in TestVisitDesiredStatesWithReleasesFiltered_EmbeddedSelectors correctly reflect the new behavior — releases from a*.yaml (with selectors: [name=prometheus, name=zipkin]) are now omitted when the CLI selector conflicts with all those subhelmfile selectors. (Note: GetReleasesWithLabels implicitly adds name=<release-name> as a label, which is why name=grafana matches the release named "grafana" in b.yaml.) ✅

CI status: Go workflow passes on commits d7594464, dda80926, and 8f904e53. ✅

All 10 review items addressed: CLI selectors, malformed selector test, doc comment, user docs, map allocation, docs contradiction, error logging, regex hoisting, EXIT trap, trap - EXIT removal. ✅

The PR is correct and ready to merge.

@max06

max06 commented Apr 22, 2026

Copy link
Copy Markdown

Ouh, lovely! Thanks a lot!

Just in case: I'm not sure if this causes conflicts with nested dependencies, like if I define a chart in one subhelmfile with a dependency on a chart in another subhelmfile. If that was ever fixed, or supposed to work. Last time I tested that (it's been a while) the DAG-related stuff was more or less non-functional.

Anyways: Appreciate your effort!

Edit: Yes, I'll definitely test this, probably later today.

@max06

max06 commented Apr 23, 2026

Copy link
Copy Markdown

I've tested your change, it works.

I noticed the selector field is a string-array, taking entries like label=value. The related fields commonLabels and releases[].labels take maps instead. Wouldn't it be easier to treat this selector the same way, since it basically does the same thing?

I also took some time measurements. My test-repo contains currently 9 subhelmfiles.

Scenario Command Without this PR With this PR
No filter (full render) build 8.3 s 8.3 s
list 7.7 s 7.7 s
--selector build 8.3 s 1.6 s
list 7.7 s 1.6 s
template 16 s ¹ 1.8 s

¹ My bad, tested without selectorsInherited unrelated to this PR. Probably faster.

I'd love to see this merged!

yxxhero and others added 6 commits April 24, 2026 14:09
)

When CLI selectors are provided (e.g. -l name=b), subhelmfiles whose
explicit selectors are provably incompatible are now skipped entirely,
avoiding unnecessary YAML loading and template rendering.

Two selector sets are incompatible when every pair has a positive label
conflict: same key with different values (e.g. name=b vs name=a).
Negative labels are not compared.

Fixes #2544

Signed-off-by: yxxhero <[email protected]>
…r subhelmfile selector skip

- Add debug log when skipping subhelmfile due to selector conflict
- Add TestSubhelmfileSelectorsConflict with 11 cases for direct unit coverage
- Document the selector-based subhelmfile skip optimization in docs/index.md
- Fix integration test: use 'app' label key instead of reserved 'name' key
  (GetReleasesWithLabels overwrites labels["name"] with the release name)

Signed-off-by: yxxhero <[email protected]>
Compare positive label slices directly instead of allocating a map per
comparison, as label counts are typically small (1-3 entries).

Addresses Copilot review comment on PR #2545.

Signed-off-by: yxxhero <[email protected]>
Reword the first two bullets to avoid the contradiction between
'CLI selectors are ignored' and the new skip optimization.

Signed-off-by: yxxhero <[email protected]>
- Log parse errors from SelectorsAreCompatible at debug level instead of
  silently discarding them
- Hoist regex compilation to package-level vars in ParseLabels to avoid
  repeated compilation per selector
- Replace EXIT traps with explicit cleanup calls in integration test to
  avoid interfering with the parent runner's trap

Signed-off-by: yxxhero <[email protected]>
@yxxhero yxxhero force-pushed the fix-2544-subhelmfile-selector-skip branch from 8f904e5 to b38df7c Compare April 24, 2026 06:09
@yxxhero yxxhero merged commit 5368ab8 into main Apr 25, 2026
16 checks passed
@yxxhero yxxhero deleted the fix-2544-subhelmfile-selector-skip branch April 25, 2026 09:18
max06 added a commit to max06/atlas that referenced this pull request May 1, 2026
v0.3.0 (commit fad007e on add-selectors-to-subhelmfiles, merged via #16)
added a hardcoded `selectors:` field to every emitted sub-helmfile entry
on the assumption that helmfile/helmfile#2545 was available. In stock
helmfile (1.4.4 and earlier — including the pre-1.0 versions consumers
still run), child `selectors:` REPLACE the inherited CLI selector when
filtering releases inside that sub-helmfile, so `--selector` became a
no-op and every render produced manifests for every deployment in the
repo. Verified empirically: 1.4.4 + --selector emitted 40 release dirs
instead of the targeted 1.

The same change deleted ATLAS's stage-1 atlas.filter.* env path on the
same wrong assumption, leaving consumers (especially the ArgoCD
ApplicationSet pattern) with no efficient way to render a single
deployment.

Restore both:
* Drop the hardcoded sub-helmfile `selectors:` block — CLI --selector
now reaches release-level commonLabels (cluster, clusterName,
clusterGroup, deploymentName, template, instance, variant) again.
* Re-add stage-1 atlas.filter.{cluster,deploymentName} short-circuit in
helmfile.all.yaml.gotmpl — non-matching clusters skip FS scan,
deployment collection, and sub-helmfile emission entirely. Wired from
env (ATLAS_FILTER_CLUSTER / ATLAS_FILTER_DEPLOYMENT_NAME) at the
top-level helmfile.yaml.gotmpl so consumers can pass it via `helmfile -e
...` or directly from an ArgoCD Application's container env.
* `selectorsInherited: true` removed — pointless without child selectors
to inherit through.

Tests: tests/bats/filtering.bats covers both contracts (CLI --selector
+ env-var stage-1 filter) with positive and negative assertions, so the
v0.3.0 regression cannot recur silently. helmfile_list helper drops
stderr because bats's `run` captures both streams and helmfile's "err:
no releases found..." would otherwise corrupt the JSON-parse assertion
for empty-result selectors.

Full suite: 290/290.



snapshot-review: leverage stage-1 atlas.filter.* env vars in fast path

The reusable PR-review workflow's fast path renders one (cluster,
deploymentName) pair at a time with --selector. Pre-fix, every
invocation parsed every sub-helmfile and decrypted SOPS for unrelated
deployments — the --selector only filtered AFTER the work was done.

With the stage-1 atlas.filter.* env path restored, ATLAS_FILTER_CLUSTER
and ATLAS_FILTER_DEPLOYMENT_NAME short-circuit in helmfile.all so
non-matching sub-helmfiles never get emitted in the first place.
helmfile.single only sees one entry, SOPS only loads for that one
deployment.

Both env vars AND --selector are passed:
- Current ATLAS: env vars short-circuit at stage-1, --selector is a
redundant safety net.
- Older ATLAS pinned by consumer (no stage-1 filter): env vars are
no-op, --selector handles release-level filtering.

Probe + bulk fallback path is untouched — still detects pinned ATLAS
refs that carry the broken hardcoded sub-helmfile selectors and falls
back to a single bulk render. Probe diagnostic and warning text updated
to reflect that the broken behavior is now historical, not the helmfile
binary's fault.

Two near-identical edits: the merge-result render (PR side) and the
baseline render (target branch HEAD). Both fast paths now export the env
vars per pair.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
max06 added a commit to max06/atlas that referenced this pull request May 1, 2026
v0.3.0 (commit fad007e on add-selectors-to-subhelmfiles, merged via #16)
added a hardcoded `selectors:` field to every emitted sub-helmfile entry
on the assumption that helmfile/helmfile#2545 was available. In stock
helmfile (1.4.4 and earlier — including the pre-1.0 versions consumers
still run), child `selectors:` REPLACE the inherited CLI selector when
filtering releases inside that sub-helmfile, so `--selector` became a
no-op and every render produced manifests for every deployment in the
repo. Verified empirically: 1.4.4 + --selector emitted 40 release dirs
instead of the targeted 1.

The same change deleted ATLAS's stage-1 atlas.filter.* env path on the
same wrong assumption, leaving consumers (especially the ArgoCD
ApplicationSet pattern) with no efficient way to render a single
deployment.

Restore both:
* Drop the hardcoded sub-helmfile `selectors:` block — CLI --selector
now reaches release-level commonLabels (cluster, clusterName,
clusterGroup, deploymentName, template, instance, variant) again.
* Re-add stage-1 atlas.filter.{cluster,deploymentName} short-circuit in
helmfile.all.yaml.gotmpl — non-matching clusters skip FS scan,
deployment collection, and sub-helmfile emission entirely. Wired from
env (ATLAS_FILTER_CLUSTER / ATLAS_FILTER_DEPLOYMENT_NAME) at the
top-level helmfile.yaml.gotmpl so consumers can pass it via `helmfile -e
...` or directly from an ArgoCD Application's container env.
* `selectorsInherited: true` removed — pointless without child selectors
to inherit through.

Tests: tests/bats/filtering.bats covers both contracts (CLI --selector
+ env-var stage-1 filter) with positive and negative assertions, so the
v0.3.0 regression cannot recur silently. helmfile_list helper drops
stderr because bats's `run` captures both streams and helmfile's "err:
no releases found..." would otherwise corrupt the JSON-parse assertion
for empty-result selectors.

Full suite: 290/290.

snapshot-review: leverage stage-1 atlas.filter.* env vars in fast path

The reusable PR-review workflow's fast path renders one (cluster,
deploymentName) pair at a time with --selector. Pre-fix, every
invocation parsed every sub-helmfile and decrypted SOPS for unrelated
deployments — the --selector only filtered AFTER the work was done.

With the stage-1 atlas.filter.* env path restored, ATLAS_FILTER_CLUSTER
and ATLAS_FILTER_DEPLOYMENT_NAME short-circuit in helmfile.all so
non-matching sub-helmfiles never get emitted in the first place.
helmfile.single only sees one entry, SOPS only loads for that one
deployment.

Both env vars AND --selector are passed:
- Current ATLAS: env vars short-circuit at stage-1, --selector is a
redundant safety net.
- Older ATLAS pinned by consumer (no stage-1 filter): env vars are
no-op, --selector handles release-level filtering.

Probe + bulk fallback path is untouched — still detects pinned ATLAS
refs that carry the broken hardcoded sub-helmfile selectors and falls
back to a single bulk render. Probe diagnostic and warning text updated
to reflect that the broken behavior is now historical, not the helmfile
binary's fault.

Two near-identical edits: the merge-result render (PR side) and the
baseline render (target branch HEAD). Both fast paths now export the env
vars per pair.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>

Add stage-3 release-aware values pipeline (prototype)

Introduces a third pipeline stage where release values are resolved at
helmfile's release-evaluation time rather than at stage-2. The new path
makes .Release.Name / .Release.Namespace available inside values gotmpls,
which the existing stage-2 progressive-merge cannot offer.

Pipeline shape:
  stage 1 (helmfile.all)      -> per-deployment sub-helmfiles
  stage 2 (helmfile.single)   -> per-instance sub-helmfiles (when engine: stage3)
  stage 3 (helmfile.instance) -> emits releases with values: [<loader>]
  stage 3' (values-loader)    -> resolves values per-release with .Release.* in scope

Each app instance must live in its own helmfile file because multiple
environments: blocks in one file collapse to a single merged env-values
(verified by spike). Splitting by instance keeps atlas.instance unambiguous
and removes the need for a release-name lookup table.

Deployments opt in via top-level `engine: stage3` in deployment.yaml.
Existing deployments without the flag run through the unchanged stage-2
path; all 284 prior assertions stay green.

Prototype scope (deferred to follow-ups):
  - SOPS decryption in the loader
  - Redaction twin-load + post-renderer wiring
  - applyListOverride for patches/transformers in stage-3
  - .yaml.gotmpl + group-level hierarchy variants in the loader
  - Progressive accumulator across the values: list

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3 loader: complete hierarchy walk (.sops.yaml + .yaml + .yaml.gotmpl)

Extends the values loader's hierarchy walk to mirror stage-2's full source
order at every level (global, group, cluster, deployment). Each level now
loads .sops.yaml -> .yaml -> .yaml.gotmpl with later sources overriding
earlier ones. .yaml.gotmpl files are tpl-rendered with a fresh ctx that
exposes the running merge so a .gotmpl at one level can reference any key
defined earlier — including SOPS-decrypted values.

SOPS at the hierarchy level was bundled in (rather than deferred to the
template/instance SOPS milestone) because every existing hierarchy
.yaml.gotmpl fixture references SOPS keys; .yaml.gotmpl is unusable
without it. Template- and instance-level SOPS files remain in the next
milestone.

deployment31 (group1/cluster2 + engine: stage3) covers:
  - plain .yaml at all four hierarchy levels
  - .yaml.gotmpl variants at every level
  - .yaml.gotmpl chaining through earlier .yaml and earlier .gotmpl
  - override precedence (group beats global, cluster beats group)
  - nested deep merge across the four levels

309/309 bats green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3 loader: progressive accumulator across the values: list

Reorders the loader so hierarchy is computed first as a baseline ctx for
the template + instance walks, then overlaid at the end so it still wins
precedence (chart < template < instance < hierarchy is preserved). Each
.yaml.gotmpl entry in a release's values: list is now tpl-rendered with a
fresh ctx that exposes:

  - .Values (atlas baseline)
  - the hierarchy as already loaded
  - the running merge of all earlier entries in the same values: list
  - .Release.* (Name, Namespace, Chart, Labels)
  - .Values.foo self-ref so wrapped form ({{ .Values.foo }}) keeps working

A later entry can therefore reference any key defined by an earlier inline
map, plain .yaml, or .yaml.gotmpl — same semantics stage-2 already
provides via its progressive twin-load merge, only this version is
release-aware.

deployment32 (cluster1 + engine: stage3) covers:
  - inline + plain .yaml + .yaml.gotmpl in one values: list
  - .gotmpl reading earlier inline keys
  - .gotmpl reading earlier plain .yaml keys
  - .gotmpl mixing hierarchy + earlier-list keys
  - .gotmpl using .Release.Name alongside earlier keys
  - .gotmpl using .Values.foo (wrapped) form

317/317 bats green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3 named instances: cover via test, no loader change needed

deployment33 instantiates the same app template twice in one deployment
via apps[].name (primary, secondary). Stage-2's per-instance fan-out
already gives each instance its own helmfile state file with a fully
populated atlas.instance.name in env-values; the loader's existing
instance-matcher already keys on (template, instance-name). The whole
flow worked on the first run with no loader edit — adding the fixture
locks in the behaviour as a regression guard.

deployment33 covers:
  - both releases render (primary, secondary)
  - each release sees its own atlas.instance.name in the values loader
  - release name correctly embeds the instance name
  - shared template name surfaces identically on both

325/325 bats green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3 loader: SOPS in template + instance values: lists

Adds a .sops.yaml branch to the template-level and instance-level walks
in the values loader. fetchSecretValue with the ref+sops:// protocol
performs whole-document decryption — same primitive used at the hierarchy
level (commit 38500a4) and by stage-2. List-order precedence is preserved:
a .sops.yaml listed after an inline map overrides the inline keys.

deployment34 reuses the existing app-tplsops template (values.yaml.gotmpl
+ inline map + values.sops.yaml) through engine: stage3. Asserts:
  - plain template-include value reaches output
  - template-inline default reaches output
  - SOPS scalar decrypts
  - SOPS nested map decrypts (preserves nested structure)
  - SOPS value listed last overrides earlier inline of the same key

The instance-level SOPS branch is added symmetrically to the loader but
no existing fixture exercises it; covered when a test surfaces.

Redaction of these template-level SOPS values is deferred to feature #6
(twin-load + post-renderer wiring).

330/330 bats green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3 redaction: shared atlas.values.merged + twin-load in stage-3

Extracts the loader's merge logic into a single named template
(atlas.values.merged in templates/_values_loader.tpl) parameterised by
.redact. Two callers share the same body:

  helmfile.values-loader.yaml.gotmpl
    Helmfile evaluates this once per release at release-time. Calls
    atlas.values.merged with redact=false and emits the result as the
    real values blob for helm.

  helmfile.instance.yaml.gotmpl  (per release, when redactSecrets is on)
    Calls atlas.values.merged twice — once with redact=false (real),
    once with redact=true (SOPS values rewritten through atlas.redact.value
    before merge, transitive taint via twin-load). Diffs the two trees via
    atlas.diff.values to produce a replacement map, b64-encodes it into
    postRendererArgs, and wires the atlas-redact post-renderer.

Redaction-mode runs walk twice per release (real + redacted) plus once at
release-time (loader). Regular ArgoCD renders skip the entire stage-3
twin-load block — only the loader runs. So the redaction overhead is paid
only by the CI/redacted-render workflow, never by ArgoCD.

deployment34 now also asserts:
  - template SOPS scalar redacted
  - template SOPS override redacted (list-order precedence preserved)
  - template SOPS nested map redacted at every leaf
  - plain (non-SOPS) values pass through untouched

1MB ARG_MAX safety check ported from stage-2. Sidedump path (used by the
snapshot-review workflow) deferred — not needed for prototype validation.

335/335 bats green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: strip release.condition and release.installed

In ATLAS, deploy intent is signalled by the presence of a deployment.yaml
entry. Letting helmfile-level condition/installed through would silently
produce empty deployments — indistinguishable from a misconfiguration —
when ArgoCD discovers the directory and finds no manifests.

Drop both fields in the per-instance release rewrite step so app templates
remain vanilla-helmfile-readable but render uniformly under ATLAS.

Fixture: deployment35 / app-stage3-condinst sets condition: never.enabled
(would be a parse error in vanilla helmfile) and installed: false (would
be silently skipped). With the strip in place both render normally.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: add template + instance commonLabels

Auto-inject two labels on every stage-3 release so consumers can filter
by app-template family and named instance:
  - template: the app-template directory name
  - instance: the apps[].name value (or template name when name unset)

Useful for ArgoCD ApplicationSet selectors and `helmfile list -l ...`.
The remaining commonLabels (cluster, clusterName, deploymentName, variant,
clusterGroup) are unchanged.

Test covers both the default-name path (deployment30 — instance defaults
to template) and the explicit-name path (deployment33 — primary and
secondary instances of the same template).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: auto-munge release names with instance.name

When apps[].name is set and differs from the template name, ATLAS rewrites
release names so each instance produces a distinct release without forcing
the template author to embed `{{ .Values.atlas.instance.name }}`. Default
style is `<instance>-<release>` (prefix); apps[].nameStyle: suffix flips
to `<release>-<instance>` for naming conventions like `vm-cust-abc`.

Both helmfile.instance and the values-loader apply the same munge logic:
helmfile.instance rewrites the emitted release.name; the loader applies
the same transform when matching the template's author-intended name
against helmfile's .Release.Name (now the munged form).

Existing fixture deployment33/app-stage3-named drops its manual
`{{ .Values.atlas.instance.name }}` munge — the template author intent
is the bare release name "stage3-named" and ATLAS produces
"primary-stage3-named" / "secondary-stage3-named" automatically.

New fixture deployment36/app-stage3-namestyle covers all three flavours
in one deployment: default-prefix, explicit-prefix, suffix.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: fail render when template sets release.postRenderer

ATLAS owns the helm post-renderer slot for its redaction pipeline. Helm 4
does not support post-renderer chaining, and silently overwriting a user-
set postRenderer would mask intent and produce confusing diffs. Reject
the field with a clear pointer to open an issue so the use case can be
discussed before any workaround.

Negative-test fixture lives under tests/fixtures-negative/ so the
standard bulk render in render.bash is unaffected. The bats test invokes
helmfile directly with its own --state-values-set roots.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: default skipSchemaValidation to true (preserve user value)

helm runs schema validation only on install/upgrade/lint, not the
template/build paths ATLAS exercises during ArgoCD render. The flag
primarily affects consumer-side bootstrap, where missing CRDs are a
common false-positive failure source. Default it on so that path stays
clean. User-set values (true or false) are preserved unchanged.

Fixture deployment37 / app-stage3-schema covers both branches: a release
without the field gets defaulted to true; a release that sets false
explicitly keeps false.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: re-accept release.secrets, merge last, path-resolve

Helmfile's env-level secrets contract: "non-HCL secrets are loaded first
but merged last". ATLAS replicates this at the release level — every
entry in release.secrets and apps[].secrets lands AFTER all values:
entries. Paths resolve template-relative for release.secrets and
deployment-relative for apps[].secrets. Decryption goes through the
existing SOPS fetchSecretValue path (same as .sops.yaml in values:),
so the helm-secrets plugin is not required.

helmfile.instance strips release.secrets from the emitted state once the
loader has handled them, so helmfile does not also try to decrypt via
helm-secrets (avoiding duplicate work and a hard plugin dependency).

Fixture deployment38 / app-stage3-secrets demonstrates all three layers:
template values, template secrets (overrides values), instance secrets
(overrides template secrets).

tests/.sops.yaml: relax creation-rule regex from `values\.sops\.yaml$`
to `\.sops\.yaml$` so secret fixtures named for their role
(template-secret.sops.yaml, instance-secret.sops.yaml) get the
testing-age recipient automatically.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: wire applyListOverride for patches/transformers

Route release.strategicMergePatches, release.jsonPatches, and
release.transformers through atlas.applyListOverride during the per-
instance rewrite, mirroring stage-2's behaviour. Template-relative paths
become absolute so helmfile can locate the patch files regardless of
consumer CWD; instance-level overrides from deployment.yaml apps[] are
appended.

release.values is intentionally excluded — stage-3's loader replaces it
wholesale and resolves entries itself via the values-loader pipeline.

Fixture deployment39 / app-stage3-patches uses a strategicMergePatch to
add an annotation onto chart1's ConfigMap, exercising both the path
resolution wiring and helmfile's actual patch application via kustomize.

Note: convertPaths joins targetPath with the original entry verbatim, so
"./foo" becomes "<dir>/./foo" rather than the cleaner "<dir>/foo".
kustomize accepts either; normalization is left for a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: path-resolve keyring + set[].file + setString[].file

Resolve template-relative paths for the remaining helm-CLI fields that
take a path:
  keyring          — single scalar (passed to helm as --keyring)
  set[].file       — nested key inside a list of maps (--set-file)
  setString[].file — same shape as set[]

Mirrors the chart: handling — only ./ and ../ prefixes are rewritten;
absolute paths and bare names pass through. Without resolution helm
would look in the consumer's CWD instead of the template dir.

Fixture deployment40 / app-stage3-paths exercises all three; the bats
test caches the build-state YAML once via setup_file (helmfile build is
expensive) and asserts each path is absolute and points inside the
template dir.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: flip default engine, port missing wiring, migrate d9 + d11

helmfile.single now defaults to engine: stage3 when deployment.yaml omits
the field. All 1-29 fixtures (and any consumer that doesn't opt in
explicitly) flow through the per-instance pipeline.

Three pieces of stage-2 functionality the previous parallel-fixture work
hadn't touched moved over so the existing suite stays green:

  * _values_loader: seed $merged with .Values.atlas so the chart sees
    .Values.atlas.* (deployment context, instance fields, redactSecrets)
    just as stage-2 did. Mirrors the dict "atlas" .Values.atlas baseline
    in helmfile.single.

  * helmfile.instance: merge apps[].labels into release.labels with
    instance taking priority. atlas-template's bootstrap selector
    (bootstrap: true) depends on this.

  * helmfile.instance: optional ATLAS_SIDEDUMP_MAP_DIR side-dump second
    argv element on the atlas-redact post-renderer. snapshot-review's
    scrub-baseline replays each map against an older-baseline render.

Fixture migration:
  * tests/templates/app-named: drop the manual
    `{{ .Values.atlas.instance.name }}` munge from release.name; ATLAS
    auto-munges now (slice 3). Release becomes "primary-app-named" /
    "secondary-app-named" via prefix.
  * cluster1-deployment9-{primary,secondary}.bats: update INSTANCE to
    the munged form.

Full suite: 369/369 with stage-3 as default.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Retire stage-2 path; thin helmfile.single to fan-out shim

helmfile.single now does only what the stage-3 default needs:
  1. Validate deployment.yaml exists.
  2. Render deployment.yaml as a Go template with the atlas object as
     context, parse the resulting apps[] list.
  3. Emit one helmfiles[] entry per app instance, each pointing at
     helmfile.instance.yaml.gotmpl with the atlas object augmented by
     instance.{template, name}.

Removed (no longer reachable now that engine: stage2 is gone):
  - hierarchy twin-load ($realValues / $redactedValues)
  - hierarchy-level $replMap construction
  - environments.default.values emission
  - per-app-template release rewriting (chart paths, secrets rejection,
    progressive twin-load merge, applyListOverride wiring, redaction
    post-renderer wiring with side-dump)
  - the engine if/else branch and the get "engine" "stage2" default

The deleted block weighed ~400 lines; helmfile.single is 75 lines now.
All of that logic lives in either helmfile.instance (per-release
rewrites, redaction post-renderer, side-dump) or _values_loader (twin-
load, hierarchy walk, progressive merge).

Cleanup:
  - Drop explicit `engine: stage3` from fixtures 30-40 + postrenderer-fail.
  - Update doc-comments that referenced stage-2 (helmfile.instance,
    _values_loader, _functions, helmfile.all, app-stage3-probe).

Full suite: 369/369.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

docs: align README and CLAUDE.md with stage-3-only architecture

README.md
  * "Using the same template multiple times": drop the manual
    `{{ .Values.atlas.instance.name }}` instruction. Document auto-munge
    with apps[].nameStyle: prefix|suffix (prefix default).
  * "Labels": expand the commonLabels table to cover clusterName,
    clusterGroup, template, instance, variant — all auto-injected now.
  * "App Templates": add a "How ATLAS treats helmfile release options"
    table covering the per-instance rewrites (name, chart, values,
    secrets, condition/installed, skipSchemaValidation, postRenderer,
    keyring + set[].file + setString[].file, patches/transformers,
    labels). Lead-in clarifies the "stay close to vanilla helmfile"
    goal.
  * "Value Inheritance Logic": insert template-secrets and instance-
    secrets layers between values and hierarchy. Document the
    "secrets merged last among per-release lists" semantics.
  * "Full Load Order": renumber to include the new secrets layers
    (5-6); standalone-cluster skip range shifts to 10-12.
  * "How It Works": replace the two-step (load + render) description
    with the four-stage pipeline (discover, fan-out, per-instance
    rewrite, release-time loader) that matches the actual code now.

CLAUDE.md
  * Common Commands: switch to the subcommand-first invocation form
    (`helmfile template -f X` / `helmfile build -f X`) so the commands
    match the prefix-based allowlist patterns.
  * Core Template Pipeline: describe all four stages explicitly
    (helmfile.all → helmfile.single → helmfile.instance → loader)
    instead of conflating values resolution into helmfile.single.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Add .claude/utils/run-bats.sh + allowlist

Wraps the test ritual (rm stale tmp dirs, run bats, count results,
list failures) into one allowlistable invocation. Modes:

  .claude/utils/run-bats.sh                       full suite, summary
  .claude/utils/run-bats.sh tests/bats/foo.bats   single file, full output
  .claude/utils/run-bats.sh -v                    full suite, full output

Exit code reflects test result (0 pass, 1 fail), so CI/agents can use
status without parsing output.

Allowlist additions: Bash(.claude/utils/run-bats.sh) and the *-form for
arguments. The script's internal rm of /tmp/atlas-bats-* runs without
prompting because the script itself is the allowlisted unit.

Also rolls in the broader allowlist set added on main (bats *, sops
--decrypt *, helm template *, helmfile template *, helmfile build *,
etc.) so the worktree shares the same prompt-free surface as main.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
max06 added a commit to max06/atlas that referenced this pull request May 1, 2026
v0.3.0 (commit fad007e on add-selectors-to-subhelmfiles, merged via #16)
added a hardcoded `selectors:` field to every emitted sub-helmfile entry
on the assumption that helmfile/helmfile#2545 was available. In stock
helmfile (1.4.4 and earlier — including the pre-1.0 versions consumers
still run), child `selectors:` REPLACE the inherited CLI selector when
filtering releases inside that sub-helmfile, so `--selector` became a
no-op and every render produced manifests for every deployment in the
repo. Verified empirically: 1.4.4 + --selector emitted 40 release dirs
instead of the targeted 1.

The same change deleted ATLAS's stage-1 atlas.filter.* env path on the
same wrong assumption, leaving consumers (especially the ArgoCD
ApplicationSet pattern) with no efficient way to render a single
deployment.

Restore both:
* Drop the hardcoded sub-helmfile `selectors:` block — CLI --selector
now reaches release-level commonLabels (cluster, clusterName,
clusterGroup, deploymentName, template, instance, variant) again.
* Re-add stage-1 atlas.filter.{cluster,deploymentName} short-circuit in
helmfile.all.yaml.gotmpl — non-matching clusters skip FS scan,
deployment collection, and sub-helmfile emission entirely. Wired from
env (ATLAS_FILTER_CLUSTER / ATLAS_FILTER_DEPLOYMENT_NAME) at the
top-level helmfile.yaml.gotmpl so consumers can pass it via `helmfile -e
...` or directly from an ArgoCD Application's container env.
* `selectorsInherited: true` removed — pointless without child selectors
to inherit through.

Tests: tests/bats/filtering.bats covers both contracts (CLI --selector
+ env-var stage-1 filter) with positive and negative assertions, so the
v0.3.0 regression cannot recur silently. helmfile_list helper drops
stderr because bats's `run` captures both streams and helmfile's "err:
no releases found..." would otherwise corrupt the JSON-parse assertion
for empty-result selectors.

Full suite: 290/290.

snapshot-review: leverage stage-1 atlas.filter.* env vars in fast path

The reusable PR-review workflow's fast path renders one (cluster,
deploymentName) pair at a time with --selector. Pre-fix, every
invocation parsed every sub-helmfile and decrypted SOPS for unrelated
deployments — the --selector only filtered AFTER the work was done.

With the stage-1 atlas.filter.* env path restored, ATLAS_FILTER_CLUSTER
and ATLAS_FILTER_DEPLOYMENT_NAME short-circuit in helmfile.all so
non-matching sub-helmfiles never get emitted in the first place.
helmfile.single only sees one entry, SOPS only loads for that one
deployment.

Both env vars AND --selector are passed:
- Current ATLAS: env vars short-circuit at stage-1, --selector is a
redundant safety net.
- Older ATLAS pinned by consumer (no stage-1 filter): env vars are
no-op, --selector handles release-level filtering.

Probe + bulk fallback path is untouched — still detects pinned ATLAS
refs that carry the broken hardcoded sub-helmfile selectors and falls
back to a single bulk render. Probe diagnostic and warning text updated
to reflect that the broken behavior is now historical, not the helmfile
binary's fault.

Two near-identical edits: the merge-result render (PR side) and the
baseline render (target branch HEAD). Both fast paths now export the env
vars per pair.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>

Add stage-3 release-aware values pipeline (prototype)

Introduces a third pipeline stage where release values are resolved at
helmfile's release-evaluation time rather than at stage-2. The new path
makes .Release.Name / .Release.Namespace available inside values gotmpls,
which the existing stage-2 progressive-merge cannot offer.

Pipeline shape:
  stage 1 (helmfile.all)      -> per-deployment sub-helmfiles
  stage 2 (helmfile.single)   -> per-instance sub-helmfiles (when engine: stage3)
  stage 3 (helmfile.instance) -> emits releases with values: [<loader>]
  stage 3' (values-loader)    -> resolves values per-release with .Release.* in scope

Each app instance must live in its own helmfile file because multiple
environments: blocks in one file collapse to a single merged env-values
(verified by spike). Splitting by instance keeps atlas.instance unambiguous
and removes the need for a release-name lookup table.

Deployments opt in via top-level `engine: stage3` in deployment.yaml.
Existing deployments without the flag run through the unchanged stage-2
path; all 284 prior assertions stay green.

Prototype scope (deferred to follow-ups):
  - SOPS decryption in the loader
  - Redaction twin-load + post-renderer wiring
  - applyListOverride for patches/transformers in stage-3
  - .yaml.gotmpl + group-level hierarchy variants in the loader
  - Progressive accumulator across the values: list

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3 loader: complete hierarchy walk (.sops.yaml + .yaml + .yaml.gotmpl)

Extends the values loader's hierarchy walk to mirror stage-2's full source
order at every level (global, group, cluster, deployment). Each level now
loads .sops.yaml -> .yaml -> .yaml.gotmpl with later sources overriding
earlier ones. .yaml.gotmpl files are tpl-rendered with a fresh ctx that
exposes the running merge so a .gotmpl at one level can reference any key
defined earlier — including SOPS-decrypted values.

SOPS at the hierarchy level was bundled in (rather than deferred to the
template/instance SOPS milestone) because every existing hierarchy
.yaml.gotmpl fixture references SOPS keys; .yaml.gotmpl is unusable
without it. Template- and instance-level SOPS files remain in the next
milestone.

deployment31 (group1/cluster2 + engine: stage3) covers:
  - plain .yaml at all four hierarchy levels
  - .yaml.gotmpl variants at every level
  - .yaml.gotmpl chaining through earlier .yaml and earlier .gotmpl
  - override precedence (group beats global, cluster beats group)
  - nested deep merge across the four levels

309/309 bats green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3 loader: progressive accumulator across the values: list

Reorders the loader so hierarchy is computed first as a baseline ctx for
the template + instance walks, then overlaid at the end so it still wins
precedence (chart < template < instance < hierarchy is preserved). Each
.yaml.gotmpl entry in a release's values: list is now tpl-rendered with a
fresh ctx that exposes:

  - .Values (atlas baseline)
  - the hierarchy as already loaded
  - the running merge of all earlier entries in the same values: list
  - .Release.* (Name, Namespace, Chart, Labels)
  - .Values.foo self-ref so wrapped form ({{ .Values.foo }}) keeps working

A later entry can therefore reference any key defined by an earlier inline
map, plain .yaml, or .yaml.gotmpl — same semantics stage-2 already
provides via its progressive twin-load merge, only this version is
release-aware.

deployment32 (cluster1 + engine: stage3) covers:
  - inline + plain .yaml + .yaml.gotmpl in one values: list
  - .gotmpl reading earlier inline keys
  - .gotmpl reading earlier plain .yaml keys
  - .gotmpl mixing hierarchy + earlier-list keys
  - .gotmpl using .Release.Name alongside earlier keys
  - .gotmpl using .Values.foo (wrapped) form

317/317 bats green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3 named instances: cover via test, no loader change needed

deployment33 instantiates the same app template twice in one deployment
via apps[].name (primary, secondary). Stage-2's per-instance fan-out
already gives each instance its own helmfile state file with a fully
populated atlas.instance.name in env-values; the loader's existing
instance-matcher already keys on (template, instance-name). The whole
flow worked on the first run with no loader edit — adding the fixture
locks in the behaviour as a regression guard.

deployment33 covers:
  - both releases render (primary, secondary)
  - each release sees its own atlas.instance.name in the values loader
  - release name correctly embeds the instance name
  - shared template name surfaces identically on both

325/325 bats green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3 loader: SOPS in template + instance values: lists

Adds a .sops.yaml branch to the template-level and instance-level walks
in the values loader. fetchSecretValue with the ref+sops:// protocol
performs whole-document decryption — same primitive used at the hierarchy
level (commit 38500a4) and by stage-2. List-order precedence is preserved:
a .sops.yaml listed after an inline map overrides the inline keys.

deployment34 reuses the existing app-tplsops template (values.yaml.gotmpl
+ inline map + values.sops.yaml) through engine: stage3. Asserts:
  - plain template-include value reaches output
  - template-inline default reaches output
  - SOPS scalar decrypts
  - SOPS nested map decrypts (preserves nested structure)
  - SOPS value listed last overrides earlier inline of the same key

The instance-level SOPS branch is added symmetrically to the loader but
no existing fixture exercises it; covered when a test surfaces.

Redaction of these template-level SOPS values is deferred to feature #6
(twin-load + post-renderer wiring).

330/330 bats green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3 redaction: shared atlas.values.merged + twin-load in stage-3

Extracts the loader's merge logic into a single named template
(atlas.values.merged in templates/_values_loader.tpl) parameterised by
.redact. Two callers share the same body:

  helmfile.values-loader.yaml.gotmpl
    Helmfile evaluates this once per release at release-time. Calls
    atlas.values.merged with redact=false and emits the result as the
    real values blob for helm.

  helmfile.instance.yaml.gotmpl  (per release, when redactSecrets is on)
    Calls atlas.values.merged twice — once with redact=false (real),
    once with redact=true (SOPS values rewritten through atlas.redact.value
    before merge, transitive taint via twin-load). Diffs the two trees via
    atlas.diff.values to produce a replacement map, b64-encodes it into
    postRendererArgs, and wires the atlas-redact post-renderer.

Redaction-mode runs walk twice per release (real + redacted) plus once at
release-time (loader). Regular ArgoCD renders skip the entire stage-3
twin-load block — only the loader runs. So the redaction overhead is paid
only by the CI/redacted-render workflow, never by ArgoCD.

deployment34 now also asserts:
  - template SOPS scalar redacted
  - template SOPS override redacted (list-order precedence preserved)
  - template SOPS nested map redacted at every leaf
  - plain (non-SOPS) values pass through untouched

1MB ARG_MAX safety check ported from stage-2. Sidedump path (used by the
snapshot-review workflow) deferred — not needed for prototype validation.

335/335 bats green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: strip release.condition and release.installed

In ATLAS, deploy intent is signalled by the presence of a deployment.yaml
entry. Letting helmfile-level condition/installed through would silently
produce empty deployments — indistinguishable from a misconfiguration —
when ArgoCD discovers the directory and finds no manifests.

Drop both fields in the per-instance release rewrite step so app templates
remain vanilla-helmfile-readable but render uniformly under ATLAS.

Fixture: deployment35 / app-stage3-condinst sets condition: never.enabled
(would be a parse error in vanilla helmfile) and installed: false (would
be silently skipped). With the strip in place both render normally.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: add template + instance commonLabels

Auto-inject two labels on every stage-3 release so consumers can filter
by app-template family and named instance:
  - template: the app-template directory name
  - instance: the apps[].name value (or template name when name unset)

Useful for ArgoCD ApplicationSet selectors and `helmfile list -l ...`.
The remaining commonLabels (cluster, clusterName, deploymentName, variant,
clusterGroup) are unchanged.

Test covers both the default-name path (deployment30 — instance defaults
to template) and the explicit-name path (deployment33 — primary and
secondary instances of the same template).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: auto-munge release names with instance.name

When apps[].name is set and differs from the template name, ATLAS rewrites
release names so each instance produces a distinct release without forcing
the template author to embed `{{ .Values.atlas.instance.name }}`. Default
style is `<instance>-<release>` (prefix); apps[].nameStyle: suffix flips
to `<release>-<instance>` for naming conventions like `vm-cust-abc`.

Both helmfile.instance and the values-loader apply the same munge logic:
helmfile.instance rewrites the emitted release.name; the loader applies
the same transform when matching the template's author-intended name
against helmfile's .Release.Name (now the munged form).

Existing fixture deployment33/app-stage3-named drops its manual
`{{ .Values.atlas.instance.name }}` munge — the template author intent
is the bare release name "stage3-named" and ATLAS produces
"primary-stage3-named" / "secondary-stage3-named" automatically.

New fixture deployment36/app-stage3-namestyle covers all three flavours
in one deployment: default-prefix, explicit-prefix, suffix.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: fail render when template sets release.postRenderer

ATLAS owns the helm post-renderer slot for its redaction pipeline. Helm 4
does not support post-renderer chaining, and silently overwriting a user-
set postRenderer would mask intent and produce confusing diffs. Reject
the field with a clear pointer to open an issue so the use case can be
discussed before any workaround.

Negative-test fixture lives under tests/fixtures-negative/ so the
standard bulk render in render.bash is unaffected. The bats test invokes
helmfile directly with its own --state-values-set roots.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: default skipSchemaValidation to true (preserve user value)

helm runs schema validation only on install/upgrade/lint, not the
template/build paths ATLAS exercises during ArgoCD render. The flag
primarily affects consumer-side bootstrap, where missing CRDs are a
common false-positive failure source. Default it on so that path stays
clean. User-set values (true or false) are preserved unchanged.

Fixture deployment37 / app-stage3-schema covers both branches: a release
without the field gets defaulted to true; a release that sets false
explicitly keeps false.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: re-accept release.secrets, merge last, path-resolve

Helmfile's env-level secrets contract: "non-HCL secrets are loaded first
but merged last". ATLAS replicates this at the release level — every
entry in release.secrets and apps[].secrets lands AFTER all values:
entries. Paths resolve template-relative for release.secrets and
deployment-relative for apps[].secrets. Decryption goes through the
existing SOPS fetchSecretValue path (same as .sops.yaml in values:),
so the helm-secrets plugin is not required.

helmfile.instance strips release.secrets from the emitted state once the
loader has handled them, so helmfile does not also try to decrypt via
helm-secrets (avoiding duplicate work and a hard plugin dependency).

Fixture deployment38 / app-stage3-secrets demonstrates all three layers:
template values, template secrets (overrides values), instance secrets
(overrides template secrets).

tests/.sops.yaml: relax creation-rule regex from `values\.sops\.yaml$`
to `\.sops\.yaml$` so secret fixtures named for their role
(template-secret.sops.yaml, instance-secret.sops.yaml) get the
testing-age recipient automatically.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: wire applyListOverride for patches/transformers

Route release.strategicMergePatches, release.jsonPatches, and
release.transformers through atlas.applyListOverride during the per-
instance rewrite, mirroring stage-2's behaviour. Template-relative paths
become absolute so helmfile can locate the patch files regardless of
consumer CWD; instance-level overrides from deployment.yaml apps[] are
appended.

release.values is intentionally excluded — stage-3's loader replaces it
wholesale and resolves entries itself via the values-loader pipeline.

Fixture deployment39 / app-stage3-patches uses a strategicMergePatch to
add an annotation onto chart1's ConfigMap, exercising both the path
resolution wiring and helmfile's actual patch application via kustomize.

Note: convertPaths joins targetPath with the original entry verbatim, so
"./foo" becomes "<dir>/./foo" rather than the cleaner "<dir>/foo".
kustomize accepts either; normalization is left for a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: path-resolve keyring + set[].file + setString[].file

Resolve template-relative paths for the remaining helm-CLI fields that
take a path:
  keyring          — single scalar (passed to helm as --keyring)
  set[].file       — nested key inside a list of maps (--set-file)
  setString[].file — same shape as set[]

Mirrors the chart: handling — only ./ and ../ prefixes are rewritten;
absolute paths and bare names pass through. Without resolution helm
would look in the consumer's CWD instead of the template dir.

Fixture deployment40 / app-stage3-paths exercises all three; the bats
test caches the build-state YAML once via setup_file (helmfile build is
expensive) and asserts each path is absolute and points inside the
template dir.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Stage-3: flip default engine, port missing wiring, migrate d9 + d11

helmfile.single now defaults to engine: stage3 when deployment.yaml omits
the field. All 1-29 fixtures (and any consumer that doesn't opt in
explicitly) flow through the per-instance pipeline.

Three pieces of stage-2 functionality the previous parallel-fixture work
hadn't touched moved over so the existing suite stays green:

  * _values_loader: seed $merged with .Values.atlas so the chart sees
    .Values.atlas.* (deployment context, instance fields, redactSecrets)
    just as stage-2 did. Mirrors the dict "atlas" .Values.atlas baseline
    in helmfile.single.

  * helmfile.instance: merge apps[].labels into release.labels with
    instance taking priority. atlas-template's bootstrap selector
    (bootstrap: true) depends on this.

  * helmfile.instance: optional ATLAS_SIDEDUMP_MAP_DIR side-dump second
    argv element on the atlas-redact post-renderer. snapshot-review's
    scrub-baseline replays each map against an older-baseline render.

Fixture migration:
  * tests/templates/app-named: drop the manual
    `{{ .Values.atlas.instance.name }}` munge from release.name; ATLAS
    auto-munges now (slice 3). Release becomes "primary-app-named" /
    "secondary-app-named" via prefix.
  * cluster1-deployment9-{primary,secondary}.bats: update INSTANCE to
    the munged form.

Full suite: 369/369 with stage-3 as default.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Retire stage-2 path; thin helmfile.single to fan-out shim

helmfile.single now does only what the stage-3 default needs:
  1. Validate deployment.yaml exists.
  2. Render deployment.yaml as a Go template with the atlas object as
     context, parse the resulting apps[] list.
  3. Emit one helmfiles[] entry per app instance, each pointing at
     helmfile.instance.yaml.gotmpl with the atlas object augmented by
     instance.{template, name}.

Removed (no longer reachable now that engine: stage2 is gone):
  - hierarchy twin-load ($realValues / $redactedValues)
  - hierarchy-level $replMap construction
  - environments.default.values emission
  - per-app-template release rewriting (chart paths, secrets rejection,
    progressive twin-load merge, applyListOverride wiring, redaction
    post-renderer wiring with side-dump)
  - the engine if/else branch and the get "engine" "stage2" default

The deleted block weighed ~400 lines; helmfile.single is 75 lines now.
All of that logic lives in either helmfile.instance (per-release
rewrites, redaction post-renderer, side-dump) or _values_loader (twin-
load, hierarchy walk, progressive merge).

Cleanup:
  - Drop explicit `engine: stage3` from fixtures 30-40 + postrenderer-fail.
  - Update doc-comments that referenced stage-2 (helmfile.instance,
    _values_loader, _functions, helmfile.all, app-stage3-probe).

Full suite: 369/369.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

docs: align README and CLAUDE.md with stage-3-only architecture

README.md
  * "Using the same template multiple times": drop the manual
    `{{ .Values.atlas.instance.name }}` instruction. Document auto-munge
    with apps[].nameStyle: prefix|suffix (prefix default).
  * "Labels": expand the commonLabels table to cover clusterName,
    clusterGroup, template, instance, variant — all auto-injected now.
  * "App Templates": add a "How ATLAS treats helmfile release options"
    table covering the per-instance rewrites (name, chart, values,
    secrets, condition/installed, skipSchemaValidation, postRenderer,
    keyring + set[].file + setString[].file, patches/transformers,
    labels). Lead-in clarifies the "stay close to vanilla helmfile"
    goal.
  * "Value Inheritance Logic": insert template-secrets and instance-
    secrets layers between values and hierarchy. Document the
    "secrets merged last among per-release lists" semantics.
  * "Full Load Order": renumber to include the new secrets layers
    (5-6); standalone-cluster skip range shifts to 10-12.
  * "How It Works": replace the two-step (load + render) description
    with the four-stage pipeline (discover, fan-out, per-instance
    rewrite, release-time loader) that matches the actual code now.

CLAUDE.md
  * Common Commands: switch to the subcommand-first invocation form
    (`helmfile template -f X` / `helmfile build -f X`) so the commands
    match the prefix-based allowlist patterns.
  * Core Template Pipeline: describe all four stages explicitly
    (helmfile.all → helmfile.single → helmfile.instance → loader)
    instead of conflating values resolution into helmfile.single.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

Add .claude/utils/run-bats.sh + allowlist

Wraps the test ritual (rm stale tmp dirs, run bats, count results,
list failures) into one allowlistable invocation. Modes:

  .claude/utils/run-bats.sh                       full suite, summary
  .claude/utils/run-bats.sh tests/bats/foo.bats   single file, full output
  .claude/utils/run-bats.sh -v                    full suite, full output

Exit code reflects test result (0 pass, 1 fail), so CI/agents can use
status without parsing output.

Allowlist additions: Bash(.claude/utils/run-bats.sh) and the *-form for
arguments. The script's internal rm of /tmp/atlas-bats-* runs without
prompting because the script itself is the allowlisted unit.

Also rolls in the broader allowlist set added on main (bats *, sops
--decrypt *, helm template *, helmfile template *, helmfile build *,
etc.) so the worktree shares the same prompt-free surface as main.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
max06 added a commit to max06/atlas that referenced this pull request May 1, 2026
…ge via a release value-template (#20) (#21)

v0.3.0 (commit fad007e on add-selectors-to-subhelmfiles, merged via #16)
added a hardcoded `selectors:` field to every emitted sub-helmfile entry
on the assumption that helmfile/helmfile#2545 was available. In stock
helmfile (1.4.4 and earlier — including the pre-1.0 versions consumers
still run), child `selectors:` REPLACE the inherited CLI selector when
filtering releases inside that sub-helmfile, so `--selector` became a
no-op and every render produced manifests for every deployment in the
repo. Verified empirically: 1.4.4 + --selector emitted 40 release dirs
instead of the targeted 1.

The same change deleted ATLAS's stage-1 atlas.filter.* env path on the
same wrong assumption, leaving consumers (especially the ArgoCD
ApplicationSet pattern) with no efficient way to render a single
deployment.

Restore both:
* Drop the hardcoded sub-helmfile `selectors:` block — CLI --selector
now reaches release-level commonLabels (cluster, clusterName,
clusterGroup, deploymentName, template, instance, variant) again.
* Re-add stage-1 atlas.filter.{cluster,deploymentName} short-circuit in
helmfile.all.yaml.gotmpl — non-matching clusters skip FS scan,
deployment collection, and sub-helmfile emission entirely. Wired from
env (ATLAS_FILTER_CLUSTER / ATLAS_FILTER_DEPLOYMENT_NAME) at the
top-level helmfile.yaml.gotmpl so consumers can pass it via `helmfile -e
...` or directly from an ArgoCD Application's container env.
* `selectorsInherited: true` removed — pointless without child selectors
to inherit through.

Tests: tests/bats/filtering.bats covers both contracts (CLI --selector
+ env-var stage-1 filter) with positive and negative assertions, so the
v0.3.0 regression cannot recur silently. helmfile_list helper drops
stderr because bats's `run` captures both streams and helmfile's "err:
no releases found..." would otherwise corrupt the JSON-parse assertion
for empty-result selectors.

Full suite: 290/290.

snapshot-review: leverage stage-1 atlas.filter.* env vars in fast path

The reusable PR-review workflow's fast path renders one (cluster,
deploymentName) pair at a time with --selector. Pre-fix, every
invocation parsed every sub-helmfile and decrypted SOPS for unrelated
deployments — the --selector only filtered AFTER the work was done.

With the stage-1 atlas.filter.* env path restored, ATLAS_FILTER_CLUSTER
and ATLAS_FILTER_DEPLOYMENT_NAME short-circuit in helmfile.all so
non-matching sub-helmfiles never get emitted in the first place.
helmfile.single only sees one entry, SOPS only loads for that one
deployment.

Both env vars AND --selector are passed:
- Current ATLAS: env vars short-circuit at stage-1, --selector is a
redundant safety net.
- Older ATLAS pinned by consumer (no stage-1 filter): env vars are
no-op, --selector handles release-level filtering.

Probe + bulk fallback path is untouched — still detects pinned ATLAS
refs that carry the broken hardcoded sub-helmfile selectors and falls
back to a single bulk render. Probe diagnostic and warning text updated
to reflect that the broken behavior is now historical, not the helmfile
binary's fault.

Two near-identical edits: the merge-result render (PR side) and the
baseline render (target branch HEAD). Both fast paths now export the env
vars per pair.



Add stage-3 release-aware values pipeline (prototype)

Introduces a third pipeline stage where release values are resolved at
helmfile's release-evaluation time rather than at stage-2. The new path
makes .Release.Name / .Release.Namespace available inside values
gotmpls, which the existing stage-2 progressive-merge cannot offer.

Pipeline shape:
  stage 1 (helmfile.all)      -> per-deployment sub-helmfiles
stage 2 (helmfile.single) -> per-instance sub-helmfiles (when engine:
stage3)
  stage 3 (helmfile.instance) -> emits releases with values: [<loader>]
stage 3' (values-loader) -> resolves values per-release with .Release.*
in scope

Each app instance must live in its own helmfile file because multiple
environments: blocks in one file collapse to a single merged env-values
(verified by spike). Splitting by instance keeps atlas.instance
unambiguous and removes the need for a release-name lookup table.

Deployments opt in via top-level `engine: stage3` in deployment.yaml.
Existing deployments without the flag run through the unchanged stage-2
path; all 284 prior assertions stay green.

Prototype scope (deferred to follow-ups):
  - SOPS decryption in the loader
  - Redaction twin-load + post-renderer wiring
  - applyListOverride for patches/transformers in stage-3
  - .yaml.gotmpl + group-level hierarchy variants in the loader
  - Progressive accumulator across the values: list



Stage-3 loader: complete hierarchy walk (.sops.yaml + .yaml +
.yaml.gotmpl)

Extends the values loader's hierarchy walk to mirror stage-2's full
source order at every level (global, group, cluster, deployment). Each
level now loads .sops.yaml -> .yaml -> .yaml.gotmpl with later sources
overriding earlier ones. .yaml.gotmpl files are tpl-rendered with a
fresh ctx that exposes the running merge so a .gotmpl at one level can
reference any key defined earlier — including SOPS-decrypted values.

SOPS at the hierarchy level was bundled in (rather than deferred to the
template/instance SOPS milestone) because every existing hierarchy
.yaml.gotmpl fixture references SOPS keys; .yaml.gotmpl is unusable
without it. Template- and instance-level SOPS files remain in the next
milestone.

deployment31 (group1/cluster2 + engine: stage3) covers:
  - plain .yaml at all four hierarchy levels
  - .yaml.gotmpl variants at every level
  - .yaml.gotmpl chaining through earlier .yaml and earlier .gotmpl
  - override precedence (group beats global, cluster beats group)
  - nested deep merge across the four levels

309/309 bats green.



Stage-3 loader: progressive accumulator across the values: list

Reorders the loader so hierarchy is computed first as a baseline ctx for
the template + instance walks, then overlaid at the end so it still wins
precedence (chart < template < instance < hierarchy is preserved). Each
.yaml.gotmpl entry in a release's values: list is now tpl-rendered with
a fresh ctx that exposes:

  - .Values (atlas baseline)
  - the hierarchy as already loaded
  - the running merge of all earlier entries in the same values: list
  - .Release.* (Name, Namespace, Chart, Labels)
- .Values.foo self-ref so wrapped form ({{ .Values.foo }}) keeps working

A later entry can therefore reference any key defined by an earlier
inline map, plain .yaml, or .yaml.gotmpl — same semantics stage-2
already provides via its progressive twin-load merge, only this version
is release-aware.

deployment32 (cluster1 + engine: stage3) covers:
  - inline + plain .yaml + .yaml.gotmpl in one values: list
  - .gotmpl reading earlier inline keys
  - .gotmpl reading earlier plain .yaml keys
  - .gotmpl mixing hierarchy + earlier-list keys
  - .gotmpl using .Release.Name alongside earlier keys
  - .gotmpl using .Values.foo (wrapped) form

317/317 bats green.



Stage-3 named instances: cover via test, no loader change needed

deployment33 instantiates the same app template twice in one deployment
via apps[].name (primary, secondary). Stage-2's per-instance fan-out
already gives each instance its own helmfile state file with a fully
populated atlas.instance.name in env-values; the loader's existing
instance-matcher already keys on (template, instance-name). The whole
flow worked on the first run with no loader edit — adding the fixture
locks in the behaviour as a regression guard.

deployment33 covers:
  - both releases render (primary, secondary)
  - each release sees its own atlas.instance.name in the values loader
  - release name correctly embeds the instance name
  - shared template name surfaces identically on both

325/325 bats green.



Stage-3 loader: SOPS in template + instance values: lists

Adds a .sops.yaml branch to the template-level and instance-level walks
in the values loader. fetchSecretValue with the ref+sops:// protocol
performs whole-document decryption — same primitive used at the
hierarchy level (commit 38500a4) and by stage-2. List-order precedence
is preserved: a .sops.yaml listed after an inline map overrides the
inline keys.

deployment34 reuses the existing app-tplsops template
(values.yaml.gotmpl
+ inline map + values.sops.yaml) through engine: stage3. Asserts:
  - plain template-include value reaches output
  - template-inline default reaches output
  - SOPS scalar decrypts
  - SOPS nested map decrypts (preserves nested structure)
  - SOPS value listed last overrides earlier inline of the same key

The instance-level SOPS branch is added symmetrically to the loader but
no existing fixture exercises it; covered when a test surfaces.

Redaction of these template-level SOPS values is deferred to feature #6
(twin-load + post-renderer wiring).

330/330 bats green.



Stage-3 redaction: shared atlas.values.merged + twin-load in stage-3

Extracts the loader's merge logic into a single named template
(atlas.values.merged in templates/_values_loader.tpl) parameterised by
.redact. Two callers share the same body:

  helmfile.values-loader.yaml.gotmpl
    Helmfile evaluates this once per release at release-time. Calls
    atlas.values.merged with redact=false and emits the result as the
    real values blob for helm.

  helmfile.instance.yaml.gotmpl  (per release, when redactSecrets is on)
    Calls atlas.values.merged twice — once with redact=false (real),
once with redact=true (SOPS values rewritten through atlas.redact.value
before merge, transitive taint via twin-load). Diffs the two trees via
    atlas.diff.values to produce a replacement map, b64-encodes it into
    postRendererArgs, and wires the atlas-redact post-renderer.

Redaction-mode runs walk twice per release (real + redacted) plus once
at release-time (loader). Regular ArgoCD renders skip the entire stage-3
twin-load block — only the loader runs. So the redaction overhead is
paid only by the CI/redacted-render workflow, never by ArgoCD.

deployment34 now also asserts:
  - template SOPS scalar redacted
  - template SOPS override redacted (list-order precedence preserved)
  - template SOPS nested map redacted at every leaf
  - plain (non-SOPS) values pass through untouched

1MB ARG_MAX safety check ported from stage-2. Sidedump path (used by the
snapshot-review workflow) deferred — not needed for prototype
validation.

335/335 bats green.



Stage-3: strip release.condition and release.installed

In ATLAS, deploy intent is signalled by the presence of a
deployment.yaml entry. Letting helmfile-level condition/installed
through would silently produce empty deployments — indistinguishable
from a misconfiguration — when ArgoCD discovers the directory and finds
no manifests.

Drop both fields in the per-instance release rewrite step so app
templates remain vanilla-helmfile-readable but render uniformly under
ATLAS.

Fixture: deployment35 / app-stage3-condinst sets condition:
never.enabled (would be a parse error in vanilla helmfile) and
installed: false (would be silently skipped). With the strip in place
both render normally.



Stage-3: add template + instance commonLabels

Auto-inject two labels on every stage-3 release so consumers can filter
by app-template family and named instance:
  - template: the app-template directory name
  - instance: the apps[].name value (or template name when name unset)

Useful for ArgoCD ApplicationSet selectors and `helmfile list -l ...`.
The remaining commonLabels (cluster, clusterName, deploymentName,
variant, clusterGroup) are unchanged.

Test covers both the default-name path (deployment30 — instance defaults
to template) and the explicit-name path (deployment33 — primary and
secondary instances of the same template).



Stage-3: auto-munge release names with instance.name

When apps[].name is set and differs from the template name, ATLAS
rewrites release names so each instance produces a distinct release
without forcing the template author to embed `{{
.Values.atlas.instance.name }}`. Default style is `<instance>-<release>`
(prefix); apps[].nameStyle: suffix flips to `<release>-<instance>` for
naming conventions like `vm-cust-abc`.

Both helmfile.instance and the values-loader apply the same munge logic:
helmfile.instance rewrites the emitted release.name; the loader applies
the same transform when matching the template's author-intended name
against helmfile's .Release.Name (now the munged form).

Existing fixture deployment33/app-stage3-named drops its manual `{{
.Values.atlas.instance.name }}` munge — the template author intent is
the bare release name "stage3-named" and ATLAS produces
"primary-stage3-named" / "secondary-stage3-named" automatically.

New fixture deployment36/app-stage3-namestyle covers all three flavours
in one deployment: default-prefix, explicit-prefix, suffix.



Stage-3: fail render when template sets release.postRenderer

ATLAS owns the helm post-renderer slot for its redaction pipeline. Helm
4 does not support post-renderer chaining, and silently overwriting a
user- set postRenderer would mask intent and produce confusing diffs.
Reject the field with a clear pointer to open an issue so the use case
can be discussed before any workaround.

Negative-test fixture lives under tests/fixtures-negative/ so the
standard bulk render in render.bash is unaffected. The bats test invokes
helmfile directly with its own --state-values-set roots.



Stage-3: default skipSchemaValidation to true (preserve user value)

helm runs schema validation only on install/upgrade/lint, not the
template/build paths ATLAS exercises during ArgoCD render. The flag
primarily affects consumer-side bootstrap, where missing CRDs are a
common false-positive failure source. Default it on so that path stays
clean. User-set values (true or false) are preserved unchanged.

Fixture deployment37 / app-stage3-schema covers both branches: a release
without the field gets defaulted to true; a release that sets false
explicitly keeps false.



Stage-3: re-accept release.secrets, merge last, path-resolve

Helmfile's env-level secrets contract: "non-HCL secrets are loaded first
but merged last". ATLAS replicates this at the release level — every
entry in release.secrets and apps[].secrets lands AFTER all values:
entries. Paths resolve template-relative for release.secrets and
deployment-relative for apps[].secrets. Decryption goes through the
existing SOPS fetchSecretValue path (same as .sops.yaml in values:), so
the helm-secrets plugin is not required.

helmfile.instance strips release.secrets from the emitted state once the
loader has handled them, so helmfile does not also try to decrypt via
helm-secrets (avoiding duplicate work and a hard plugin dependency).

Fixture deployment38 / app-stage3-secrets demonstrates all three layers:
template values, template secrets (overrides values), instance secrets
(overrides template secrets).

tests/.sops.yaml: relax creation-rule regex from `values\.sops\.yaml$`
to `\.sops\.yaml$` so secret fixtures named for their role
(template-secret.sops.yaml, instance-secret.sops.yaml) get the
testing-age recipient automatically.



Stage-3: wire applyListOverride for patches/transformers

Route release.strategicMergePatches, release.jsonPatches, and
release.transformers through atlas.applyListOverride during the per-
instance rewrite, mirroring stage-2's behaviour. Template-relative paths
become absolute so helmfile can locate the patch files regardless of
consumer CWD; instance-level overrides from deployment.yaml apps[] are
appended.

release.values is intentionally excluded — stage-3's loader replaces it
wholesale and resolves entries itself via the values-loader pipeline.

Fixture deployment39 / app-stage3-patches uses a strategicMergePatch to
add an annotation onto chart1's ConfigMap, exercising both the path
resolution wiring and helmfile's actual patch application via kustomize.

Note: convertPaths joins targetPath with the original entry verbatim, so
"./foo" becomes "<dir>/./foo" rather than the cleaner "<dir>/foo".
kustomize accepts either; normalization is left for a follow-up.



Stage-3: path-resolve keyring + set[].file + setString[].file

Resolve template-relative paths for the remaining helm-CLI fields that
take a path:
  keyring          — single scalar (passed to helm as --keyring)
  set[].file       — nested key inside a list of maps (--set-file)
  setString[].file — same shape as set[]

Mirrors the chart: handling — only ./ and ../ prefixes are rewritten;
absolute paths and bare names pass through. Without resolution helm
would look in the consumer's CWD instead of the template dir.

Fixture deployment40 / app-stage3-paths exercises all three; the bats
test caches the build-state YAML once via setup_file (helmfile build is
expensive) and asserts each path is absolute and points inside the
template dir.



Stage-3: flip default engine, port missing wiring, migrate d9 + d11

helmfile.single now defaults to engine: stage3 when deployment.yaml
omits the field. All 1-29 fixtures (and any consumer that doesn't opt in
explicitly) flow through the per-instance pipeline.

Three pieces of stage-2 functionality the previous parallel-fixture work
hadn't touched moved over so the existing suite stays green:

* _values_loader: seed $merged with .Values.atlas so the chart sees
.Values.atlas.* (deployment context, instance fields, redactSecrets)
just as stage-2 did. Mirrors the dict "atlas" .Values.atlas baseline in
helmfile.single.

* helmfile.instance: merge apps[].labels into release.labels with
instance taking priority. atlas-template's bootstrap selector
(bootstrap: true) depends on this.

* helmfile.instance: optional ATLAS_SIDEDUMP_MAP_DIR side-dump second
argv element on the atlas-redact post-renderer. snapshot-review's
scrub-baseline replays each map against an older-baseline render.

Fixture migration:
* tests/templates/app-named: drop the manual `{{
.Values.atlas.instance.name }}` munge from release.name; ATLAS
auto-munges now (slice 3). Release becomes "primary-app-named" /
"secondary-app-named" via prefix.
* cluster1-deployment9-{primary,secondary}.bats: update INSTANCE to the
munged form.

Full suite: 369/369 with stage-3 as default.



Retire stage-2 path; thin helmfile.single to fan-out shim

helmfile.single now does only what the stage-3 default needs:
  1. Validate deployment.yaml exists.
2. Render deployment.yaml as a Go template with the atlas object as
context, parse the resulting apps[] list.
3. Emit one helmfiles[] entry per app instance, each pointing at
helmfile.instance.yaml.gotmpl with the atlas object augmented by
instance.{template, name}.

Removed (no longer reachable now that engine: stage2 is gone):
  - hierarchy twin-load ($realValues / $redactedValues)
  - hierarchy-level $replMap construction
  - environments.default.values emission
- per-app-template release rewriting (chart paths, secrets rejection,
progressive twin-load merge, applyListOverride wiring, redaction
post-renderer wiring with side-dump)
  - the engine if/else branch and the get "engine" "stage2" default

The deleted block weighed ~400 lines; helmfile.single is 75 lines now.
All of that logic lives in either helmfile.instance (per-release
rewrites, redaction post-renderer, side-dump) or _values_loader (twin-
load, hierarchy walk, progressive merge).

Cleanup:
- Drop explicit `engine: stage3` from fixtures 30-40 +
postrenderer-fail.
- Update doc-comments that referenced stage-2 (helmfile.instance,
_values_loader, _functions, helmfile.all, app-stage3-probe).

Full suite: 369/369.



docs: align README and CLAUDE.md with stage-3-only architecture

README.md
* "Using the same template multiple times": drop the manual `{{
.Values.atlas.instance.name }}` instruction. Document auto-munge with
apps[].nameStyle: prefix|suffix (prefix default).
* "Labels": expand the commonLabels table to cover clusterName,
clusterGroup, template, instance, variant — all auto-injected now.
* "App Templates": add a "How ATLAS treats helmfile release options"
table covering the per-instance rewrites (name, chart, values, secrets,
condition/installed, skipSchemaValidation, postRenderer, keyring +
set[].file + setString[].file, patches/transformers, labels). Lead-in
clarifies the "stay close to vanilla helmfile" goal.
* "Value Inheritance Logic": insert template-secrets and instance-
secrets layers between values and hierarchy. Document the "secrets
merged last among per-release lists" semantics.
* "Full Load Order": renumber to include the new secrets layers (5-6);
standalone-cluster skip range shifts to 10-12.
* "How It Works": replace the two-step (load + render) description with
the four-stage pipeline (discover, fan-out, per-instance rewrite,
release-time loader) that matches the actual code now.

CLAUDE.md
* Common Commands: switch to the subcommand-first invocation form
(`helmfile template -f X` / `helmfile build -f X`) so the commands match
the prefix-based allowlist patterns.
* Core Template Pipeline: describe all four stages explicitly
(helmfile.all → helmfile.single → helmfile.instance → loader) instead of
conflating values resolution into helmfile.single.



Add .claude/utils/run-bats.sh + allowlist

Wraps the test ritual (rm stale tmp dirs, run bats, count results, list
failures) into one allowlistable invocation. Modes:

  .claude/utils/run-bats.sh                       full suite, summary
.claude/utils/run-bats.sh tests/bats/foo.bats single file, full output
.claude/utils/run-bats.sh -v full suite, full output

Exit code reflects test result (0 pass, 1 fail), so CI/agents can use
status without parsing output.

Allowlist additions: Bash(.claude/utils/run-bats.sh) and the *-form for
arguments. The script's internal rm of /tmp/atlas-bats-* runs without
prompting because the script itself is the allowlisted unit.

Also rolls in the broader allowlist set added on main (bats *, sops
--decrypt *, helm template *, helmfile template *, helmfile build *,
etc.) so the worktree shares the same prompt-free surface as main.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
hbjydev pushed a commit to hbjydev/phoebe that referenced this pull request May 29, 2026
…) (#32)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [aqua:helmfile/helmfile](https://github.com/helmfile/helmfile) | minor | `1.4.1` → `1.5.2` |

---

### Release Notes

<details>
<summary>helmfile/helmfile (aqua:helmfile/helmfile)</summary>

### [`v1.5.2`](https://github.com/helmfile/helmfile/releases/tag/v1.5.2)

[Compare Source](helmfile/helmfile@v1.5.1...v1.5.2)

#### What's Changed

- Bump Helm support to 3.21.0 and 4.2.0 by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2588](helmfile/helmfile#2588)
- bump helm-diff to v3.15.7 by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2591](helmfile/helmfile#2591)
- fix: refresh Chart.lock after rewriting file:// dependencies by [@&#8203;sstarcher](https://github.com/sstarcher) in [#&#8203;2587](helmfile/helmfile#2587)
- feat: support HELMFILE\_KUBE\_CONTEXT env var for default kube context by [@&#8203;dschmidt](https://github.com/dschmidt) in [#&#8203;2593](helmfile/helmfile#2593)
- feat: support HELMFILE\_NAMESPACE env var for default namespace by [@&#8203;dschmidt](https://github.com/dschmidt) in [#&#8203;2592](helmfile/helmfile#2592)
- fix: normalize dependency chart path before DirectoryExistsAt check by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2598](helmfile/helmfile#2598)
- Add jsonPatches regression coverage for chartify lookup rendering by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2586](helmfile/helmfile#2586)
- feat: add defaultInherit for automatic release template inheritance by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2600](helmfile/helmfile#2600)
- fix: restore kubedog status progress output during tracking by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2602](helmfile/helmfile#2602)
- feat: show diff preview when sync --interactive is used by [@&#8203;vomba](https://github.com/vomba) in [#&#8203;2603](helmfile/helmfile#2603)
- build(deps): bump github.com/containerd/containerd from 1.7.30 to 1.7.32 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2607](helmfile/helmfile#2607)
- feat: support HELMFILE\_\* env vars for more global flags by [@&#8203;dschmidt](https://github.com/dschmidt) in [#&#8203;2606](helmfile/helmfile#2606)

#### New Contributors

- [@&#8203;vomba](https://github.com/vomba) made their first contribution in [#&#8203;2603](helmfile/helmfile#2603)

**Full Changelog**: <helmfile/helmfile@v1.5.1...v1.5.2>

### [`v1.5.1`](https://github.com/helmfile/helmfile/releases/tag/v1.5.1)

[Compare Source](helmfile/helmfile@v1.5.0...v1.5.1)

#### What's Changed

- fix: add trackFailOnError option to control kubedog exit code by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2576](helmfile/helmfile#2576)
- docs: update helmfile skill to reflect v1.1 documentation by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2577](helmfile/helmfile#2577)
- fix(state): resolve OCI repo prefix in ad-hoc release dependencies by [@&#8203;dschmidt](https://github.com/dschmidt) in [#&#8203;2579](helmfile/helmfile#2579)
- feat(state): add mergeStrategy: fallback for first-file-wins env values by [@&#8203;dschmidt](https://github.com/dschmidt) in [#&#8203;2578](helmfile/helmfile#2578)
- Expose internal apis by [@&#8203;ceriath](https://github.com/ceriath) in [#&#8203;2520](helmfile/helmfile#2520)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.100.1 to 1.101.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2581](helmfile/helmfile#2581)
- chore: Deduplicate preparation code of sync and apply by [@&#8203;ceriath](https://github.com/ceriath) in [#&#8203;2523](helmfile/helmfile#2523)
- build(deps): bump gitpython from 3.1.47 to 3.1.49 in /docs by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2582](helmfile/helmfile#2582)
- chore: Emit "cleanup" events later to match the behavior in "apply" by [@&#8203;ceriath](https://github.com/ceriath) in [#&#8203;2522](helmfile/helmfile#2522)
- build(deps): bump golang.org/x/term from 0.42.0 to 0.43.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2584](helmfile/helmfile#2584)
- build(deps): bump gitpython from 3.1.49 to 3.1.50 in /docs by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2585](helmfile/helmfile#2585)
- fix: template helmDefaults.postRendererArgs with release data by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2583](helmfile/helmfile#2583)

**Full Changelog**: <helmfile/helmfile@v1.5.0...v1.5.1>

### [`v1.5.0`](https://github.com/helmfile/helmfile/releases/tag/v1.5.0)

[Compare Source](helmfile/helmfile@v1.4.5...v1.5.0)

#### What's Changed

- feat: add --write-output flag to helmfile fetch for air-gapped environments by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2572](helmfile/helmfile#2572)
- feat: add 'create' subcommand to scaffold helmfile deployment projects by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2574](helmfile/helmfile#2574)
- docs: restructure documentation and improve newcomer experience by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2573](helmfile/helmfile#2573)
- docs: deduplicate Technical Details sections in values-and-merging.md by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2575](helmfile/helmfile#2575)

**Full Changelog**: <helmfile/helmfile@v1.4.5...v1.5.0>

### [`v1.4.5`](https://github.com/helmfile/helmfile/releases/tag/v1.4.5)

[Compare Source](helmfile/helmfile@v1.4.4...v1.4.5)

#### What's Changed

- build(deps): bump go.opentelemetry.io/otel/sdk from 1.42.0 to 1.43.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2524](helmfile/helmfile#2524)
- chore: rename variables to match in apply and sync by [@&#8203;ceriath](https://github.com/ceriath) in [#&#8203;2521](helmfile/helmfile#2521)
- chore: bump Helm to v4.1.4 by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2525](helmfile/helmfile#2525)
- build(deps): bump golang.org/x/term from 0.41.0 to 0.42.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2529](helmfile/helmfile#2529)
- bump helm v3.20.1 to v3.20.2 by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2530](helmfile/helmfile#2530)
- build(deps): bump github.com/helmfile/vals from 0.43.8 to 0.43.9 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2533](helmfile/helmfile#2533)
- Update Go from 1.25.8 to 1.26.2 by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2535](helmfile/helmfile#2535)
- fix: boolean false overrides dropped in multi-document helmfiles ([#&#8203;2527](helmfile/helmfile#2527)) by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2532](helmfile/helmfile#2532)
- build(deps): bump github.com/helmfile/chartify from 0.26.2 to 0.26.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2538](helmfile/helmfile#2538)
- fix: add mutex lock for concurrent rewriteChartDependencies access by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2509](helmfile/helmfile#2509)
- fix: update state values files handling to replace arrays instead of merging by [@&#8203;Moglum](https://github.com/Moglum) in [#&#8203;2537](helmfile/helmfile#2537)
- fix: helmDefaults.postRendererArgs not passed to helm commands by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2510](helmfile/helmfile#2510)
- enabledns flags available on template command by [@&#8203;Diliz](https://github.com/Diliz) in [#&#8203;2511](helmfile/helmfile#2511)
- build(deps): bump k8s.io/apimachinery from 0.35.3 to 0.35.4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2540](helmfile/helmfile#2540)
- build(deps): bump k8s.io/client-go from 0.35.3 to 0.35.4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2539](helmfile/helmfile#2539)
- build(deps): bump github.com/zclconf/go-cty from 1.18.0 to 1.18.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2542](helmfile/helmfile#2542)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.14 to 1.32.15 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2543](helmfile/helmfile#2543)
- fix: eliminate race condition in rewriteChartDependencies by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2541](helmfile/helmfile#2541)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.99.0 to 1.99.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2546](helmfile/helmfile#2546)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.15 to 1.32.16 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2547](helmfile/helmfile#2547)
- build(deps): bump k8s.io/apimachinery from 0.35.4 to 0.36.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2553](helmfile/helmfile#2553)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.99.1 to 1.100.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2552](helmfile/helmfile#2552)
- Fix helmfile init failing to update outdated helm plugins with Helm v4 by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2554](helmfile/helmfile#2554)
- fix: skip subhelmfiles when selectors conflict with CLI selectors by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2545](helmfile/helmfile#2545)
- build(deps): bump gitpython from 3.1.41 to 3.1.47 in /docs by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2555](helmfile/helmfile#2555)
- fix: apply post-renderer to output-dir-template output by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2531](helmfile/helmfile#2531)
- build(deps): bump go.uber.org/zap from 1.27.1 to 1.28.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2557](helmfile/helmfile#2557)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.100.0 to 1.100.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2558](helmfile/helmfile#2558)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.16 to 1.32.17 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2559](helmfile/helmfile#2559)
- update readme add install from source by [@&#8203;Sianao](https://github.com/Sianao) in [#&#8203;2561](helmfile/helmfile#2561)
- Honor `skipSchemaValidation` during chartification when `forceNamespace` is set by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2550](helmfile/helmfile#2550)
- build(deps): bump github.com/Masterminds/semver/v3 from 3.4.0 to 3.5.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2565](helmfile/helmfile#2565)
- fix: deduplicate chart dependencies in helmfile.lock by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2567](helmfile/helmfile#2567)
- build(deps): replace werf/kubedog-for-werf-helm with werf/kubedog by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2568](helmfile/helmfile#2568)
- build(deps): bump helmfile/vals from v0.43.9 to v0.44.0 by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2569](helmfile/helmfile#2569)
- fix: use --post-renderer-args=VALUE format to prevent Helm flag parsing failure by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2570](helmfile/helmfile#2570)

#### New Contributors

- [@&#8203;Moglum](https://github.com/Moglum) made their first contribution in [#&#8203;2537](helmfile/helmfile#2537)
- [@&#8203;Diliz](https://github.com/Diliz) made their first contribution in [#&#8203;2511](helmfile/helmfile#2511)
- [@&#8203;Sianao](https://github.com/Sianao) made their first contribution in [#&#8203;2561](helmfile/helmfile#2561)

**Full Changelog**: <helmfile/helmfile@v1.4.4...v1.4.5>

### [`v1.4.4`](https://github.com/helmfile/helmfile/releases/tag/v1.4.4)

[Compare Source](helmfile/helmfile@v1.4.3...v1.4.4)

#### What's Changed

- build(deps): bump azure/setup-helm from 4.3.1 to 5.0.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2499](helmfile/helmfile#2499)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.97.1 to 1.97.2 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2500](helmfile/helmfile#2500)
- fix: helmfile fetch fails for kustomization directories by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2504](helmfile/helmfile#2504)
- fix: keep all chart dependencies key / values by [@&#8203;champtar](https://github.com/champtar) in [#&#8203;2501](helmfile/helmfile#2501)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.97.2 to 1.97.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2505](helmfile/helmfile#2505)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.12 to 1.32.13 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2506](helmfile/helmfile#2506)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.97.3 to 1.98.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2512](helmfile/helmfile#2512)
- build(deps): bump github.com/go-jose/go-jose/v4 from 4.1.3 to 4.1.4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2513](helmfile/helmfile#2513)
- chore: update go version to 1.25.8 by [@&#8203;eadred](https://github.com/eadred) in [#&#8203;2514](helmfile/helmfile#2514)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.13 to 1.32.14 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2516](helmfile/helmfile#2516)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.98.0 to 1.99.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2517](helmfile/helmfile#2517)
- build(deps): bump github.com/helmfile/vals from 0.43.7 to 0.43.8 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2518](helmfile/helmfile#2518)

#### New Contributors

- [@&#8203;champtar](https://github.com/champtar) made their first contribution in [#&#8203;2501](helmfile/helmfile#2501)
- [@&#8203;eadred](https://github.com/eadred) made their first contribution in [#&#8203;2514](helmfile/helmfile#2514)

**Full Changelog**: <helmfile/helmfile@v1.4.3...v1.4.4>

### [`v1.4.3`](https://github.com/helmfile/helmfile/releases/tag/v1.4.3)

[Compare Source](helmfile/helmfile@v1.4.2...v1.4.3)

#### What's Changed

- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.97.0 to 1.97.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2488](helmfile/helmfile#2488)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.11 to 1.32.12 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2487](helmfile/helmfile#2487)
- Fix interactive apply asks in no change situation by [@&#8203;tarrow](https://github.com/tarrow) in [#&#8203;945](helmfile/helmfile#945)
- build(deps): bump google.golang.org/grpc from 1.79.2 to 1.79.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2490](helmfile/helmfile#2490)
- fix: helmfile list now reflects version from helmfile.lock by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2486](helmfile/helmfile#2486)
- build(deps): bump k8s.io/client-go from 0.35.2 to 0.35.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2492](helmfile/helmfile#2492)
- build(deps): bump github.com/fatih/color from 1.18.0 to 1.19.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2494](helmfile/helmfile#2494)
- fix: cleanup hooks not receiving error signal by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2475](helmfile/helmfile#2475)
- fix: pass --timeout flag through to helm for sync and apply by [@&#8203;hristiy4n](https://github.com/hristiy4n) in [#&#8203;2495](helmfile/helmfile#2495)
- fix: error on missing secret key when using vals by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2496](helmfile/helmfile#2496)
- build: update helm-diff to v3.15.3 by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2498](helmfile/helmfile#2498)
- feat: add an arg that passing description to `helm upgrade` command by [@&#8203;swimablefish](https://github.com/swimablefish) in [#&#8203;2497](helmfile/helmfile#2497)

#### New Contributors

- [@&#8203;tarrow](https://github.com/tarrow) made their first contribution in [#&#8203;945](helmfile/helmfile#945)
- [@&#8203;hristiy4n](https://github.com/hristiy4n) made their first contribution in [#&#8203;2495](helmfile/helmfile#2495)
- [@&#8203;swimablefish](https://github.com/swimablefish) made their first contribution in [#&#8203;2497](helmfile/helmfile#2497)

**Full Changelog**: <helmfile/helmfile@v1.4.2...v1.4.3>

### [`v1.4.2`](https://github.com/helmfile/helmfile/releases/tag/v1.4.2)

[Compare Source](helmfile/helmfile@v1.4.1...v1.4.2)

#### What's Changed

- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.96.2 to 1.96.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2456](helmfile/helmfile#2456)
- build(deps): bump docker/login-action from 3 to 4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2454](helmfile/helmfile#2454)
- build(deps): bump docker/setup-qemu-action from 3 to 4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2453](helmfile/helmfile#2453)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.10 to 1.32.11 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2455](helmfile/helmfile#2455)
- Add CHANGELOG.md by [@&#8203;PhilipLudington](https://github.com/PhilipLudington) in [#&#8203;2457](helmfile/helmfile#2457)
- build(deps): bump docker/setup-buildx-action from 3 to 4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2462](helmfile/helmfile#2462)
- build(deps): bump markdown from 3.6 to 3.8.1 in /docs by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2463](helmfile/helmfile#2463)
- build(deps): bump docker/build-push-action from 6 to 7 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2469](helmfile/helmfile#2469)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.96.3 to 1.96.4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2470](helmfile/helmfile#2470)
- build(deps): bump docker/metadata-action from 5 to 6 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2468](helmfile/helmfile#2468)
- feat: add helm-legacy track mode for Helm v4 compatibility by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2466](helmfile/helmfile#2466)
- docs: add comprehensive values merging and data flow guide by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2461](helmfile/helmfile#2461)
- fix: nested helmfile values should replace arrays, not merge element-by-element by [@&#8203;aditmeno](https://github.com/aditmeno) in [#&#8203;2458](helmfile/helmfile#2458)
- build(deps): bump go.yaml.in/yaml/v2 from 2.4.3 to 2.4.4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2473](helmfile/helmfile#2473)
- build(deps): bump golang.org/x/sync from 0.19.0 to 0.20.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2472](helmfile/helmfile#2472)
- fix: use --force-replace flag for Helm 4 instead of deprecated --force by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2477](helmfile/helmfile#2477)
- build(deps): bump golang.org/x/term from 0.40.0 to 0.41.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2479](helmfile/helmfile#2479)
- build(deps): bump github.com/helmfile/vals from 0.43.6 to 0.43.7 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2478](helmfile/helmfile#2478)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.96.4 to 1.97.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2484](helmfile/helmfile#2484)
- build(deps): bump Helm from v4.1.1 to v4.1.3 by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2481](helmfile/helmfile#2481)
- feat: add --force-conflicts flag support for Helm 4 by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2480](helmfile/helmfile#2480)

#### New Contributors

- [@&#8203;PhilipLudington](https://github.com/PhilipLudington) made their first contribution in [#&#8203;2457](helmfile/helmfile#2457)

**Full Changelog**: <helmfile/helmfile@v1.4.1...v1.4.2>

</details>

---

### Configuration

📅 **Schedule**: (in timezone Europe/London)

- 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 PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTUuMSIsInVwZGF0ZWRJblZlciI6IjQzLjE5NS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJ0eXBlL21pbm9yIl19-->

Reviewed-on: https://forgejo.hayden.moe/hayden/phoebe/pulls/32
hbjydev pushed a commit to hbjydev/phoebe that referenced this pull request Jun 10, 2026
…) (#42)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [aqua:helmfile/helmfile](https://github.com/helmfile/helmfile) | minor | `1.4.1` → `1.5.3` |

---

> ⚠️ **Warning**
>
> Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/28) for more information.

---

### Release Notes

<details>
<summary>helmfile/helmfile (aqua:helmfile/helmfile)</summary>

### [`v1.5.3`](https://github.com/helmfile/helmfile/releases/tag/v1.5.3)

[Compare Source](helmfile/helmfile@v1.5.2...v1.5.3)

#### What's Changed

- build(deps): bump github.com/gookit/color from 1.5.4 to 1.6.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2608](helmfile/helmfile#2608)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.17 to 1.32.18 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2610](helmfile/helmfile#2610)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.101.0 to 1.102.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2612](helmfile/helmfile#2612)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.102.0 to 1.102.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2613](helmfile/helmfile#2613)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.18 to 1.32.20 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2614](helmfile/helmfile#2614)
- fix: support array of maps in set/setTemplate values by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2615](helmfile/helmfile#2615)
- fix: remove naked return by returning expected values by [@&#8203;ceriath](https://github.com/ceriath) in [#&#8203;2617](helmfile/helmfile#2617)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.102.1 to 1.103.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2619](helmfile/helmfile#2619)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.20 to 1.32.21 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2618](helmfile/helmfile#2618)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.103.0 to 1.103.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2620](helmfile/helmfile#2620)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.21 to 1.32.22 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2621](helmfile/helmfile#2621)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.103.1 to 1.103.2 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2622](helmfile/helmfile#2622)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.22 to 1.32.23 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2623](helmfile/helmfile#2623)
- Bump helm-diff to v3.15.8 across runtime defaults and execution environments by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2624](helmfile/helmfile#2624)
- build(deps): bump golang.org/x/sync from 0.20.0 to 0.21.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2625](helmfile/helmfile#2625)

**Full Changelog**: <helmfile/helmfile@v1.5.2...v1.5.3>

### [`v1.5.2`](https://github.com/helmfile/helmfile/releases/tag/v1.5.2)

[Compare Source](helmfile/helmfile@v1.5.1...v1.5.2)

#### What's Changed

- Bump Helm support to 3.21.0 and 4.2.0 by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2588](helmfile/helmfile#2588)
- bump helm-diff to v3.15.7 by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2591](helmfile/helmfile#2591)
- fix: refresh Chart.lock after rewriting file:// dependencies by [@&#8203;sstarcher](https://github.com/sstarcher) in [#&#8203;2587](helmfile/helmfile#2587)
- feat: support HELMFILE\_KUBE\_CONTEXT env var for default kube context by [@&#8203;dschmidt](https://github.com/dschmidt) in [#&#8203;2593](helmfile/helmfile#2593)
- feat: support HELMFILE\_NAMESPACE env var for default namespace by [@&#8203;dschmidt](https://github.com/dschmidt) in [#&#8203;2592](helmfile/helmfile#2592)
- fix: normalize dependency chart path before DirectoryExistsAt check by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2598](helmfile/helmfile#2598)
- Add jsonPatches regression coverage for chartify lookup rendering by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2586](helmfile/helmfile#2586)
- feat: add defaultInherit for automatic release template inheritance by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2600](helmfile/helmfile#2600)
- fix: restore kubedog status progress output during tracking by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2602](helmfile/helmfile#2602)
- feat: show diff preview when sync --interactive is used by [@&#8203;vomba](https://github.com/vomba) in [#&#8203;2603](helmfile/helmfile#2603)
- build(deps): bump github.com/containerd/containerd from 1.7.30 to 1.7.32 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2607](helmfile/helmfile#2607)
- feat: support HELMFILE\_\* env vars for more global flags by [@&#8203;dschmidt](https://github.com/dschmidt) in [#&#8203;2606](helmfile/helmfile#2606)

#### New Contributors

- [@&#8203;vomba](https://github.com/vomba) made their first contribution in [#&#8203;2603](helmfile/helmfile#2603)

**Full Changelog**: <helmfile/helmfile@v1.5.1...v1.5.2>

### [`v1.5.1`](https://github.com/helmfile/helmfile/releases/tag/v1.5.1)

[Compare Source](helmfile/helmfile@v1.5.0...v1.5.1)

#### What's Changed

- fix: add trackFailOnError option to control kubedog exit code by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2576](helmfile/helmfile#2576)
- docs: update helmfile skill to reflect v1.1 documentation by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2577](helmfile/helmfile#2577)
- fix(state): resolve OCI repo prefix in ad-hoc release dependencies by [@&#8203;dschmidt](https://github.com/dschmidt) in [#&#8203;2579](helmfile/helmfile#2579)
- feat(state): add mergeStrategy: fallback for first-file-wins env values by [@&#8203;dschmidt](https://github.com/dschmidt) in [#&#8203;2578](helmfile/helmfile#2578)
- Expose internal apis by [@&#8203;ceriath](https://github.com/ceriath) in [#&#8203;2520](helmfile/helmfile#2520)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.100.1 to 1.101.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2581](helmfile/helmfile#2581)
- chore: Deduplicate preparation code of sync and apply by [@&#8203;ceriath](https://github.com/ceriath) in [#&#8203;2523](helmfile/helmfile#2523)
- build(deps): bump gitpython from 3.1.47 to 3.1.49 in /docs by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2582](helmfile/helmfile#2582)
- chore: Emit "cleanup" events later to match the behavior in "apply" by [@&#8203;ceriath](https://github.com/ceriath) in [#&#8203;2522](helmfile/helmfile#2522)
- build(deps): bump golang.org/x/term from 0.42.0 to 0.43.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2584](helmfile/helmfile#2584)
- build(deps): bump gitpython from 3.1.49 to 3.1.50 in /docs by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2585](helmfile/helmfile#2585)
- fix: template helmDefaults.postRendererArgs with release data by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2583](helmfile/helmfile#2583)

**Full Changelog**: <helmfile/helmfile@v1.5.0...v1.5.1>

### [`v1.5.0`](https://github.com/helmfile/helmfile/releases/tag/v1.5.0)

[Compare Source](helmfile/helmfile@v1.4.5...v1.5.0)

#### What's Changed

- feat: add --write-output flag to helmfile fetch for air-gapped environments by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2572](helmfile/helmfile#2572)
- feat: add 'create' subcommand to scaffold helmfile deployment projects by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2574](helmfile/helmfile#2574)
- docs: restructure documentation and improve newcomer experience by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2573](helmfile/helmfile#2573)
- docs: deduplicate Technical Details sections in values-and-merging.md by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2575](helmfile/helmfile#2575)

**Full Changelog**: <helmfile/helmfile@v1.4.5...v1.5.0>

### [`v1.4.5`](https://github.com/helmfile/helmfile/releases/tag/v1.4.5)

[Compare Source](helmfile/helmfile@v1.4.4...v1.4.5)

#### What's Changed

- build(deps): bump go.opentelemetry.io/otel/sdk from 1.42.0 to 1.43.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2524](helmfile/helmfile#2524)
- chore: rename variables to match in apply and sync by [@&#8203;ceriath](https://github.com/ceriath) in [#&#8203;2521](helmfile/helmfile#2521)
- chore: bump Helm to v4.1.4 by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2525](helmfile/helmfile#2525)
- build(deps): bump golang.org/x/term from 0.41.0 to 0.42.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2529](helmfile/helmfile#2529)
- bump helm v3.20.1 to v3.20.2 by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2530](helmfile/helmfile#2530)
- build(deps): bump github.com/helmfile/vals from 0.43.8 to 0.43.9 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2533](helmfile/helmfile#2533)
- Update Go from 1.25.8 to 1.26.2 by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2535](helmfile/helmfile#2535)
- fix: boolean false overrides dropped in multi-document helmfiles ([#&#8203;2527](helmfile/helmfile#2527)) by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2532](helmfile/helmfile#2532)
- build(deps): bump github.com/helmfile/chartify from 0.26.2 to 0.26.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2538](helmfile/helmfile#2538)
- fix: add mutex lock for concurrent rewriteChartDependencies access by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2509](helmfile/helmfile#2509)
- fix: update state values files handling to replace arrays instead of merging by [@&#8203;Moglum](https://github.com/Moglum) in [#&#8203;2537](helmfile/helmfile#2537)
- fix: helmDefaults.postRendererArgs not passed to helm commands by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2510](helmfile/helmfile#2510)
- enabledns flags available on template command by [@&#8203;Diliz](https://github.com/Diliz) in [#&#8203;2511](helmfile/helmfile#2511)
- build(deps): bump k8s.io/apimachinery from 0.35.3 to 0.35.4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2540](helmfile/helmfile#2540)
- build(deps): bump k8s.io/client-go from 0.35.3 to 0.35.4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2539](helmfile/helmfile#2539)
- build(deps): bump github.com/zclconf/go-cty from 1.18.0 to 1.18.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2542](helmfile/helmfile#2542)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.14 to 1.32.15 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2543](helmfile/helmfile#2543)
- fix: eliminate race condition in rewriteChartDependencies by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2541](helmfile/helmfile#2541)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.99.0 to 1.99.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2546](helmfile/helmfile#2546)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.15 to 1.32.16 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2547](helmfile/helmfile#2547)
- build(deps): bump k8s.io/apimachinery from 0.35.4 to 0.36.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2553](helmfile/helmfile#2553)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.99.1 to 1.100.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2552](helmfile/helmfile#2552)
- Fix helmfile init failing to update outdated helm plugins with Helm v4 by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2554](helmfile/helmfile#2554)
- fix: skip subhelmfiles when selectors conflict with CLI selectors by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2545](helmfile/helmfile#2545)
- build(deps): bump gitpython from 3.1.41 to 3.1.47 in /docs by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2555](helmfile/helmfile#2555)
- fix: apply post-renderer to output-dir-template output by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2531](helmfile/helmfile#2531)
- build(deps): bump go.uber.org/zap from 1.27.1 to 1.28.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2557](helmfile/helmfile#2557)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.100.0 to 1.100.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2558](helmfile/helmfile#2558)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.16 to 1.32.17 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2559](helmfile/helmfile#2559)
- update readme add install from source by [@&#8203;Sianao](https://github.com/Sianao) in [#&#8203;2561](helmfile/helmfile#2561)
- Honor `skipSchemaValidation` during chartification when `forceNamespace` is set by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;2550](helmfile/helmfile#2550)
- build(deps): bump github.com/Masterminds/semver/v3 from 3.4.0 to 3.5.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2565](helmfile/helmfile#2565)
- fix: deduplicate chart dependencies in helmfile.lock by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2567](helmfile/helmfile#2567)
- build(deps): replace werf/kubedog-for-werf-helm with werf/kubedog by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2568](helmfile/helmfile#2568)
- build(deps): bump helmfile/vals from v0.43.9 to v0.44.0 by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2569](helmfile/helmfile#2569)
- fix: use --post-renderer-args=VALUE format to prevent Helm flag parsing failure by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2570](helmfile/helmfile#2570)

#### New Contributors

- [@&#8203;Moglum](https://github.com/Moglum) made their first contribution in [#&#8203;2537](helmfile/helmfile#2537)
- [@&#8203;Diliz](https://github.com/Diliz) made their first contribution in [#&#8203;2511](helmfile/helmfile#2511)
- [@&#8203;Sianao](https://github.com/Sianao) made their first contribution in [#&#8203;2561](helmfile/helmfile#2561)

**Full Changelog**: <helmfile/helmfile@v1.4.4...v1.4.5>

### [`v1.4.4`](https://github.com/helmfile/helmfile/releases/tag/v1.4.4)

[Compare Source](helmfile/helmfile@v1.4.3...v1.4.4)

#### What's Changed

- build(deps): bump azure/setup-helm from 4.3.1 to 5.0.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2499](helmfile/helmfile#2499)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.97.1 to 1.97.2 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2500](helmfile/helmfile#2500)
- fix: helmfile fetch fails for kustomization directories by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2504](helmfile/helmfile#2504)
- fix: keep all chart dependencies key / values by [@&#8203;champtar](https://github.com/champtar) in [#&#8203;2501](helmfile/helmfile#2501)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.97.2 to 1.97.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2505](helmfile/helmfile#2505)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.12 to 1.32.13 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2506](helmfile/helmfile#2506)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.97.3 to 1.98.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2512](helmfile/helmfile#2512)
- build(deps): bump github.com/go-jose/go-jose/v4 from 4.1.3 to 4.1.4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2513](helmfile/helmfile#2513)
- chore: update go version to 1.25.8 by [@&#8203;eadred](https://github.com/eadred) in [#&#8203;2514](helmfile/helmfile#2514)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.13 to 1.32.14 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2516](helmfile/helmfile#2516)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.98.0 to 1.99.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2517](helmfile/helmfile#2517)
- build(deps): bump github.com/helmfile/vals from 0.43.7 to 0.43.8 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2518](helmfile/helmfile#2518)

#### New Contributors

- [@&#8203;champtar](https://github.com/champtar) made their first contribution in [#&#8203;2501](helmfile/helmfile#2501)
- [@&#8203;eadred](https://github.com/eadred) made their first contribution in [#&#8203;2514](helmfile/helmfile#2514)

**Full Changelog**: <helmfile/helmfile@v1.4.3...v1.4.4>

### [`v1.4.3`](https://github.com/helmfile/helmfile/releases/tag/v1.4.3)

[Compare Source](helmfile/helmfile@v1.4.2...v1.4.3)

#### What's Changed

- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.97.0 to 1.97.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2488](helmfile/helmfile#2488)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.11 to 1.32.12 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2487](helmfile/helmfile#2487)
- Fix interactive apply asks in no change situation by [@&#8203;tarrow](https://github.com/tarrow) in [#&#8203;945](helmfile/helmfile#945)
- build(deps): bump google.golang.org/grpc from 1.79.2 to 1.79.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2490](helmfile/helmfile#2490)
- fix: helmfile list now reflects version from helmfile.lock by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2486](helmfile/helmfile#2486)
- build(deps): bump k8s.io/client-go from 0.35.2 to 0.35.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2492](helmfile/helmfile#2492)
- build(deps): bump github.com/fatih/color from 1.18.0 to 1.19.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2494](helmfile/helmfile#2494)
- fix: cleanup hooks not receiving error signal by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2475](helmfile/helmfile#2475)
- fix: pass --timeout flag through to helm for sync and apply by [@&#8203;hristiy4n](https://github.com/hristiy4n) in [#&#8203;2495](helmfile/helmfile#2495)
- fix: error on missing secret key when using vals by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2496](helmfile/helmfile#2496)
- build: update helm-diff to v3.15.3 by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2498](helmfile/helmfile#2498)
- feat: add an arg that passing description to `helm upgrade` command by [@&#8203;swimablefish](https://github.com/swimablefish) in [#&#8203;2497](helmfile/helmfile#2497)

#### New Contributors

- [@&#8203;tarrow](https://github.com/tarrow) made their first contribution in [#&#8203;945](helmfile/helmfile#945)
- [@&#8203;hristiy4n](https://github.com/hristiy4n) made their first contribution in [#&#8203;2495](helmfile/helmfile#2495)
- [@&#8203;swimablefish](https://github.com/swimablefish) made their first contribution in [#&#8203;2497](helmfile/helmfile#2497)

**Full Changelog**: <helmfile/helmfile@v1.4.2...v1.4.3>

### [`v1.4.2`](https://github.com/helmfile/helmfile/releases/tag/v1.4.2)

[Compare Source](helmfile/helmfile@v1.4.1...v1.4.2)

#### What's Changed

- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.96.2 to 1.96.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2456](helmfile/helmfile#2456)
- build(deps): bump docker/login-action from 3 to 4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2454](helmfile/helmfile#2454)
- build(deps): bump docker/setup-qemu-action from 3 to 4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2453](helmfile/helmfile#2453)
- build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.10 to 1.32.11 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2455](helmfile/helmfile#2455)
- Add CHANGELOG.md by [@&#8203;PhilipLudington](https://github.com/PhilipLudington) in [#&#8203;2457](helmfile/helmfile#2457)
- build(deps): bump docker/setup-buildx-action from 3 to 4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2462](helmfile/helmfile#2462)
- build(deps): bump markdown from 3.6 to 3.8.1 in /docs by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2463](helmfile/helmfile#2463)
- build(deps): bump docker/build-push-action from 6 to 7 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2469](helmfile/helmfile#2469)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.96.3 to 1.96.4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2470](helmfile/helmfile#2470)
- build(deps): bump docker/metadata-action from 5 to 6 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2468](helmfile/helmfile#2468)
- feat: add helm-legacy track mode for Helm v4 compatibility by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2466](helmfile/helmfile#2466)
- docs: add comprehensive values merging and data flow guide by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2461](helmfile/helmfile#2461)
- fix: nested helmfile values should replace arrays, not merge element-by-element by [@&#8203;aditmeno](https://github.com/aditmeno) in [#&#8203;2458](helmfile/helmfile#2458)
- build(deps): bump go.yaml.in/yaml/v2 from 2.4.3 to 2.4.4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2473](helmfile/helmfile#2473)
- build(deps): bump golang.org/x/sync from 0.19.0 to 0.20.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2472](helmfile/helmfile#2472)
- fix: use --force-replace flag for Helm 4 instead of deprecated --force by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2477](helmfile/helmfile#2477)
- build(deps): bump golang.org/x/term from 0.40.0 to 0.41.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2479](helmfile/helmfile#2479)
- build(deps): bump github.com/helmfile/vals from 0.43.6 to 0.43.7 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2478](helmfile/helmfile#2478)
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.96.4 to 1.97.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2484](helmfile/helmfile#2484)
- build(deps): bump Helm from v4.1.1 to v4.1.3 by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2481](helmfile/helmfile#2481)
- feat: add --force-conflicts flag support for Helm 4 by [@&#8203;yxxhero](https://github.com/yxxhero) in [#&#8203;2480](helmfile/helmfile#2480)

#### New Contributors

- [@&#8203;PhilipLudington](https://github.com/PhilipLudington) made their first contribution in [#&#8203;2457](helmfile/helmfile#2457)

**Full Changelog**: <helmfile/helmfile@v1.4.1...v1.4.2>

</details>

---

### Configuration

📅 **Schedule**: (in timezone Europe/London)

- 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 PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTUuMSIsInVwZGF0ZWRJblZlciI6IjQzLjE5NS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJ0eXBlL21pbm9yIl19-->

Reviewed-on: https://forgejo.hayden.moe/hayden/phoebe/pulls/42
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.

subhelmfiles should only be evaluated if their selectors match

5 participants