caddyserver/caddy · error
%w, at %s:%d import chain ['%s']
Error message
%w, at %s:%d import chain ['%s']
What it means
WrapErr decorates any Caddyfile parse error with the file and line of the current token; this variant fires when the token came from an imported file (via import), appending the full import chain so you can trace which snippet imported the failing line.
Source
Thrown at caddyconfig/caddyfile/dispenser.go:445
// the end of the input when searching for the next token.
func (d *Dispenser) EOFErr() error {
return d.Errf("unexpected EOF")
}
// Err generates a custom parse-time error with a message of msg.
func (d *Dispenser) Err(msg string) error {
return d.WrapErr(errors.New(msg))
}
// Errf is like Err, but for formatted error messages
func (d *Dispenser) Errf(format string, args ...any) error {
return d.WrapErr(fmt.Errorf(format, args...))
}
// WrapErr takes an existing error and adds the Caddyfile file and line number.
func (d *Dispenser) WrapErr(err error) error {
if len(d.Token().imports) > 0 {
return fmt.Errorf("%w, at %s:%d import chain ['%s']", err, d.File(), d.Line(), strings.Join(d.Token().imports, "','"))
}
return fmt.Errorf("%w, at %s:%d", err, d.File(), d.Line())
}
// Delete deletes the current token and returns the updated slice
// of tokens. The cursor is not advanced to the next token.
// Because deletion modifies the underlying slice, this method
// should only be called if you have access to the original slice
// of tokens and/or are using the slice of tokens outside this
// Dispenser instance. If you do not re-assign the slice with the
// return value of this method, inconsistencies in the token
// array will become apparent (or worse, hide from you like they
// did me for 3 and a half freaking hours late one night).
func (d *Dispenser) Delete() []Token {
if d.cursor >= 0 && d.cursor <= len(d.tokens)-1 {
d.tokens = append(d.tokens[:d.cursor], d.tokens[d.cursor+1:]...)
d.cursor--
}View on GitHub (pinned to 50e54ee279)
Solutions
- Open the file:line in the message; the import chain after 'import chain' lists the hops from the main Caddyfile
- Fix the underlying error described before the ', at file:line' part
- If the chain is confusing, temporarily inline the imported file to get a flat error
- Check for stale imported files that reference directives removed in your Caddy version
Example fix
# before: snippets/site.caddy has a typo root * /vr-srv # inside import snippets/*.caddy # after root * /var/www
Defensive patterns
Strategy: try-catch
Try / catch
if err := serverType.Setup(serverBlocks); err != nil {
var line, file string
if _, e := fmt.Sscanf(err.Error(), "%s at %s", &line, &file); e == nil {
// surface file/line and import chain to the user in CI output
}
} Prevention
- Run `caddy adapt` in CI to catch snippet errors before deploy
- Keep import trees shallow; prefer one level of snippets
- Version-control imported snippet files alongside the main Caddyfile
When it happens
Trigger: Any Caddyfile error constructed with d.Err/d.Errf/d.WrapErr where the current token has a non-empty imports list — i.e. the error is inside a file or snippet reached through one or more import statements.
Common situations: A syntax error or unrecognized directive inside an imported snippet (e.g. site snippets in a separate file pulled in with import blocks/*.caddy). The import chain names each hop, so files deep in an import tree are debuggable.
Related errors
- %w, at %s:%d
- a cycle of imports exists between %s and %s
- cannot use duplicate server name '%s'
- invalid action type
- incomplete heredoc <<%s on line #%d, expected ending marker
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/7ffd6529bd38364e.
Report an issue: GitHub.