{"record":{"id":"151280bca7768944","repo":"windmill-labs/windmill","slug":"webhook-key-e","errorCode":null,"errorMessage":"{webhook_key}: {e}","messagePattern":"\\{webhook_key\\}: \\{e\\}","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/windmill-common/src/instance_config.rs","lineNumber":1315,"sourceCode":"/// workspaces listed in instance settings and re-saves them.\n///\n/// AUTHORIZATION: replaces instance-wide settings and takes no authed context, so\n/// callers MUST have established superadmin or equivalent system authority (the CLI\n/// and the operator both run with direct instance credentials).\npub async fn sync_global_settings_declarative(\n    db: &sqlx::Pool<sqlx::Postgres>,\n    current: &BTreeMap<String, serde_json::Value>,\n    desired: &BTreeMap<String, serde_json::Value>,\n) -> anyhow::Result<()> {\n    let webhook_key = crate::global_settings::GITHUB_APP_WEBHOOK_BASE_URL_SETTING;\n    // Non-string shapes are rejected rather than ignored: `as_str()` alone would let a\n    // bool/number/object through as if the key were absent, and the diff below would\n    // then persist it — where the HTTP path answers \"must be a URL string\".\n    match desired.get(webhook_key) {\n        None | Some(serde_json::Value::Null) => {}\n        Some(serde_json::Value::String(s)) if s.trim().is_empty() => {}\n        Some(serde_json::Value::String(s)) => crate::global_settings::validate_webhook_base_url(s)\n            .map_err(|e| anyhow::anyhow!(\"{webhook_key}: {e}\"))?,\n        // Names the JSON kind rather than printing it: this is the last message on\n        // this path that could report submitted content, and an object or array could\n        // carry a secret into `sync-config` output and operator logs.\n        Some(other) => {\n            let kind = match other {\n                serde_json::Value::Bool(_) => \"a boolean\",\n                serde_json::Value::Number(_) => \"a number\",\n                serde_json::Value::Array(_) => \"an array\",\n                serde_json::Value::Object(_) => \"an object\",\n                _ => \"a non-string value\",\n            };\n            return Err(anyhow::anyhow!(\n                \"{webhook_key} must be a URL string, got {kind}\"\n            ));\n        }\n    }\n\n    let diff = diff_global_settings(current, desired, ApplyMode::Replace);","sourceCodeStart":1297,"sourceCodeEnd":1333,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-common/src/instance_config.rs#L1297-L1333","documentation":"sync_global_settings_declarative validates the github_app_webhook_base_url global setting before applying a declarative settings diff. When the desired value is a non-empty string that fails validate_webhook_base_url, the underlying validation error is re-wrapped prefixed with the setting key. This exists so CLI sync-config and the Kubernetes operator reject the same values the HTTP API would reject.","triggerScenarios":"A declarative config (settings YAML/ConfigMap for sync-config or the operator) sets github_app_webhook_base_url to a string that is not a valid URL — missing scheme, not absolute, or otherwise rejected by validate_webhook_base_url.","commonSituations":"Writing windmill.example.com/webhook without https://, typos like htps://, config mistakes in a Helm/K8s values file, copying a relative path instead of a full URL.","solutions":["Prefix the value with its scheme: use https://host/path (or http only for local testing)","Validate locally: the URL must parse as an absolute http(s) URL","Fix the value in the source config (CLI -c file, ConfigMap, or values.yaml) and re-run sync","If the error text after the prefix is unclear, check validate_webhook_base_url in global_settings for the exact rule"],"exampleFix":"// before (config)\ngithub_app_webhook_base_url: windmill.example.com/api/webhooks\n// after\ngithub_app_webhook_base_url: https://windmill.example.com/api/webhooks","handlingStrategy":"validation","validationCode":"fn is_valid_webhook_base_url(s: &str) -> bool {\n    s.starts_with(\"https://\") || s.starts_with(\"http://\")\n}\n// in config before sync:\nif let Some(v) = desired.get(\"github_app_webhook_base_url\") {\n    let s = v.as_str().expect(\"webhook base url must be a string\");\n    assert!(is_valid_webhook_base_url(s), \"github_app_webhook_base_url must be an absolute http(s) URL, got {s:?}\");\n}","typeGuard":"fn as_url_string(v: &serde_json::Value) -> Option<&str> {\n    v.as_str().filter(|s| !s.trim().is_empty())\n}","tryCatchPattern":"match sync_global_settings_declarative(&db, &current, &desired).await {\n    Err(e) if e.to_string().starts_with(\"github_app_webhook_base_url:\") =>\n        anyhow::bail!(\"config rejected: fix github_app_webhook_base_url in your declarative settings — {}\", e),\n    other => other,\n}","preventionTips":["Always write webhook URLs with explicit scheme in config files","Validate declarative settings locally before running sync-config against production","Keep declarative config and HTTP-layer validation rules in sync when editing global settings"],"tags":["validation","config","webhook","settings"],"backgroundTag":"invalid-url-config","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}