docker/cli · error

failed to parse hook template

Error message

failed to parse hook template

What it means

ErrHookTemplateParse is a sentinel error (errors.New at cli-plugins/hooks/template.go:49) that is wrapped (via %w) by commandInfo.flagValue and commandInfo.argValue when a plugin's hook-message template references data that cannot be resolved. It fires when the template calls {{flagValue "x"}} for a flag that does not exist on the command, {{argValue n}} for an argument index that was not passed, or when the cobra command context (cmd) is nil. The text/template parse/execute step itself can also surface a wrapped error here.

Solutions

  1. Inspect the plugin's hook Response.Template string and cross-check every {{flagValue "name"}} / {{argValue n}} against the actual flags and positional args of the command that triggers the hook.
  2. Use the documented backward-compatible funcs {{command}} and {{flagValue}} (not the deprecated {{flag}}/{{arg}}) and confirm the flag name matches exactly.
  3. If authoring the hook in a test, ensure a non-nil *cobra.Command with the expected flags registered is passed to hooks.ParseTemplate.
  4. Run the CLI with DOCKER_CLI_HOOKS=0 or remove the offending plugin entry from config.json to confirm the template is the culprit.

Example fix

// before (template references a flag that does not exist on 'docker context ls')
Template: `hint: use {{flagValue "format"}} for JSON`
// after — use a flag that actually exists, or drop the directive
Template: `hint: run 'docker context show' to see the active context`
Defensive patterns

Strategy: validation

Validate before calling

// Before installing/invoking a hook, dry-run the template against a representative command:
cmd, _ := fakeCobraCmdWithFlags("context", []string{"ls"}) // register the flags the template uses
if _, err := hooks.ParseTemplate(pluginResponse.Template, cmd); err != nil {
    log.Printf("hook template invalid: %v", err)
    // do not register this hook
}

Type guard

// Detect the sentinel via errors.Is (it is wrapped with %w):
func isHookTemplateParseErr(err error) bool { return errors.Is(err, hooks.ErrHookTemplateParse) }

Try / catch

// In the plugin host (already done by the manager): wrap per-plugin invocation so a bad template
// degrades to a Debug log instead of failing the CLI:
if _, _, err := tryInvokeHook(name, cfg); err != nil {
    if errors.Is(err, hooks.ErrHookTemplateParse) {
        logrus.WithError(err).WithField("plugin", name).Debug("hook template parse failed; skipping")
        continue
    }
    return err
}

Prevention

When it happens

Trigger: Configuring a plugin hook in ~/.docker/config.json whose "hooks"/"error-hooks" value triggers a plugin that emits a Response.Template containing {{flagValue "someFlag"}} where "someFlag" is not a registered flag on the executed command, or {{argValue 3}} when fewer than 4 positional args were supplied, or invoking a hook path whose cobra command was not populated.

Common situations: Plugin authors copying a hook template from documentation for a different subcommand (flags differ between e.g. 'docker image ls' and 'docker context ls'); templates written against an older CLI version referencing flags that were renamed/removed; running the hook subcommand in a test harness that passes a nil *cobra.Command.

Understand the failure class

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/daf9ce468b1c3f12. Report an issue: GitHub.

Appendix: source

Thrown at cli-plugins/hooks/template.go:49

			"arg":  func(_ any, i int) (string, error) { return msgContext.argValue(i) },
		}).Parse(hookTemplate)
		if err != nil {
			return nil, err
		}
		var b bytes.Buffer
		err = tmpl.Execute(&b, msgContext)
		if err != nil {
			return nil, err
		}
		out = b.String()
	}
	if n := strings.Count(out, "\n"); n > maxMessages {
		return nil, fmt.Errorf("hook template contains too many messages (%d): maximum is %d", n, maxMessages)
	}
	return strings.SplitN(out, "\n", maxMessages), nil
}

var ErrHookTemplateParse = errors.New("failed to parse hook template")

// commandInfo provides info about the command for which the hook was invoked.
// It is used for templated hook-messages.
type commandInfo struct {
	cmd *cobra.Command
}

// Name returns the name of the (sub)command for which the hook was invoked.
//
// It's used for backward-compatibility with old templates.
func (c commandInfo) Name() string {
	return c.command()
}

// command returns the name of the (sub)command for which the hook was invoked.
func (c commandInfo) command() string {
	if c.cmd == nil {
		return ""

View on GitHub (pinned to 4f84911bfe)