{"record":{"id":"2f028bfcf2fa0c54","repo":"zeroclaw-labs/zeroclaw","slug":"git-channel-provider-provider-requires-channel","errorCode":null,"errorMessage":"git channel provider `{provider}` requires channels.git.<alias>.api_base_url - the instance's API base URL including /api/v1, e.g. `https://git.example.org/api/v1` (or `https://gitea.com/api/v1` for the public Gitea service). No default host is assumed because API requests carry the access token","messagePattern":"git channel provider `(.+?)` requires channels\\.git\\.<alias>\\.api_base_url - the instance's API base URL including /api/v1, e\\.g\\. `https://git\\.example\\.org/api/v1` \\(or `https://gitea\\.com/api/v1` for the public Gitea service\\)\\. No default host is assumed because API requests carry the access token","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/git/channel.rs","lineNumber":113,"sourceCode":"                anyhow::bail!(\n                    \"git channel provider `github` requires the `provider-github` feature\"\n                );\n            }\n        }\n        provider @ (\"gitea\" | \"forgejo\") => {\n            #[cfg(feature = \"provider-gitea\")]\n            {\n                // Fail closed before any HTTP client exists: every request\n                // attaches `access_token` as a bearer credential, so guessing\n                // a default host would send the token to an endpoint the\n                // operator never named (e.g. a Forgejo PAT to gitea.com).\n                let Some(api_base_url) = cfg\n                    .api_base_url\n                    .as_deref()\n                    .map(str::trim)\n                    .filter(|s| !s.is_empty())\n                else {\n                    anyhow::bail!(\n                        \"git channel provider `{provider}` requires \\\n                         channels.git.<alias>.api_base_url - the instance's API base \\\n                         URL including /api/v1, e.g. `https://git.example.org/api/v1` \\\n                         (or `https://gitea.com/api/v1` for the public Gitea service). \\\n                         No default host is assumed because API requests carry the \\\n                         access token\"\n                    );\n                };\n                Ok(Box::new(super::providers::gitea::GiteaProvider::new(\n                    api_base_url.to_string(),\n                    cfg.access_token.clone(),\n                    cfg.proxy_url.clone(),\n                )))\n            }\n            #[cfg(not(feature = \"provider-gitea\"))]\n            {\n                anyhow::bail!(\n                    \"git channel provider `{provider}` requires the `provider-gitea` feature\"","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/git/channel.rs#L95-L131","documentation":"For the gitea and forgejo providers, build_provider requires channels.git.<alias>.api_base_url — the instance's API base URL including the /api/v1 suffix (e.g. https://git.example.org/api/v1, or https://gitea.com/api/v1 for the public service). No default host is assumed because API requests carry the access token, which is instance-scoped. A missing, empty, or whitespace-only value bails at channel construction.","triggerScenarios":"provider = \"gitea\" or \"forgejo\" with api_base_url absent, empty, or whitespace-only (the code trims before checking). Note construction only checks non-emptiness: a value without /api/v1 passes construction but fails later with API 404s.","commonSituations":"Self-hosted Gitea/Forgejo where the operator assumed a default host exists; copying the web UI URL instead of the API path; forgetting the /api/v1 suffix; token from a different instance than the configured host.","solutions":["Set api_base_url to the instance's API root including /api/v1, e.g. https://git.example.org/api/v1","For the public Gitea service use https://gitea.com/api/v1","Use the bare API base — no trailing slash, no /repos suffix","Make sure the host matches the instance the access token belongs to, since the token travels with every API request"],"exampleFix":"# before\n[channels.git.mine]\nprovider = \"forgejo\"\napi_base_url = \"\" # empty → construction bails\n\n# after\n[channels.git.mine]\nprovider = \"forgejo\"\napi_base_url = \"https://git.example.org/api/v1\"","handlingStrategy":"validation","validationCode":"// Rust — fail fast at config load\nif matches!(provider.as_str(), \"gitea\" | \"forgejo\") {\n    let base = cfg.api_base_url.as_deref().map(str::trim).unwrap_or(\"\");\n    if base.is_empty() {\n        anyhow::bail!(\n            \"api_base_url is required for {provider}; e.g. https://git.example.org/api/v1\"\n        );\n    }\n    if !base.trim_end_matches('/').ends_with(\"/api/v1\") {\n        anyhow::bail!(\"api_base_url should include the /api/v1 suffix: {base}\");\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Store the full API base including /api/v1 in config templates","Verify the host matches the instance that issued the access token"],"tags":["git","gitea","forgejo","configuration","api-base-url"],"backgroundTag":"missing-required-config","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}