{"record":{"id":"532ffeef391475b4","repo":"dbt-labs/dbt-core","slug":"task-should-always-operate-on-at-least-a-single-no","errorCode":null,"errorMessage":"Task should always operate on at least a single node","messagePattern":"Task should always operate on at least a single node","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/dbt-tasks-core/src/task.rs","lineNumber":226,"sourceCode":"    ///   Such tasks MUST create similar task-like spans for each underlying node themselves.\n    ///\n    /// If `skip_reason` is provided, the default implementation applies the same shared skip\n    /// mapping used on span skip-close callbacks so `NodeEvaluated` starts with a skipped\n    /// outcome already set.\n    ///\n    /// # Panics\n    /// - If task reports zero nodes but doesn't override this method.\n    /// - If task reports has no phase but doesn't override this method.\n    fn telemetry_request(\n        &self,\n        in_dir: &Path,\n        out_dir: &Path,\n        skip_reason: Option<&SkipReason>,\n    ) -> SpanTreeRequest<FsResult<NodeStatus>, SkipReason> {\n        let mut nodes = self.dbt_nodes().into_iter();\n\n        let Some(node) = nodes.next() else {\n            unreachable!(\"Task should always operate on at least a single node\");\n        };\n\n        if nodes.next().is_some() {\n            // task for multiple nodes use current span as task span.\n            // Task inner code should create actual spans for themselves.\n            return SpanTreeRequest::use_current();\n        }\n\n        let Some(task_phase) = self.task_phase() else {\n            unreachable!(\n                \"Task that doesn't belong to a phase should override telemetry_tree method\"\n            );\n        };\n\n        let phase = task_phase.into();\n        let mut attrs = node.get_node_evaluated_event(phase, in_dir, out_dir);\n        if let Some(skip_reason) = skip_reason {\n            update_node_outcome_from_skip_reason(&mut attrs, skip_reason);","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/dbt-labs/dbt-core/blob/0267ce9170576975b76b64ce856b2e5848e96617/crates/dbt-tasks-core/src/task.rs#L208-L244","documentation":"The telemetry_request method assumes every task it serializes telemetry for operates on at least one dbt node; a task with an empty node list is a construction invariant violation, so the code panics instead of emitting an empty span-tree request. It exists to keep the span-tree telemetry contract (exactly one primary node for single-node tasks) well-defined.","triggerScenarios":"Calling telemetry_request on a task whose dbt_nodes() iterator yields no elements — i.e. the task was created/selected with zero nodes.","commonSituations":"An upstream selection filter matched nothing but the task was still instantiated; a bug in node selection (e.g. state:modified selectors returning empty while the task proceeds); running a single-node task type with a missing/misconfigured target resource.","solutions":["Fix the node-selection code so a task is never created with zero nodes, or skip telemetry_request when dbt_nodes() is empty.","Guard the caller: check that the selection produced at least one node before building the task.","Log and return a no-op SpanTreeRequest for empty tasks instead of reaching this method.","Reproduce with the same selector flags and inspect why selection yielded nothing."],"exampleFix":"// before\nlet node = nodes.next().expect(\"Task should always operate on at least a single node\");\n\n// after\nlet Some(node) = nodes.next() else { return SpanTreeRequest::use_current(); };","handlingStrategy":"validation","validationCode":"if task.dbt_nodes().count() == 0 { return; } // skip telemetry for empty tasks","typeGuard":"fn has_nodes(task: &dyn AnyTask) -> bool { task.dbt_nodes().next().is_some() }","tryCatchPattern":"let Some(node) = nodes.next() else { return SpanTreeRequest::use_current(); };","preventionTips":["Never construct a single-node task from an empty selection; bail out earlier.","Verify selectors matched resources before instantiating the task.","Handle the empty-selection case at task creation, not at telemetry time."],"tags":["telemetry","task","node-selection","unreachable","rust"],"backgroundTag":"internal-invariant-violation","analyzedSha":"0267ce9170576975b76b64ce856b2e5848e96617","analyzedAt":"2026-09-07T21:53:39.732Z","contentChangedAt":"2026-09-07T21:53:39.732Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}