Troubleshooting
This guide covers common errors, their causes, and how to resolve them.
Error Types
ErrRequired
Message: fig: required field not set
Cause: A field marked required:"true" has no value from any source (secret, env, or default).
Wrapped in: FieldError with the field name.
Example:
type Config struct {
APIKey string `env:"API_KEY" required:"true"`
}
var cfg Config
err := fig.Load(&cfg)
// Error: fig: field APIKey: fig: required field not set
Solutions:
- Set the environment variable:
export API_KEY=your-key - Add a default value:
default:"fallback-key" - Provide the value via secret provider
- Remove
required:"true"if the field is optional
ErrInvalidType
Message: fig: invalid type conversion
Cause: A string value could not be parsed into the target type.
Wrapped in: FieldError with the field name.
Examples:
// "abc" is not a valid integer
type Config struct {
Port int `env:"PORT"`
}
// PORT=abc → fig: field Port: fig: invalid type conversion
// "maybe" is not a valid boolean
type Config struct {
Debug bool `env:"DEBUG"`
}
// DEBUG=maybe → fig: field Debug: fig: invalid type conversion
Solutions:
- Fix the environment variable value
- Use valid formats:
- Integers:
123,-456 - Floats:
1.5,-2.7 - Bools:
true,false,1,0,t,f - Durations:
30s,5m,1h30m
- Integers:
ErrNotStruct
Message: fig: type must be a struct
Cause: Load was called with a non-struct type.
Example:
var s string
err := fig.Load(&s)
// Error: fig: type must be a struct
Solution: Pass a pointer to a struct.
ErrSecretNotFound
Message: fig: secret not found
Cause: The secret provider could not find the requested key.
Behaviour: This error is handled gracefully—fig continues to the next source (env, then default). It only becomes a problem if the field is required and no other source provides a value.
When you see it: Typically wrapped in FieldError when a required secret-only field has no value:
type Config struct {
Password string `secret:"db/password" required:"true"`
}
// No provider, or provider doesn't have "db/password"
// Error: fig: field Password: fig: required field not set
FieldError
All field-level errors are wrapped in FieldError:
type FieldError struct {
Field string
Err error
}
Extracting field information:
err := fig.Load(&cfg)
if err != nil {
var fieldErr *fig.FieldError
if errors.As(err, &fieldErr) {
fmt.Printf("Field: %s\n", fieldErr.Field)
fmt.Printf("Cause: %v\n", fieldErr.Err)
}
}
Checking for specific errors:
if errors.Is(err, fig.ErrRequired) {
// Handle missing required field
}
if errors.Is(err, fig.ErrInvalidType) {
// Handle type conversion failure
}
Common Issues
Empty Environment Variables
Empty environment variables are treated as unset:
// APP_HOST="" (empty string)
type Config struct {
Host string `env:"APP_HOST" default:"localhost"`
}
// cfg.Host = "localhost" (falls through to default)
If you need to distinguish between "unset" and "empty string", use a pointer:
type Config struct {
Host *string `env:"APP_HOST"`
}
// APP_HOST="" → cfg.Host = ptr to ""
// APP_HOST unset → cfg.Host = nil
Unexported Fields
Unexported fields cannot be set:
type Config struct {
host string `env:"APP_HOST"` // lowercase, unexported
}
// cfg.host remains zero value
Solution: Export the field (capitalise the first letter).
Missing Nested Struct Metadata
Nested structs must be defined in a way that sentinel can scan them:
// Works: named struct type
type DatabaseConfig struct {
Host string `env:"DB_HOST"`
}
type Config struct {
Database DatabaseConfig
}
// Doesn't work: anonymous struct
type Config struct {
Database struct {
Host string `env:"DB_HOST"`
}
}
Solution: Use named struct types for nested configuration.
Tagged Nested Structs
If a nested struct field has fig tags, it's treated as a leaf, not a container:
type Config struct {
// This is treated as a single field, not recursed into
Database DatabaseConfig `env:"DATABASE_JSON"`
}
Solution: Remove tags from the parent field to enable recursion.
Provider Errors vs Not Found
Providers should return ErrSecretNotFound only when the secret doesn't exist. Other errors (network, permissions) should propagate:
func (p *MyProvider) Get(ctx context.Context, key string) (string, error) {
val, err := p.client.Get(key)
if err == client.ErrNotFound {
return "", fig.ErrSecretNotFound // fig continues to next source
}
if err != nil {
return "", err // fig fails with this error
}
return val, nil
}
If your provider returns ErrSecretNotFound for all errors, you'll mask real problems.
Debugging Strategies
Print Resolution
Add logging to understand which sources are being used:
type loggingProvider struct {
inner fig.SecretProvider
}
func (p *loggingProvider) Get(ctx context.Context, key string) (string, error) {
val, err := p.inner.Get(ctx, key)
if err != nil {
log.Printf("secret %q: not found", key)
} else {
log.Printf("secret %q: found", key)
}
return val, err
}
Inspect FieldError Chain
Walk the error chain to understand the full context:
err := fig.Load(&cfg)
if err != nil {
for e := err; e != nil; e = errors.Unwrap(e) {
fmt.Printf("%T: %v\n", e, e)
}
}
Use Struct Literals for Testing
When debugging, create a known-good config to compare against:
expected := Config{
Host: "localhost",
Port: 8080,
}
var actual Config
fig.Load(&actual)
if actual != expected {
t.Errorf("got %+v, want %+v", actual, expected)
}