Skip to content

Update all non-major dependencies - #75

Merged
sarumaj merged 1 commit into
mainfrom
renovate/all-minor-patch
Sep 19, 2026
Merged

sarumaj merged 1 commit into
mainfrom
renovate/all-minor-patch

Conversation

@renovate

@renovate renovate Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence Type Update
github.com/creasty/defaults v1.8.0 → v1.11.0 age confidence require minor
golang.org/x/term v0.45.0 → v0.46.0 age confidence require minor
golang.org/x/text v0.41.0 → v0.42.0 age confidence require minor
isbang/compose-action v3.1.0 → v3.2.0 age confidence action minor

Release Notes

creasty/defaults (github.com/creasty/defaults)

v1.11.0

Compare Source

v1.10.0 ended with six known issues. This release fixes three of them — data with a cycle crashed the process, a SetDefaults ran after an unmarshaler had taken the tag, and a failed default left part of itself behind — and half of a fourth: Set no longer writes to a map of slices or maps. The other two, and the rest of the fourth, stay as they are, now documented as intended. The API is unchanged, and so is go.mod.

Behavior changes come first, as before. Two of them can change what a working program does, and neither announces itself: one stops a SetDefaults call, and the other changes what Set leaves behind when it returns an error.

[BREAKING] SetDefaults is skipped behind a pointer once an unmarshaler took the tag (#​97)

The README says that when a tag is handed to UnmarshalText, SetDefaults is not called. v1.10.0 made that hold for a struct, behind a pointer or not, and listed the rest as a known issue: a pointer to any other type still got SetDefaults after its unmarshaler took the tag. It no longer does:

type Level int

func (l *Level) UnmarshalText(b []byte) error {
	n, err := strconv.Atoi(string(b))
	if err != nil {
		return err
	}
	*l = Level(n)
	return nil
}

func (l *Level) SetDefaults() { *l += 100 }

type Config struct {
	Level *Level `default:"3"` // v1.10.0: 103. v1.11.0: 3.
}
a field of that Level, left zero v1.10.0 v1.11.0
*Level default:"3" 103 3
**Level default:"3" 103 3
embedded *Level default:"3" 103 3
*Level, with UnmarshalJSON in place of UnmarshalText 103 3
*Level default:"", which no unmarshaler is offered 100 100
*Level default:"0x10", which UnmarshalText rejects and parsing by kind takes 116 116
Level default:"3" 3 3

Nothing reports the missing call. How to tell whether you are affected: look for a type that is not a struct — a named int, string or slice — with both SetDefaults and UnmarshalText or UnmarshalJSON, behind a pointer a tag allocates. If its SetDefaults adjusted what the unmarshaler produced, that adjustment is gone; make it in the unmarshaler instead. A field of the type itself never got the call, and a pointer the caller allocated is still left alone.

[BREAKING] A failed default no longer leaves part of itself behind (#​98)

When a default failed, Set returned the error but kept what it had filled on the way down. The value was then no longer zero, so a second Set could skip the tag and return nil, which v1.10.0 listed as a known issue for *scalar, *[]T and *map fields. Now each value Set found zero on the way to the failing default is put back to zero, whatever part of the default it had taken:

after Set returned the error v1.10.0 v1.11.0
*int default:"eighty" &0, and a second Set returns nil nil, and a second Set returns the error again
*[]string default:"[1]" &[], and a second Set returns nil nil, and a second Set returns the error again
a struct field default:"{\"X\": 1, \"Y\": \"two\"}" {X:1 Y:0} {X:0 Y:0}
an untagged struct field whose A took 7 before B failed {A:7 B:0} {A:0 B:0}
big.Int default:"12x", which its UnmarshalText fills partway before rejecting 12 0
[]T default:"[{}]", whose element's default fails one element nil

Only values that were zero are put back. A pointer, slice or map the caller provided is kept, and the fields of the struct passed to Set keep the defaults they took before the failure: there, A stays 7. The *uuid.UUID row in the v1.10.0 notes changes the same way, to an error with the pointer left nil.

Who is affected: code that ignores Set's error, or recovers from MustSet's panic, and then uses the value. Where it found an allocated pointer it now finds nil, so reading through it panics; where it found part of a default it finds a zero value; and a second Set returns the error again, where it could return nil.

Data with a cycle ends instead of crashing the process (#​104, #​108)

Set followed wherever the caller's data pointed, so data with a way back to itself, such as a child pointing up to its parent, was walked until the stack overflowed. That is a fatal error rather than a panic: no recover catches it, and the process exits.

type Node struct {
	Name     string `default:"node"`
	Parent   *Node
	Children []*Node
}

root := &Node{}
root.Children = []*Node{{Parent: root}}
err := defaults.Set(root)
// v1.10.0: fatal error: stack overflow
// v1.11.0: err is nil, both nodes are named "node", and the parent pointer is kept

Set now keeps track of the structs, slices and maps on the path it is walking. Where the data leads back to one of them, it goes no further, and the walk already under way finishes that value, so a struct gets no second SetDefaults from its own cycle. Down to 64 values deep the check scans the path and allocates nothing; below that it builds an index of the path and looks values up in it, so deep data costs a few allocations rather than time growing with the square of the depth (#​108). See Performance.

A walk that finished in v1.10.0 finishes the same way, with one exception: if a SetDefaults below a repeated value broke the cycle as a side effect, say by clearing the pointer back, v1.10.0 walked that value a second time, and this release does not. The check covers the current path only, so a value that two paths reach, neither through the other, is still walked on both, as described below.

Set no longer writes to a map of slices or maps (#​105)

A map value is not addressable, so Set fills a copy and stores it back under its key. It stored back every struct, slice and map value it walked, changed or not, which v1.10.0 listed as a known issue: a goroutine reading the map meanwhile raced with Set, and could crash. A slice or map copy shares its array or table with the value in the map, so storing it back never changed anything, and it no longer happens:

a map[string][]T that Set walks v1.10.0 v1.11.0
another goroutine reads the map during Set data race, which can crash with concurrent map read and map write Set only reads the map
an element's SetDefaults deletes the key its slice is under Set stores the key back the key stays deleted

The same holds for a map of maps. A struct value is still stored back, changed or not, so a map of structs still must not be read during Set; that is now documented as intended, below.

An int64 default neither parser takes reports both reasons (#​96)

An int64-kinded field is offered to time.ParseDuration and then to strconv.ParseInt, since reflection cannot tell a time.Duration from a plain int64 (#​66). When both rejected the tag, only ParseInt's error was reported, so a duration with a unit Go does not know read as bad integer syntax:

v1.10.0 v1.11.0
time.Duration default:"1d" strconv.ParseInt: parsing "1d": invalid syntax time: unknown unit "d" in duration "1d"; strconv.ParseInt: parsing "1d": invalid syntax
int64 default:"abc" strconv.ParseInt: parsing "abc": invalid syntax time: invalid duration "abc"; strconv.ParseInt: parsing "abc": invalid syntax
int default:"1d" strconv.ParseInt: parsing "1d": invalid syntax unchanged

The field X: invalid default "…": prefix is unchanged, and on a plain int64 the duration half is noise, the cost of the shared parser. A type whose own unmarshaler rejected the tag still reports only that rejection (#​90). Both errors are wrapped, so errors.As still reaches the *strconv.NumError and errors.Is still matches strconv.ErrSyntax and strconv.ErrRange, but errors.Unwrap no longer reaches it in one step: errors.Unwrap(err).(*strconv.NumError) stops matching. An empty tag is still no error, and no longer pays for an error message it throws away (#​107).

Kept as they are, and now documented

Three behaviors that look wrong were weighed and kept. The README now describes each, and a test marked // QUIRK or // BUG pins it, so changing one later means flipping that test:

  • A value reachable by more than one path is filled on each path, with a SetDefaults call on each, as v1.10.0's known issues said. #​99 filled such a value once per call, at the cost of an allocation on every Set that entered a value, and was closed in favor of #​103 and #​104. A SetDefaults that is not idempotent applies once per path, and a chain of values each shared by two pointers takes time that doubles with every link.
  • A struct held as a map value is stored back under its key, changed or not, so do not call Set while another goroutine reads a map of structs it walks. #​101 stored a struct back only when it changed, at the cost of an allocation for each struct a tag or setter touched, and was closed in favor of #​105.
  • A pointer the caller allocated to a slice, a map or a pointer is not descended into. What a caller's *[]T, *map[K]T or **T holds gets no defaults, and a default that would fail there is not reported, though a *[]T or *map[K]T held as a map value is descended into, as it has been since v1.6.0. #​100 descended into all three and was closed in favor of #​106, which pins the skip: nobody had asked for the descent, and it would bring new errors and new cycles.

#​95 pins more behavior no test covered, marking what looks wrong // QUIRK, and corrects documentation that described behavior Set does not have:

  • An integer tag is a Go integer literal, legacy octal included: default:"0644" is 420, and default:"08080" is an error. Slice and map tags are JSON, where a number rejects a leading zero and an integer map key "010" is 10.
  • A tagged field promoted from an unexported embedded struct stays empty, and a map entry under a NaN key gets no defaults.
  • {} and [] are never handed to UnmarshalJSON directly, though encoding/json may call it while parsing the other literal.
  • The README said a pointer to the zero value is preserved. A pointer to a zero scalar is, but a pointer to a struct is descended into, so the struct's zero fields still get their defaults.

API and minimum Go version: unchanged

Set, MustSet, CanUpdate, Setter and ErrInvalidType are as in v1.10.0. go.mod still says go 1.22, and CI tests 1.22, 1.26 and 1.27.

Performance

Measured with make bench-compare BASE=v1.10.0, the suite from #​102: 6 interleaved rounds at 400ms, Go 1.26.5, darwin/arm64 (M1 Max). Every difference shown is significant at p < 0.05. Allocations are unchanged on every row but those named below.

Faster: slices and maps of scalars. Set walked every element of a slice and every entry of a map whatever their type, though only a struct, pointer, slice or map element can hold a default. It now skips the rest (#​103). Tagged fields also got cheaper, since the tag is offered to a field's unmarshalers through one interface conversion rather than two (#​110):

v1.10.0 v1.11.0
a filled []int of 1000 6031 ns 55 ns
a filled map[string]int of 1000 54.1 µs, 2001 allocs 56 ns, 0 allocs
a map[string]int default parsed from its tag 937 ns, 17 allocs 798 ns, 12 allocs
Set, a struct, a pointer, a slice and a map 2.27 µs, 32 allocs 2.07 µs, 29 allocs
a struct with one field of every parsed kind 2.04 µs 1.93 µs
ten tagged *int fields 1.91 µs 1.79 µs

Set on four scalar fields is unchanged, at 547 ns against 538 ns (p=0.18).

Slower: walking values that could hold a default, by 3 to 14%. Each struct, slice and map Set enters now checks the path above it for a cycle (#​104), and each field it visits takes one more call (#​98):

v1.10.0 v1.11.0
one untagged struct field 44.5 ns 49.1 ns (+10.4%)
structs nested by value, 64 deep 54.9 µs 62.4 µs (+13.8%)
a value eight pointers share 125 µs 140 µs (+12.0%)
1000 structs holding a pointer, filled 204 µs 221 µs (+8.7%)
100 pointers in a map, filled 21.8 µs 23.2 µs (+6.2%)
a default that fails four values down 877 ns 931 ns (+6.1%)

Deep data costs a little more, and a few allocations. Down to 64 values deep the cycle check scans the path; below that it builds an index of the path, which is what keeps the cost from growing with the square of the depth (#​108). Set on an already-filled linked list of structs, one tagged field each, measured with testing.Benchmark outside the committed suite (the last row is a single call):

length v1.10.0 v1.11.0
10 1.4 µs, 0 allocs 1.9 µs, 0 allocs
100 16.7 µs, 0 allocs 24.8 µs, 3 allocs
1,000 179 µs, 0 allocs 248 µs, 7 allocs
10,000 3.07 ms, 0 allocs 4.53 ms, 15 allocs
100,000 54 ms 68 ms

A slice of pointers to structs runs in one of two modes. A filled []*T of 1000 elements takes about 187 µs in some processes and about 313 µs in others, where v1.10.0 took 183 µs in every process. A process picks a mode as it starts and keeps it for its whole run. Over twelve fresh processes each, v1.10.0 was fast in twelve, this release's #​111 was fast in twelve, and #​112 — which only stopped aliasing a struct field to a local — was fast in four. That change removes no work and alters no behavior: the cost is in the machine code the compiler generates around it. The alias stays gone by choice, since keeping a local to steer code generation would argue for hoisting the tag's other fields into locals too. A map of slices moves with it, at 30 µs against 38 µs.

Parse/unmarshaler/text reads 23% faster than on v1.10.0. That is code placement rather than work removed: the same row moved as far with dead code added to an unchanged tree (#​108).

Documentation

  • The README gains Integers, Unmarshalers, Shared and cyclic values, Maps and Errors sections, and its pointer paragraph now says which pointers Set descends into. (#​95, #​97, #​98, #​104, #​105, #​106)
  • Set's doc comment says what it fills, what it descends into, what it writes to a map and what a failure leaves behind. Setter's says SetDefaults is called once for each path to its struct, but not again from a cycle.
  • The README no longer promises a runnable MustSet example; the package example calls it. (#​95)

Internals

  • benchmark_test.go is a suite of 46 cases: whole calls, the zero check, each parse path, walks with nothing left to fill, and failures. make bench-compare BASE=<ref> runs it against another commit in interleaved rounds and compares them with benchstat, and CI runs every case once on each Go version. Every number under Performance but the deep-nesting table comes from it. (#​102, #​108)
  • Three pieces of machinery the public API reaches only through Set now live in files of their own, with their tests beside them in package defaults: the promoted-method check (method.go), the unmarshaler handoff (unmarshal.go) and the path check and its index (path.go). They are the only exception to this repository's black-box test rule, since nothing else can reach them. (#​109, #​110, #​111)
  • path.go imports unsafe to hold each value on the path as an unsafe.Pointer, which keeps that value alive so nothing allocated below it can reuse its address, and its index keys hold addresses as uintptr so that entries stay off the heap. There is no pointer arithmetic. (#​104, #​108)
  • Statement coverage stays at 100%, and make cover now measures ./... rather than the root package alone. (#​109)

Upgrading

Run your tests. If a value that a SetDefaults used to adjust comes back as its unmarshaler left it, see the first section. If code carries on after Set returns an error, expect nil and zero values where part of a default used to be. If you match the text of errors from an int64 or time.Duration field, they now name both parsers.

Full Changelog: creasty/defaults@v1.10.0...v1.11.0

v1.10.0

Compare Source

v1.9.0 ended with a list of four known issues. This release fixes three of them — #​67, #​71 and #​79 — and two more bugs, #​69 and #​89, and exports the error Set returns for an argument it cannot fill (#​70).

Behavior changes come first again. Two of them can change what a working program does. One is loud: a tag that was never applied is now an error. The other is quiet — a SetDefaults that ran only through method promotion no longer runs — so it comes first.

[BREAKING] SetDefaults runs once per value, and not through promotion (#​85, #​86)

In v1.9.0 a SetDefaults could run two or three times on the same value. A struct behind a pointer got one call as its fields were filled and another from the pointer (#​67). A method promoted from an embedded field ran once for the field and again through the struct embedding it, and once more for each further level of embedding.

A value reached by one path now gets one call, and an embedded field gets exactly the calls a named field of its type would get. For a setter that is idempotent — the usual if c.Port == 0 { c.Port = 8080 } — this first table changes nothing:

SetDefaults calls v1.9.0 v1.10.0
*T field, tagged default:"{}" or allocated by the caller 2 1
element of a []*T 2 1
**T field 2 1
embedded T 2 1
embedded T, two levels deep 3 1
embedded *T 3 1

That count is per path. A value reached by more than one path — two pointers to one struct, two slices over one array, one map held in two fields — still gets one call from each: two pointers to one struct went from 4 calls to 2, not to 1. See Known issues.

The second table is the one to read twice. These calls happened only through promotion, or after an unmarshaler had already taken the tag, and nothing reports that they are gone:

SetDefaults calls v1.9.0 v1.10.0
embedded unexported struct 1 0
embedded T tagged default:"-" 1 0
embedded interface holding a Setter 1 0
embedded T whose UnmarshalText took the tag 1 0
*T field, T a struct, whose UnmarshalText took the tag 1 0
embedded *T left nil called with a nil receiver no call
embedded interface left nil panic no call

The *T field row is not an embedding. A *T field ran SetDefaults after UnmarshalText where a T field does not; it now agrees with the T field and with the README, which says the tag is handed to UnmarshalText and SetDefaults is not called. That holds only when T is a struct: a pointer to a non-struct type with both methods, such as *Level for type Level int, still gets SetDefaults after UnmarshalText took the tag, as in v1.9.0.

How to tell whether you are affected: look for a type whose SetDefaults you rely on, embedded where it gets no call of its own. A value it used to fill now stays zero:

type base struct{ Timeout time.Duration }

func (b *base) SetDefaults() {
	if b.Timeout == 0 {
		b.Timeout = 30 * time.Second
	}
}

type Client struct {
	base // v1.9.0: Timeout is 30s. v1.10.0: Timeout is 0.
}

To keep the call, declare SetDefaults on the embedding struct and forward it:

func (c *Client) SetDefaults() { c.base.SetDefaults() }

Forward only to a field the second table says gets no call. An exported embedded struct is visited and already gets its call, so forwarding to it runs the setter twice.

Also check any setter that is not idempotent. One that appends, counts or toggles now runs once where it ran two or three times — that is the fix, but it undoes anything that compensated for the repeat.

An embedded non-struct type with a setter, such as type Level int, loses one call too, behind a pointer or not. A struct that declares its own SetDefaults next to an embedded one still gets both calls.

[BREAKING] An array or complex type whose unmarshaler rejects its tag is now an error (#​92)

v1.9.0 made an invalid default an error, but missed one path. A type's own UnmarshalText or UnmarshalJSON is offered the tag first, and when it refuses, Set falls back to parsing by kind. Arrays and complex numbers have no parsing by kind, so the refusal went nowhere: the field stayed zero and Set returned nil. For uuid.UUID, a [16]byte, that zero is a nil UUID that looks legitimate. Now:

field ID: invalid default "not-a-uuid": invalid UUID length: 10
v1.9.0 v1.10.0
uuid.UUID default:"not-a-uuid" nil UUID, no error error
*uuid.UUID default:"not-a-uuid" pointer to a nil UUID, no error error, pointer still allocated
a named complex128 whose UnmarshalText rejects the tag 0, no error error
[3]int default:"[1,2,3]", no unmarshaler ignored ignored

As with v1.9.0's change, this can stop a program at startup, and only over a tag that never applied. An array type with no unmarshaler has nothing to reject its tag, so the last row is unchanged.

A rejected tag reports the unmarshaler's reason (#​90)

When a type's own unmarshaler refused a tag and parsing by kind failed as well, Set reported the second failure — often from encoding/json, a parser the tag was never written for. It now reports the unmarshaler's:

v1.9.0 v1.10.0
time.Time default:"garbage" invalid character 'g' looking for beginning of value parsing time "garbage" as "2006-01-02T15:04:05Z07:00": cannot parse "garbage" as "2006"
slog.Level default:"bogus" strconv.ParseInt: parsing "bogus": invalid syntax slog: level string "bogus": unknown name
a struct wrapping time.Duration with UnmarshalText, default:"garbage" invalid character 'g' looking for beginning of value time: invalid duration "garbage"

The field X: invalid default "…": prefix is unchanged and the cause is still wrapped with %w, but errors.As now reaches the unmarshaler's error type — a *time.ParseError for time.Time, where it used to be a *json.SyntaxError.

Nothing that succeeded fails now. The fall-back to parsing by kind stays, so slog.Level with default:"4" — which its unmarshalers reject, since they take names only — is still WARN.

One message gets worse. When both of a type's unmarshalers reject a tag, UnmarshalText's reason is the one reported, since it was asked first. For a JSON-quoted time.Time with a bad date, default:"\"2020-13-01T00:00:00Z\"", that is a complaint about the quote, where v1.9.0 said month out of range.

A default that recurses without end is an error, not a crash (#​87)

A type that refers to itself through a field whose tag creates another of it recursed until the stack overflowed. A stack overflow is fatal rather than a panic, so no recover could catch it.

v1.9.0 v1.10.0
Next *Node default:"{}" fatal error: stack overflow field Next: default "{}" recurses without end
Children []Tree default:"[{}]" fatal error: stack overflow field Children: default "[{}]" recurses without end
Edges map[string]Graph default:"{\"a\":{}}" fatal error: stack overflow field Edges: default "{\"a\":{}}" recurses without end

The check is exact, not a depth limit: it stops where the same tag is about to fill a zero value of the same type inside itself, which is the one case that cannot end. A recursive type that does end — at a field with no tag, a tag that creates nothing, or a value already filled in — is walked as before.

A nil argument is an error, not a panic (#​84)

v1.9.0 v1.10.0
Set(nil) panic: runtime error: invalid memory address or nil pointer dereference ErrInvalidType
Set((*Config)(nil)) panic: reflect: call of reflect.Value.Type on zero Value ErrInvalidType
MustSet with either panics as Set does panics with ErrInvalidType

Code that panicked was never working, so nothing regresses. One consequence: if you ignore Set's error, a nil *Config no longer crashes inside Set, so it crashes later, wherever it is first dereferenced.


API: one addition, ErrInvalidType (#​93)

The error for an argument that is not a non-nil pointer to a struct is exported, so it can be told apart from a bad tag without matching its text:

if err := defaults.Set(v); errors.Is(err, defaults.ErrInvalidType) {
	// v is nil, not a pointer, or not a pointer to a struct
}

Its message is still not a struct pointer, so code matching the text keeps working. Test for it with errors.Is rather than ==, as its doc comment says. MustSet panics with the sentinel itself.

Set, MustSet, CanUpdate and Setter are unchanged.

Minimum Go version

go.mod moves from go 1.21 to go 1.22. The zero-value check now uses reflect.Value.IsZero (#​83), which until Go 1.22 compared a float's bits and so read -0.0 as non-zero (golang/go#61827). On 1.21 a float field holding -0.0 would have kept it instead of taking its default; on 1.22 and later it takes the default, as in v1.9.0. CI tests 1.22, 1.26 and 1.27.

Go 1.21 has been out of support since 1.23 shipped. A module still on it can stay on v1.9.0; go get of v1.10.0 raises the module's go line to 1.22.

Performance

The zero-value check uses reflect.Value.IsZero rather than reflect.DeepEqual against a fresh zero value (#​83), and Set runs it once per field. A struct with a SetDefaults now also pays for the promotion check from #​86, which outweighs that saving on a small struct. Measured with make bench plus a struct that has a setter — Go 1.26.5, darwin/arm64, medians of six interleaved runs:

v1.9.0 v1.10.0
Set, four scalar fields 711 ns, 9 allocs 544 ns, 5 allocs
Set, a struct, a pointer, a slice and a map 2707 ns, 39 allocs 2287 ns, 32 allocs
Set, two fields and a SetDefaults 384 ns, 6 allocs, 80 B 474 ns, 6 allocs, 128 B
CanUpdate, scalar 25.0 ns 3.4 ns
CanUpdate, four-field struct 98.0 ns, 1 alloc 26.4 ns, 0 allocs

Documentation

  • The README opens with a quick start instead of a 127-line transcript of example/main.go. The zero-value caveat gets its own section, and the design principles from #​61, default:"-", an install line and a pkg.go.dev badge are new. (#​82)
  • example/ is replaced by a package example on pkg.go.dev whose output go test checks; the old program's expected output had gone stale. (#​91)
  • The call for a new maintainer is gone: the project is maintained again. (#​81)
  • Setter's doc comment says when SetDefaults is called (#​86), and ErrInvalidType's how to test for it (#​93).

Internals

  • reflect lists a promoted method and a declared one alike, so #​86 tells them apart by where the method's code lives: the compiler positions the wrapper it generates for a promoted method in <autogenerated>. The Go spec does not promise that. The suite pins both directions, so if a Go release changes it, the tests fail; CI runs the gc toolchain on 1.22, 1.26 and 1.27.
  • defaults.go is split into set.go, mustset.go and canupdate.go, each beside its tests, and internal/fixture is gone. (#​82)
  • CI reports a single test-ok check for a branch rule to require, whatever Go versions the matrix holds. (#​88)
  • make bench benchmarks Set and CanUpdate. Statement coverage stays at 100%.

Known issues, unchanged by this release

  • time.Duration and a plain int64 share one parser, so default:"1" on a Duration means 1ns and an int64 accepts "1h" (#​66). It is the last of v1.9.0's four.
  • A value reached by more than one path gets SetDefaults once per path, as described above.
  • Data with a cycle, such as a parent pointer back up a tree, is walked without end until the stack overflows. Unlike the recursing tag #​87 now reports, this is still a fatal error that recover cannot catch.
  • A pointer to a non-struct type whose UnmarshalText took the tag still gets SetDefaults afterwards.
  • Set writes back every struct, slice and map value it visits in a map, changed or not, so calling it while another goroutine reads that map is a data race, and can crash with concurrent map read and map write even when there is nothing to fill.
  • When a default fails on a *scalar, *[]T or *map field, the pointer stays allocated, so a second Set on the same value returns nil and leaves the zero value behind. The *uuid.UUID row above is the same case.

Upgrading

Run your tests. If Set now fails for an array or complex type, that tag was never applied, and the message gives the unmarshaler's reason. If a value that a SetDefaults used to fill comes back zero, find the embedded type it belongs to and forward the call, as shown above. If you inspect errors from a type with its own unmarshaler, the messages and errors.As targets now follow that unmarshaler.

Thanks

To @​dima-starosud, whose review on #​64 suggested reflect.Value.IsZero — taken up in #​83.

Full Changelog: creasty/defaults@v1.9.0...v1.10.0

v1.9.0

Compare Source

The first release since v1.8.0 (August 2024), and almost entirely other people's
work: seven pull requests that had been waiting between eight months and two years,
adapted onto a rebuilt test suite.

Behavior changes come first, because there are four of them and one is loud. The
exported API is unchanged, so dependent code keeps compiling; what moved is what the
library does with tags that never worked.

[BREAKING] An invalid default value is now an error (#​59, by @​marxoffice)

A tag that failed to parse used to be discarded silently: the field kept its zero
value and Set returned nil. It now returns an error naming the field, the tag and
the cause.

field Port: invalid default "abc": strconv.ParseInt: parsing "abc": invalid syntax

This is the one to read twice. If you use the idiom from the README —

if err := defaults.Set(&cfg); err != nil {
	log.Fatal(err)
}

— then a tag that has been quietly broken for years will now stop your program at
startup. That is the point of the change, not a side effect of it.

How to tell whether you are affected: look for a tag that never actually applied.

v1.8.0 v1.9.0
int default:"abc" 0, no error error
int8 default:"999" (overflows the type) 0, no error error
int32 default:"1h" (only int64 takes durations) 0, no error error
int default:" 1 " (padded number) 0, no error error
a bad tag inside a struct reached through a pointer field swallowed error (#​68)

Errors also gained the field name and tag as a prefix, so string matching on errors
needs updating even where an error was already returned
: a malformed JSON container
tag reported unexpected end of JSON input and now reports
field V: invalid default "[1,2,3": unexpected end of JSON input. The cause is
wrapped with %w, so errors.Is and errors.As reach through to it — *strconv.NumError,
*json.SyntaxError and *json.UnmarshalTypeError are all still matchable.

[BREAKING] default:"" now allocates (#​63, by @​5p2O5pe25ouT)

An empty tag used to be indistinguishable from no tag at all. Set reads tags with
StructTag.Lookup now, so default:"" means give me this type's zero value while an
absent tag still means leave this field alone.

v1.8.0 v1.9.0
*string default:"" nil pointer to ""
[]string default:"" nil empty, non-nil slice
map[string]int default:"" nil empty, non-nil map

Untagged fields are unchanged and still come back nil. The visible consequence is
serialization: null becomes "", [] or {}, so snapshot and golden-file tests
downstream will move.

A padded duration tag now parses (#​55, by @​boskuv)

time.Duration with default:" 10s " produced 0 and now produces 10s.

The trim belongs to the duration attempt alone, which is why a padded number is now
an error rather than silently zero (above). Named duration types are covered too —
type Timeout time.Duration parses " 10s " — because the trim sits in the int64
branch instead of keying off time.Duration's exact type.

CanUpdate(nil) no longer panics (#​64, by @​lovewave02)

It used to panic with reflect: call of reflect.Value.Type on zero Value. It returns
true now. Code that panicked was never working, so nothing can regress here.


Minimum Go version

go.mod moves from go 1.14 to go 1.21. CI tests the declared floor plus every
currently supported release: 1.21, 1.26 and 1.27. testify is a test-only dependency —
consumers never build it.

No API change

Set, MustSet, CanUpdate and Setter are unchanged, and nothing was added or
removed. That is deliberate: this release is behavior and tests, not surface.

Documentation

  • The README now says what a zero value can and cannot express, and that a pointer is
    how you keep the distinction — *bool is how you let an explicit false survive
    default:"true". Six separate reports had run into this: #​60, #​49, #​37, #​31, #​30 and
    #​15. (#​51, by @​fchikwekwe)
  • encoding.TextUnmarshaler is documented as a second way to set defaults, and it takes
    precedence over defaults.Setter. (#​56, by @​llorllale)

Internals

  • The test suite was rewritten. One 771-line file built around a single ~120-field
    struct became 15 black-box files exercising only the public API, at 100% statement
    coverage with a make cover gate that fails the build below it. Today's behavior is
    pinned test by test, including the parts that look wrong — those carry a QUIRK or
    BUG comment with a link, so changing one shows up as a deliberate test diff.
    (#​65, #​72)
  • CI moved to GitHub Actions from a CircleCI config that had stopped running.
    (#​65)
  • The map element loop was flattened, and shouldInitializeField now reads only the
    field with the tag hoisted to its caller. Neither changes behavior. (#​58, by @​gitsang)

Known issues, unchanged by this release

  • time.Duration and a plain int64 share one parser, so default:"1" on a Duration
    means 1ns and an int64 accepts "1h". Not cleanly fixable — reflection cannot tell
    a named duration type from a named int64 (#​66).
  • A failing UnmarshalText or UnmarshalJSON is discarded, so the error you see can
    name the wrong parser (#​79).
  • SetDefaults is called twice on a pointer-to-struct field (#​67).
  • A self-referential type whose tag creates an element recurses until the stack
    overflows (#​71).

Upgrading

Run your tests. If Set now returns an error, that tag was not working before — the
message names the field and the value. If a nil pointer, slice or map became an empty
one, check whether anything downstream serializes it.

Thanks

To everyone whose pull request sat unreviewed and is in this release anyway:
@​lovewave02, @​5p2O5pe25ouT, @​boskuv, @​fchikwekwe, @​marxoffice, @​gitsang and
@​llorllale.

Full Changelog: creasty/defaults@v1.8.0...v1.9.0

isbang/compose-action (isbang/compose-action)

v3.2.0

Compare Source

Release Summary

Compose logs are now streamed during post-job cleanup, improving visibility into service shutdown and troubleshooting.

Internal updates include refreshed documentation, developer-experience improvements, and dependency/tooling upgrades across GitHub Actions, devcontainers, and npm development dependencies.

Breaking changes

There are no breaking changes.

What's Changed

Full Changelog: hoverkraft-tech/compose-action@v3.1.0...v3.2.0


Configuration

📅 Schedule: (UTC)

  • 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 becomes conflicted, or you tick the rebase/retry checkbox.

👻 Immortal: This PR will be recreated if closed unmerged. Get config help if that's undesired.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate

renovate Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

ℹ️ Artifact update notice

File name: go.mod

In order to perform the update(s) described in the table above, Renovate ran the go get command, which resulted in the following additional change(s):

  • 1 additional dependency was updated

Details:

Package Change
golang.org/x/sys v0.47.0 -> v0.48.0

@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch 3 times, most recently from 274e67b to ba41db1 Compare September 16, 2026 12:40
@renovate
renovate Bot force-pushed the renovate/all-minor-patch branch from ba41db1 to bfec941 Compare September 17, 2026 21:38
@sarumaj
sarumaj merged commit 9ce9585 into main Sep 19, 2026
@sarumaj
sarumaj deleted the renovate/all-minor-patch branch September 19, 2026 09:49
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.

1 participant