{"record":{"id":"dbaec1f5f982c42d","repo":"vectordotdev/vector","slug":"sink-not-present","errorCode":null,"errorMessage":"Sink not present","messagePattern":"Sink not present","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/validate.rs","lineNumber":362,"sourceCode":"        return !opts.deny_warnings;\n    }\n\n    let healthchecks = topology::take_healthchecks(diff, pieces);\n    // We are running health checks in serial so it's easier for the users\n    // to parse which errors/warnings/etc. belong to which healthcheck.\n    let mut validated = true;\n    for (id, healthcheck) in healthchecks {\n        let mut failed = |error| {\n            validated = false;\n            fmt.error(error);\n        };\n\n        trace!(\"Healthcheck for {id} starting.\");\n        match tokio::spawn(healthcheck).await {\n            Ok(Ok(_)) => {\n                if config\n                    .sink(&id)\n                    .expect(\"Sink not present\")\n                    .healthcheck()\n                    .enabled\n                {\n                    fmt.success(format!(\"Health check \\\"{id}\\\"\"));\n                } else {\n                    fmt.warning(format!(\"Health check disabled for \\\"{id}\\\"\"));\n                    validated &= !opts.deny_warnings;\n                }\n            }\n            Ok(Err(e)) => failed(format!(\"Health check for \\\"{id}\\\" failed: {e}\")),\n            Err(error) if error.is_cancelled() => {\n                failed(format!(\"Health check for \\\"{id}\\\" was cancelled\"))\n            }\n            Err(_) => failed(format!(\"Health check for \\\"{id}\\\" panicked\")),\n        }\n        trace!(\"Healthcheck for {id} done.\");\n    }\n","sourceCodeStart":344,"sourceCodeEnd":380,"githubUrl":"https://github.com/vectordotdev/vector/blob/3708c39b12a93212ed8b8d7510b4cc7769cb5864/src/validate.rs#L344-L380","documentation":"During `vector validate` (or config validation at startup), healthchecks returned by topology::take_healthchecks are executed one by one; after a healthcheck future succeeds, the code looks the component back up with config.sink(&id).expect(\"Sink not present\") to read its healthcheck.enabled setting. The expect() encodes the invariant that every healthcheck id corresponds to a configured sink. If an id has no matching sink entry, this expect panics and crashes the validate run. In released Vector the invariant holds (healthchecks are built from the config's sinks), so hitting this means an internal bug or a version skew between config parsing and topology building.","triggerScenarios":"Running `vector validate` (without --skip-healthchecks) against a config, and a healthcheck id returned by take_healthchecks does not resolve via config.sink(&id). This can only happen if healthchecks were taken for non-sink components or the config object was mutated between building pieces and validating — e.g., a bug in a custom build of Vector, a patched topology module, or source-provided healthchecks not filtered out.","commonSituations":"Custom forks/embedded Vector-lib where topology::take_healthchecks is extended; running `vector validate` on a config after a partial code upgrade (binary version newer/older than expected config semantics); otherwise virtually unreachable for stock configs — normal bad configs produce 'Health check for X failed' messages instead of this panic.","solutions":["Rerun with `vector validate --skip-healthchecks` (or set healthchecks.enabled = false in the config) to bypass the panicking path and validate the rest of the config.","Check for version mismatch: make sure the `vector` binary executing validate is the version you think (vector --version) and re-run with the matching binary.","If you run a fork/patched Vector, audit topology::take_healthchecks to confirm it only yields ids that exist in config.sinks, and file an issue upstream with the config and panic backtrace.","Report the panic to the Vector maintainers with your config (redacted) and backtrace — stock builds should never hit this."],"exampleFix":"// before (src/validate.rs:360-365)\nif config.sink(&id).expect(\"Sink not present\").healthcheck().enabled {\n    fmt.success(format!(\"Health check \\\"{id}\\\"\"));\n}\n\n// after — degrade gracefully instead of panicking the validator\nmatch config.sink(&id) {\n    Some(sink) if sink.healthcheck().enabled => {\n        fmt.success(format!(\"Health check \\\"{id}\\\"\"));\n    }\n    Some(_) => {\n        fmt.warning(format!(\"Health check disabled for \\\"{id}\\\"\"));\n        validated &= !opts.deny_warnings;\n    }\n    None => failed(format!(\"Health check for \\\"{id}\\\" has no matching sink\")),\n}","handlingStrategy":"validation","validationCode":"# Avoid the panicking healthcheck path entirely when validating config files\nvector validate --skip-healthchecks /etc/vector/vector.yaml\n\n# Or disable healthchecks in the config before validating\ngrep -q '^healthchecks:' config.yaml || printf '\\nhealthchecks:\\n  enabled: false\\n' >> config.yaml","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run `vector validate --skip-healthchecks` in CI for pure config-shape checks; only run full healthchecks against throwaway environments.","Verify binary/config version alignment (vector --version vs. config features) before validating configs produced for a different Vector release.","If you embed vector-lib or fork topology code, add a unit test asserting every id from take_healthchecks exists in config.sinks."],"tags":["rust","vector","config-validation","healthcheck","panic","invariant"],"backgroundTag":"internal-invariant-panic","analyzedSha":"3708c39b12a93212ed8b8d7510b4cc7769cb5864","analyzedAt":"2026-08-20T07:02:18.786Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}