{"record":{"id":"6e8a7a8febd47c6a","repo":"argoproj/argo-workflows","slug":"400-6e8a7a","errorCode":"400","errorMessage":"%s.%s %s","messagePattern":"%s\\.%s %s","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"workflow/validate/validate.go","lineNumber":146,"sourceCode":"\t\tsubstitutions[match[1]] = placeholderGenerator.NextPlaceholder()\n\t}\n\n\t// since we don't need to resolve/evaluate here we can do just a simple replacement\n\tfor old, new := range substitutions {\n\t\trmatch, _ := regexp.Compile(`{{\\s*=\\s*` + regexp.QuoteMeta(old) + `\\s*}}`)\n\t\tmanifest = rmatch.ReplaceAllString(manifest, new)\n\t}\n\n\treturn manifest\n}\n\n// validateHooks takes an array of hooks to validate and the name of the\n// container they are in and generates an error for the first invalid hook\n// or nil if they are all valid\nfunc validateHooks(hooks wfv1.LifecycleHooks, hookBaseName string) error {\n\tfor hookName, hook := range hooks {\n\t\tif hookName != wfv1.ExitLifecycleEvent && hook.Expression == \"\" {\n\t\t\treturn errors.Errorf(errors.CodeBadRequest, \"%s.%s %s\", hookBaseName, hookName, \"Expression required\")\n\t\t}\n\t}\n\treturn nil\n}\n\n// Workflow accepts a workflow and performs validation against it.\nfunc Workflow(ctx context.Context, wftmplGetter templateresolution.WorkflowTemplateNamespacedGetter, cwftmplGetter templateresolution.ClusterWorkflowTemplateGetter, wf *wfv1.Workflow, wfDefaults *wfv1.Workflow, opts Opts) error {\n\ttctx := newTemplateValidationCtx(wf, opts)\n\n\ttmplCtx := templateresolution.NewContext(wftmplGetter, cwftmplGetter, wf, wf, logging.RequireLoggerFromContext(ctx))\n\tvar wfSpecHolder wfv1.WorkflowSpecHolder\n\tvar wfTmplRef *wfv1.TemplateRef\n\tvar err error\n\n\tif len(wf.Name) > maxCharsInObjectName {\n\t\treturn fmt.Errorf(\"workflow name %q must not be more than 63 characters long (currently %d)\", wf.Name, len(wf.Name))\n\t}\n","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/workflow/validate/validate.go#L128-L164","documentation":"validateHooks requires every lifecycle hook except the 'exit' hook to define an expression; a hook without one fails with this 400 error formatted as '<hookBaseName>.<hookName> Expression required'. Lifecycle hooks conditionally run templates based on expression results, so an empty expression is meaningless.","triggerScenarios":"Submitting or linting a Workflow whose spec.hooks (or template-level hooks) contains an entry like `hooks.foo: {template: myTmpl}` with no `expression` field, other than the special `exit` hook.","commonSituations":"Copy-pasting hook examples and deleting the expression; assuming hooks run unconditionally like steps; YAML typo such as `expressions:` instead of `expression:`; building hooks programmatically and leaving Expression unset.","solutions":["Add an `expression` to every hook, e.g. `expression: '{{workflow.status}} == Failed'`","Check for YAML key typos (`expression` vs `expressions`) that silently leave the field empty","If the hook should always run, use an expression that always evaluates true (e.g. `true`)","The special `exit` hook is exempt and may omit the expression"],"exampleFix":"// before\nhooks:\n  failed:\n    template: notify\n// after\nhooks:\n  failed:\n    expression: '{{workflow.status}} == Failed'\n    template: notify","handlingStrategy":"validation","validationCode":"func hookHasExpression(name string, h wfv1.LifecycleHook) error {\n    if name != wfv1.ExitLifecycleEvent && h.Expression == \"\" {\n        return fmt.Errorf(\"hook %q needs an expression\", name)\n    }\n    return nil\n}","typeGuard":"func isExitHook(name string) bool { return name == wfv1.ExitLifecycleEvent }","tryCatchPattern":"err := validate.Watchface... // on submit\nif err := argoClient.Create(ctx, wf); err != nil {\n    if strings.Contains(err.Error(), \"Expression required\") {\n        // fix hook definition and resubmit\n    }\n    return err\n}","preventionTips":["Always set expression on every hook except exit","Double-check YAML key spelling: expression not expressions","Use CI linting (`argo lint`) before submit","Use schema validation in editors (Argo JSON schema) to catch missing fields"],"tags":["validation","hooks","expression"],"backgroundTag":"workflow-validation-failed","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}