caddyserver/caddy · error
%v; additionally, cleanup: %v
Error message
%v; additionally, cleanup: %v
What it means
During LoadModuleByID, if a module's Provision(ctx) fails AND the module implements CleanerUpper, Caddy calls Cleanup() to release dangling state. If that cleanup also errors, both errors are combined: '%v; additionally, cleanup: %v'. The first %v is the provisioning failure; the second is the cleanup failure. This composite is then wrapped by 'provision %s: %v'.
Source
Thrown at context.go:433
ctx.cfg.apps[id] = appModule
defer func() {
if err != nil {
ctx.cfg.failedApps[id] = err
}
}()
}
ctx.ancestry = append(ctx.ancestry, val)
if prov, ok := val.(Provisioner); ok {
err = prov.Provision(ctx)
if err != nil {
// incomplete provisioning could have left state
// dangling, so make sure it gets cleaned up
if cleanerUpper, ok := val.(CleanerUpper); ok {
err2 := cleanerUpper.Cleanup()
if err2 != nil {
err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2)
}
}
return nil, fmt.Errorf("provision %s: %v", modInfo, err)
}
}
if validator, ok := val.(Validator); ok {
err = validator.Validate()
if err != nil {
// since the module was already provisioned, make sure we clean up
if cleanerUpper, ok := val.(CleanerUpper); ok {
err2 := cleanerUpper.Cleanup()
if err2 != nil {
err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2)
}
}
return nil, fmt.Errorf("%s: invalid configuration: %v", modInfo, err)
}View on GitHub (pinned to 50e54ee279)
Solutions
- Fix the primary provisioning error (the part before '; additionally') — that is the root cause
- In your own modules, make Cleanup() idempotent and tolerant of partial provisioning so it never masks the primary error
- If the cleanup error comes from a third-party plugin, report it; upgrade the plugin in case it was fixed
- Re-run with debug logging (`caddy run --log-level debug`) to get more provisioning context
Example fix
// before (plugin Cleanup assumes Provision finished)
func (m *M) Cleanup() error { return m.conn.Close() }
// after
func (m *M) Cleanup() error {
if m.conn == nil { return nil }
return m.conn.Close()
} Defensive patterns
Strategy: try-catch
Try / catch
if _, err := ctx.LoadModuleByID(id, raw); err != nil {
// split the composite if needed: part before '; additionally' is the root cause
root := strings.SplitN(err.Error(), "; additionally", 2)[0]
log.Printf("provision failed (root cause): %s", root)
return err
} Prevention
- Write idempotent, nil-safe Cleanup() in custom modules
- Never assume Provision completed inside Cleanup
- Test the failure path: make Provision error on purpose and assert Cleanup succeeds
When it happens
Trigger: A module's Provision returns an error (bad options, unreachable dependency) and its Cleanup() also fails (e.g. closing already-closed resources, failed connection teardown), so err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2) before being returned from the provisioning path.
Common situations: Modules holding open resources (file handles, TLS conns, DNS clients) whose cleanup is unsafe when provisioning died mid-way; plugin bugs where Cleanup assumes Provision completed.
Related errors
- provision %s: %v
- --input is required
- protocol argument was not a string
- %s is invalid policy
- ErrInvalidSplitPath
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/40d7ee147afe7d99.
Report an issue: GitHub.