zitadel/zitadel · error
unable to read steps: %w
Error message
unable to read steps: %w
What it means
This error wraps a failure from the mapstructure/viper decoding step in cmd/setup/config.go's readSteps, which unmarshals the YAML config's Steps section into the migration Steps struct. ZITADEL throws it when the setup steps configuration cannot be decoded into the strongly-typed steps object. It indicates the setup command's config for migration steps is malformed or has incompatible types.
Source
Thrown at cmd/setup/config.go:230
v.SetConfigFile(file)
err := v.MergeInConfig()
logging.OnError(ctx, err).Warn("unable to read setup file", "file", file)
}
steps := new(Steps)
err = v.Unmarshal(steps,
viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(
hook.Base64ToBytesHookFunc(),
hook.TagToLanguageHookFunc(),
hook.StringToURLHookFunc(),
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToTimeHookFunc(time.RFC3339),
mapstructure.StringToSliceHookFunc(","),
mapstructure.TextUnmarshallerHookFunc(),
)),
)
if err != nil {
return nil, fmt.Errorf("unable to read steps: %w", err)
}
return steps, nil
}
View on GitHub (pinned to 13948f2bcd)
Solutions
- Check the wrapped error (%w) for the exact mapstructure field that failed to decode.
- Fix the type of the offending field in your config YAML / env override to match the Steps struct in cmd/setup/config.go.
- Ensure timestamp-like step fields use valid RFC3339 values and list fields are comma-separated strings.
- If running an upgraded version, remove stale step keys from your custom config and rely on defaults.
Example fix
// before (config.yaml) Steps: FirstInstancePat: 12345 # wrong type: string expected // after Steps: FirstInstancePat: "pat12345"
Defensive patterns
Strategy: validation
Validate before calling
// Validate steps config fields' types before running setup:
// e.g. ensure string fields are strings and time fields parse as RFC3339
if _, err := time.Parse(time.RFC3339, cfg.Steps.SomeTimeField); err != nil {
return fmt.Errorf("invalid Steps.SomeTimeField: %w", err)
} Try / catch
if _, err := readSteps(v); err != nil {
var decodeErr *mapstructure.Error
if errors.As(err, &decodeErr) { /* inspect failing fields */ }
return err
} Prevention
- Keep custom step overrides minimal; rely on defaults.yaml.
- Run config through a YAML linter before deployment.
- After upgrading ZITADEL, diff your Steps config against the new defaults.yaml.
When it happens
Trigger: Running `zitadel setup` (or start with steps config) when config.ReadConfig/decoder fails: a YAML key in the Steps section has the wrong type (e.g. string where int expected), an unsupported TextUnmarshaller target value, or the underlying config read returns an error.
Common situations: Hand-edited defaults.yaml/config.yaml with a typo in a steps field type; passing --steps or step config via env vars with values that fail mapstructure hooks (invalid RFC3339 timestamps, wrong comma-separated list); upgrading ZITADEL and an old steps key no longer matches the struct.
Related errors
- %s: VacuumThreshold and AnalyzeThreshold must be greater tha
- invalid tlsMode: %w
- unable to bind "init-projections" flag: %w
- unable to bind "for-mirror" flag: %w
- no master key provided: %w
AI-assisted analysis of zitadel/zitadel@13948f2bcd (2026-09-06).
Data as JSON: /api/errors/6a8910b4eb5aa75b.
Report an issue: GitHub.