rtk-ai/rtk · error

ambiguous hash prefix

Error message

ambiguous hash prefix '{hash}': matches {}

What it means

RTK's recall store lets you look up a recorded command output by a hash prefix instead of the full hash. `load_by_hash` queries all rows whose hash starts with the prefix; if more than one row matches, the prefix is ambiguous and the lookup is deliberately refused so you never silently fetch the wrong output. The error lists every matching full hash so you can disambiguate by using a longer prefix or one of the exact hashes.

Solutions

  1. Re-run the recall with a longer hash prefix until it uniquely matches one entry.
  2. Use one of the full hashes printed in the error message to fetch the exact record.
  3. List the recall table (e.g. via rtk gain/history or a direct sqlite query of the recall table) to find the correct full hash before recalling.

Example fix

// before
rtk recall ab1
// error: ambiguous hash prefix 'ab1': matches ab12..., ab9f...

// after
rtk recall ab1234  # longer prefix, or the full hash
Defensive patterns

Strategy: validation

Validate before calling

// Check prefix uniqueness before recalling
let matches: Vec<String> = list_recall_hashes()
    .await?
    .into_iter()
    .filter(|h| h.starts_with(&prefix))
    .collect();
if matches.len() > 1 {
    eprintln!("ambiguous prefix {prefix}: use one of {:?}", matches);
    return Ok(());
}

Type guard

fn unique_prefix(prefix: &str, hashes: &[String]) -> Option<String> {
    let m: Vec<_> = hashes.iter().filter(|h| h.starts_with(prefix)).collect();
    match m.as_slice() {
        [one] => Some((*one).clone()),
        _ => None,
    }
}

Try / catch

match rtk_recall(&prefix) {
    Err(e) if e.to_string().contains("ambiguous hash prefix") => {
        // retry with a longer prefix or pick a full hash from the message
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling recall (via `run_recall`) with a hash argument shorter than needed to uniquely identify one row in the SQLite recall table — i.e. the prefix matches 2+ stored hashes. Tests exercise it by storing multiple entries whose hashes share a prefix and then calling load_by_hash with that shared prefix.

Common situations: Copy-pasting a truncated hash from a log or shell history; querying with a very short prefix (1-3 chars) when the store has accumulated many entries; hashing collisions across similar commands recorded in the same DB.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of rtk-ai/rtk@0924356b4c (2026-09-19). Data as JSON: /api/errors/055ccdd72714d6e9. Report an issue: GitHub.

Appendix: source

Thrown at src/core/retriever.rs:610

fn load_by_hash(conn: &Connection, hash: &str) -> Result<Option<Row>> {
    let exact = format!("SELECT {SELECT_COLS} FROM recall WHERE hash = ?1");
    if let Some(row) = conn.query_row(&exact, params![hash], map_row).optional()? {
        return Ok(Some(row));
    }
    let mut stmt = conn.prepare(
        "SELECT hash FROM recall WHERE substr(hash, 1, length(?1)) = ?1 ORDER BY hash ASC",
    )?;
    let candidates: Vec<String> = stmt
        .query_map(params![hash], |r| r.get(0))?
        .filter_map(|r| r.ok())
        .collect();
    match candidates.as_slice() {
        [] => Ok(None),
        [only] => {
            let sql = format!("SELECT {SELECT_COLS} FROM recall WHERE hash = ?1");
            Ok(conn.query_row(&sql, params![only], map_row).optional()?)
        }
        many => anyhow::bail!(
            "ambiguous hash prefix '{hash}': matches {}",
            many.join(", ")
        ),
    }
}

fn decode(row: &Row) -> Result<Vec<u8>> {
    match row.codec.as_str() {
        "gzip" => gunzip(&row.blob),
        _ => Ok(row.blob.clone()),
    }
}

pub struct RecallArgs<'a> {
    pub hash: Option<&'a str>,
    pub full: bool,
    pub from: Option<usize>,
    pub lines: Option<usize>,

View on GitHub (pinned to 0924356b4c)