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

  1. Fix the primary provisioning error (the part before '; additionally') — that is the root cause
  2. In your own modules, make Cleanup() idempotent and tolerant of partial provisioning so it never masks the primary error
  3. If the cleanup error comes from a third-party plugin, report it; upgrade the plugin in case it was fixed
  4. 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

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


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/40d7ee147afe7d99. Report an issue: GitHub.