{"record":{"id":"62327730a197ef21","repo":"hashicorp/nomad","slug":"exposed-service-check-s-s-s-s-is-not-a-task","errorCode":null,"errorMessage":"exposed service check %s[%s]->%s->%s is not a task-group service","messagePattern":"exposed service check (.+?)\\[(.+?)\\]->(.+?)->(.+?) is not a task-group service","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/job_endpoint_hook_expose_check.go","lineNumber":122,"sourceCode":"func tgValidateUseOfCheckExpose(tg *structs.TaskGroup) error {\n\t// validation for group services (which must use built-in connect proxy)\n\tfor _, s := range tg.Services {\n\t\tfor _, check := range s.Checks {\n\t\t\tif check.Expose && !s.Connect.HasSidecar() {\n\t\t\t\treturn fmt.Errorf(\n\t\t\t\t\t\"exposed service check %s->%s->%s requires use of sidecar_proxy\",\n\t\t\t\t\ttg.Name, s.Name, check.Name,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t// validation for task services (which must not be configured to use Expose)\n\tfor _, t := range tg.Tasks {\n\t\tfor _, s := range t.Services {\n\t\t\tfor _, check := range s.Checks {\n\t\t\t\tif check.Expose {\n\t\t\t\t\treturn fmt.Errorf(\n\t\t\t\t\t\t\"exposed service check %s[%s]->%s->%s is not a task-group service\",\n\t\t\t\t\t\ttg.Name, t.Name, s.Name, check.Name,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// tgValidateExposeNetworkMode ensures there is exactly 1 network configured for\n// the task group, and that it uses \"bridge\" or \"cni/*\" mode (i.e. enables network\n// namespaces).\nfunc tgValidateExposeNetworkMode(tg *structs.TaskGroup) error {\n\tif tgUsesExposeCheck(tg) {\n\t\treturn groupConnectNetworkModeValidate(tg, \"connect expose check\", false)\n\t}\n\treturn nil","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/job_endpoint_hook_expose_check.go#L104-L140","documentation":"Nomad only allows exposed (expose=true) service checks on task-group-level services backed by a Consul Connect sidecar. A check defined on a service inside a task's service block can never be exposed, because expose paths are wired into the group's Envoy proxy namespace. Validation fails fast with the group, task, service, and check names.","triggerScenarios":"Submitting a job where any task-level service (inside task { service { ... } }) declares a check with expose = true, regardless of connect configuration.","commonSituations":"Developers moving expose-enabled checks from group services into task services during refactoring; mistaking task services for group services in HCL; templated jobs that hoist checks into task blocks.","solutions":["Move the service (and its exposed check) out of the task and up to the task group level, adding connect { sidecar_service {} } if needed.","Remove expose = true from the task-service check and use a normal check with a port instead.","Split the app so the exposed dependency is registered as a group service."],"exampleFix":"// before\ntask \"web\" {\n  service {\n    name = \"web\"\n    check { expose = true }\n  }\n}\n// after\ngroup \"app\" {\n  service {\n    name = \"web\"\n    connect { sidecar_service {} }\n    check { expose = true }\n  }\n  task \"web\" { ... }\n}","handlingStrategy":"validation","validationCode":"// Ensure no task-level service declares an exposed check\njob.TaskGroups.each { tg ->\n  tg.Tasks.each { t ->\n    (t.Services || []).each { s ->\n      (s.Checks || []).each { c ->\n        if c.Expose) throw new Error(`exposed check '${c.Name}' found in task '${t.Name}' service '${s.Name}'; move service to group level`)\n      }\n    }\n  }\n}","typeGuard":"func isGroupService(tg *api.TaskGroup, svc *api.Service) bool {\n\tfor _, s := range tg.Services {\n\t\tif s.Name == svc.Name {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}","tryCatchPattern":"// golang\nif _, err := client.Jobs().Validate(job); err != nil {\n\tif strings.Contains(err.Error(), \"is not a task-group service\") {\n\t\t// relocate service block to group level or drop expose\n\t}\n}","preventionTips":["Put all Connect-enabled services at the task group level, not inside tasks.","Search task stanzas for expose = true before submitting.","Use nomad job validate in CI to catch placement mistakes.","Document that expose applies only to group services in team job templates."],"tags":["nomad","consul-connect","job-validation","expose-check"],"backgroundTag":"expose-check-requires-sidecar","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}