Hmbown/CodeWhale · error · anyhow::Error

external agy credential file {} is not a SQLite database

Error message

external agy credential file {} is not a SQLite database

What it means

The granted file was secure-opened (regular file, no symlink leaf) but its first 16 bytes do not start with the 15-byte SQLite magic `SQLite format 3`. This is a cheap format check before any SQLite parsing, so a wrong-path grant or an empty/corrupted credential store fails fast and safely.

Source

Thrown at crates/tui/src/agy_credentials.rs:98

    if grant.source() != ExternalCredentialSource::AgyCli {
        bail!(
            "Antigravity import requires an agy_cli grant, not {}",
            grant.source().as_str()
        );
    }
    let path = grant.path();
    // Secure-open the exact granted path first: regular file only, no
    // symlink/reparse-point leaf, size-capped before any SQLite parsing.
    let mut file = crate::external_credentials::open_external_regular_file(path)?;
    let mut header = [0u8; 16];
    let read = file.read(&mut header).with_context(|| {
        format!(
            "reading SQLite header of {}",
            codewhale_config::quote_os_path(path)
        )
    })?;
    if read < 16 || header[..15] != *b"SQLite format 3" {
        bail!(
            "external agy credential file {} is not a SQLite database",
            codewhale_config::quote_os_path(path)
        );
    }
    file.seek(SeekFrom::Start(0)).ok();
    let metadata = file
        .metadata()
        .with_context(|| format!("statting {}", codewhale_config::quote_os_path(path)))?;
    if metadata.len() > AGY_STATE_DB_LIMIT {
        bail!(
            "external agy credential store {} exceeds the {} byte safety limit",
            codewhale_config::quote_os_path(path),
            AGY_STATE_DB_LIMIT
        );
    }
    // Pin the file identity: SQLite reopens the path by name, so hold the
    // secure handle open across the query and prove the inode did not move.
    let pinned = file_identity(&file);

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Point the read-only grant at the real agy SQLite credential store (commonly `<profile>/state.vscdb`) and retry.
  2. Verify the file before import: `head -c 15 <path>` should print `SQLite format 3` (or `file <path>` reports SQLite 3.x).
  3. If the store is missing/truncated, launch the Antigravity/IDE client so it recreates state.vscdb, then re-authenticate.

Example fix

// before
let grant = consent_for(path_join(&profile, "state.json")); // wrong file
let token = antigravity_oauth_token_from_grant(&grant)?;

// after
let grant = consent_for(path_join(&profile, "state.vscdb")); // real SQLite store
let token = antigravity_oauth_token_from_grant(&grant)?;
Defensive patterns

Strategy: validation

Validate before calling

let mut f = std::fs::File::open(grant.path())?;
let mut magic = [0u8; 16];
use std::io::Read as _;
let n = f.read(&mut magic)?;
if n < 16 || magic[..15] != *b"SQLite format 3" {
    anyhow::bail!("granted path is not a SQLite db; check the consent entry");
}

Type guard

fn looks_like_sqlite(header: &[u8]) -> bool {
    header.len() >= 16 && &header[..15] == b"SQLite format 3"
}

Try / catch

match antigravity_oauth_token_from_grant(&grant) {
    Ok(t) => t,
    Err(e) if e.to_string().contains("is not a SQLite database") => { /* fix grant path; re-consent */ return Err(e) }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: antigravity_oauth_token_from_grant where the granted path is 0–15 bytes long, or its header differs from `SQLite format 3` — e.g. the grant points at a JSON/log/config file, a truncated state.vscdb, or a placeholder created by tooling.

Common situations: Consent config names the wrong file under the agy/IDE profile directory; the IDE stores credentials in a different file after a version change; the state.vscdb was copied incompletely or is mid-recreation after a crash.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/521beccd8a9b36a8. Report an issue: GitHub.