gitbutlerapp/gitbutler · error

Expected `project_id` to be either a ProjectHandle or a lega

Error message

Expected `project_id` to be either a ProjectHandle or a legacy ProjectId, got '{value}'

What it means

`ProjectHandleOrLegacyProjectId::from_str` (built with the `legacy` feature) tries to parse the string first as a `ProjectHandle`, then as a numeric `LegacyProjectId`, and throws this when both fail. It is the API-boundary type that accepts either the new handle format or the old numeric project id when routing requests like `project_id` in URLs/JSON. The message names the offending value.

Source

Thrown at crates/but-project-handle/src/project_handle.rs:157

    {
        let value = <String as serde::Deserialize>::deserialize(deserializer)?;
        value.parse().map_err(serde::de::Error::custom)
    }
}

impl std::str::FromStr for ProjectHandleOrLegacyProjectId {
    type Err = anyhow::Error;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        if let Ok(handle) = value.parse::<ProjectHandle>() {
            return Ok(Self::ProjectHandle(handle));
        }
        #[cfg(feature = "legacy")]
        if let Ok(project_id) = value.parse::<LegacyProjectId>() {
            return Ok(Self::LegacyProjectId(project_id));
        }
        #[cfg(feature = "legacy")]
        return Err(anyhow::anyhow!(
            "Expected `project_id` to be either a ProjectHandle or a legacy ProjectId, got '{value}'"
        ));
        #[cfg(not(feature = "legacy"))]
        return Err(anyhow::anyhow!(
            "Expected `project_id` to be a ProjectHandle, got '{value}'"
        ));
    }
}

impl<'de> serde::Deserialize<'de> for ProjectHandleOrLegacyProjectId {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let value = <String as serde::Deserialize>::deserialize(deserializer)?;
        value.parse().map_err(serde::de::Error::custom)
    }
}

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Send a valid ProjectHandle string (as produced by current GitButler) or a plain numeric id like "42" when the legacy feature is enabled
  2. Log the exact offending value and fix the client/URL that produced it
  3. If the value should have been numeric, strip whitespace/quotes before parsing

Example fix

// before
let id: ProjectHandleOrLegacyProjectId = "project-#1".parse()?; // Err

// after
let id: ProjectHandleOrLegacyProjectId = "01J9Z8Q9H4V2A5B6C7D8E9F0G1".parse()?; // handle
// or, with the legacy feature:
let id: ProjectHandleOrLegacyProjectId = "42".parse()?; // legacy numeric id
Defensive patterns

Strategy: validation

Validate before calling

let looks_like_handle = !value.is_empty() && value.chars().all(|c| c.is_ascii_alphanumeric());
let looks_like_legacy = value.chars().all(|c| c.is_ascii_digit()) && !value.is_empty();
anyhow::ensure!(looks_like_handle || looks_like_legacy, "bad project_id: {value}");

Type guard

fn accepts_project_id(value: &str) -> bool {
    value.parse::<ProjectHandle>().is_ok()
        || value.chars().all(|c| c.is_ascii_digit()) && !value.is_empty()
}

Try / catch

match value.parse::<ProjectHandleOrLegacyProjectId>() {
    Ok(id) => id,
    Err(e) => return Err(e.context(format!("project_id {value:?} is neither a handle nor a numeric id"))),
}

Prevention

When it happens

Trigger: Passing a project_id that is neither a valid ProjectHandle string nor a plain integer, e.g. "abc", "#12", "12.0", or ""; also numeric strings with whitespace like " 12" when the legacy parse is strict.

Common situations: Stale URLs or stored references from before the handle migration that were hand-edited; clients sending the wrong field or quoting numbers as floats; JSON payloads where project_id is null-coerced to an empty string.

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 gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/01fca6e49b67a763. Report an issue: GitHub.