atuinsh/atuin · error

failed to create client

Error message

failed to create client

What it means

`atuin store push` (with delete) constructs a sync client via `Client::new` and `.expect("failed to create client")`s on failure. This panics when the HTTP client cannot be built — most commonly an invalid sync URL in settings, bad extra headers, or TLS/network client initialization failure.

Solutions

  1. Fix `sync_url` in ~/.config/atuin/config.toml to a valid absolute URL (e.g. `https://api.atuin.sh`).
  2. Remove or correct any invalid entries in `extra_headers` in the config.
  3. Verify the server hostname resolves and TLS config is valid (`curl <sync_url>`).

Example fix

// before (config.toml)
sync_url = "api.atuin.sh"
// after
sync_url = "https://api.atuin.sh"
Defensive patterns

Strategy: validation

Validate before calling

// validate sync_url before running store push
let url = url::Url::parse(&settings.sync_url)
    .expect("sync_url in config.toml must be a valid absolute URL, e.g. https://api.atuin.sh");

Prevention

When it happens

Trigger: Running `atuin store push` when `Client::new(...)` fails at crates/atuin/src/command/client/store/push.rs:55 — e.g. `sync_url` in config is malformed/unparsable, or invalid characters in `extra_headers` prevent HTTP client construction.

Common situations: Misconfigured `sync_url` in ~/.config/atuin/config.toml (typo, missing scheme, self-signed TLS host); corporate proxies with bad header values; syncing against a self-hosted server with an invalid address.

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


AI-assisted analysis of atuinsh/atuin@900007c480 (2026-09-19). Data as JSON: /api/errors/dcb09dd325a53de0. Report an issue: GitHub.

Appendix: source

Thrown at crates/atuin/src/command/client/store/push.rs:55

impl Push {
    pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> {
        let host_id = Settings::host_id().await?;

        if self.force {
            println!("Forcing remote store overwrite!");
            println!("Clearing remote store");

            let caps = atuin_client::api_client::caps_client(settings)?;
            let client = Client::new(
                settings.sync_address.clone(),
                &settings.sync_auth_token().await?,
                settings.network_connect_timeout,
                settings.network_timeout * 10, // we may be deleting a lot of data... so up the
                // timeout
                &settings.extra_headers,
                caps,
            )
            .expect("failed to create client");

            client.delete_store().await?;
        }

        // We can actually just use the existing diff/etc to push
        // 1. Diff
        // 2. Get operations
        // 3. Filter operations by
        //  a) are they an upload op?
        //  b) are they for the host/tag we are pushing here?
        let key = paseto_v4::Key::try_load_from_path(&settings.key_path)
            .context("could not load encryption key")?;
        let session = SyncSession::builder()
            .store(store)
            .client_source(ClientSource::FromSettings {
                settings,
                caps: None,
            })

View on GitHub (pinned to 900007c480)