astrid-runtime/astrid · error
endpoint is not an http(s) URL
Error message
endpoint is not an http(s) URL: {url} What it means
fetch_options validates that the resolved endpoint starts with http:// or https:// before making the request. Any other scheme (relative path, ftp, file, empty string) is rejected as unsafe for the operator-supplied endpoint.
Solutions
- Prefix the endpoint in the capsule config with https:// or http://
- Fix the endpoint template/value so the resolved URL is absolute with an http(s) scheme
- Validate the configured endpoint when writing capsule config
Example fix
// before endpoint = "myhost.example/v1/models" // after endpoint = "https://myhost.example/v1/models"
Defensive patterns
Strategy: validation
Validate before calling
if !(url.starts_with("http://") || url.starts_with("https://")) {
eprintln!("endpoint must be an absolute http(s) URL: {}", url);
return Ok(());
} Try / catch
match fetch_options(opts, values).await {
Err(e) if e.to_string().contains("not an http(s) URL") => prompt_free_text()?,
other => other?,
} Prevention
- Always author capsule endpoints with an explicit https:// (or http:// for local) prefix
- Validate endpoint config at capsule packaging time
- Avoid relative paths or bare hostnames in endpoint templates
When it happens
Trigger: The capsule's configured endpoint resolves to a non-http(s) string — e.g. a bare hostname, relative path like /v1/models, an ftp:// or file:// URL, or an empty endpoint.
Common situations: Misconfigured capsule endpoint missing the scheme prefix; config authored for a different tool expecting relative URLs; typos dropping the `https://` prefix.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- endpoint still contains unresolved placeholders
- a corpus produced no chunks
- a representation record must cover at least one logical…
- absent migration source has a digest
- alice
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/79c2330b70c9ae4f.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/capsule/model_discovery.rs:190
/// sent only when `bearer` resolves to a non-empty value after trimming
/// **and** the resolved fetch host matches the host of the user-configured
/// provider `base_url` (see [`should_send_bearer`]) — so a capsule cannot
/// exfiltrate the credential to an arbitrary host. The response body is
/// capped at [`MAX_RESPONSE_BYTES`].
///
/// Returns `Ok(non_empty_options)` on success, or `Err` on any failure
/// (unresolved template, network error, non-2xx, oversized body, non-JSON,
/// empty list). The caller maps `Err` to a free-text fallback.
pub(crate) async fn fetch_options(
opts: &OptionsFrom,
values: &HashMap<String, String>,
) -> anyhow::Result<Vec<String>> {
let url = resolve_template(&opts.http, values);
anyhow::ensure!(
!url.contains('{'),
"endpoint still contains unresolved placeholders: {url}"
);
anyhow::ensure!(
url.starts_with("http://") || url.starts_with("https://"),
"endpoint is not an http(s) URL: {url}"
);
let bearer = opts
.bearer
.as_ref()
.map(|b| resolve_template(b, values))
.map(|b| b.trim().to_string())
.filter(|b| !b.is_empty());
// Bind the credential to the configured provider host. A capsule's
// `http`/`bearer` are independent templates with no inherent host
// binding, so without this check a manifest could point `http` at an
// attacker host while still resolving `bearer` to the user's API key.
// The bearer is attached only when the resolved fetch host matches the
// host of the user-configured `base_url`; otherwise it is withheld (the
// request will most likely 401 and fall back to free-text — the correctView on GitHub (pinned to affd8760f4)