{"record":{"id":"daf9ce468b1c3f12","repo":"docker/cli","slug":"failed-to-parse-hook-template","errorCode":null,"errorMessage":"failed to parse hook template","messagePattern":"failed to parse hook template","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli-plugins/hooks/template.go","lineNumber":49,"sourceCode":"\t\t\t\"arg\":  func(_ any, i int) (string, error) { return msgContext.argValue(i) },\n\t\t}).Parse(hookTemplate)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar b bytes.Buffer\n\t\terr = tmpl.Execute(&b, msgContext)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tout = b.String()\n\t}\n\tif n := strings.Count(out, \"\\n\"); n > maxMessages {\n\t\treturn nil, fmt.Errorf(\"hook template contains too many messages (%d): maximum is %d\", n, maxMessages)\n\t}\n\treturn strings.SplitN(out, \"\\n\", maxMessages), nil\n}\n\nvar ErrHookTemplateParse = errors.New(\"failed to parse hook template\")\n\n// commandInfo provides info about the command for which the hook was invoked.\n// It is used for templated hook-messages.\ntype commandInfo struct {\n\tcmd *cobra.Command\n}\n\n// Name returns the name of the (sub)command for which the hook was invoked.\n//\n// It's used for backward-compatibility with old templates.\nfunc (c commandInfo) Name() string {\n\treturn c.command()\n}\n\n// command returns the name of the (sub)command for which the hook was invoked.\nfunc (c commandInfo) command() string {\n\tif c.cmd == nil {\n\t\treturn \"\"","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/docker/cli/blob/4f84911bfe8811e9b028e4b1fee8e7510be79387/cli-plugins/hooks/template.go#L31-L67","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Use the documented backward-compatible funcs {{command}} and {{flagValue}} (not the deprecated {{flag}}/{{arg}}) and confirm the flag name matches exactly.","If authoring the hook in a test, ensure a non-nil *cobra.Command with the expected flags registered is passed to hooks.ParseTemplate.","Run the CLI with DOCKER_CLI_HOOKS=0 or remove the offending plugin entry from config.json to confirm the template is the culprit."],"exampleFix":"// before (template references a flag that does not exist on 'docker context ls')\nTemplate: `hint: use {{flagValue \"format\"}} for JSON`\n// after — use a flag that actually exists, or drop the directive\nTemplate: `hint: run 'docker context show' to see the active context`","handlingStrategy":"validation","validationCode":"// Before installing/invoking a hook, dry-run the template against a representative command:\ncmd, _ := fakeCobraCmdWithFlags(\"context\", []string{\"ls\"}) // register the flags the template uses\nif _, err := hooks.ParseTemplate(pluginResponse.Template, cmd); err != nil {\n    log.Printf(\"hook template invalid: %v\", err)\n    // do not register this hook\n}","typeGuard":"// Detect the sentinel via errors.Is (it is wrapped with %w):\nfunc isHookTemplateParseErr(err error) bool { return errors.Is(err, hooks.ErrHookTemplateParse) }","tryCatchPattern":"// In the plugin host (already done by the manager): wrap per-plugin invocation so a bad template\n// degrades to a Debug log instead of failing the CLI:\nif _, _, err := tryInvokeHook(name, cfg); err != nil {\n    if errors.Is(err, hooks.ErrHookTemplateParse) {\n        logrus.WithError(err).WithField(\"plugin\", name).Debug(\"hook template parse failed; skipping\")\n        continue\n    }\n    return err\n}","preventionTips":["Cross-check every {{flagValue \"x\"}} against the command's actual registered flags before shipping a hook template.","Prefer {{command}} and {{argValue n}} only for args you know are present; guard arg indices against NArg().","Add a unit test that runs hooks.ParseTemplate with a realistic *cobra.Command for each hook you ship.","Version your templates with the CLI version they target; flag sets change across releases."],"tags":["cli-plugins","hooks","template","configuration"],"backgroundTag":null,"analyzedSha":"4f84911bfe8811e9b028e4b1fee8e7510be79387","analyzedAt":"2026-08-07T12:15:29.814Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}