gitbutlerapp/gitbutler · error

Expected `project_id` to be a ProjectHandle, got

Error message

Expected `project_id` to be a ProjectHandle, got '{value}'

What it means

This is the non-legacy variant of the ProjectHandleOrLegacyProjectId parse failure. When the crate is built without the 'legacy' feature, only a ProjectHandle is accepted; this error is thrown for any string that fails ProjectHandle parsing. The legacy ProjectId path does not exist in this build.

Solutions

  1. Send a valid ProjectHandle string instead of a legacy numeric ProjectId
  2. Rebuild/reconfigure with the 'legacy' feature enabled if legacy IDs must be accepted
  3. Update callers to the current API that returns project handles

Example fix

// before
let id = "42".parse::<ProjectHandleOrLegacyProjectId>()?;
// after
let id = "my-project-handle".parse::<ProjectHandleOrLegacyProjectId>()?;
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_project_handle(raw: &str) -> anyhow::Result<ProjectHandleOrLegacyProjectId> {
    anyhow::ensure!(raw.parse::<u64>().is_err(), "legacy numeric project ids are not supported; use a project handle");
    raw.parse::<ProjectHandleOrLegacyProjectId>().map_err(Into::into)
}

Try / catch

let id = raw.parse::<ProjectHandleOrLegacyProjectId>()
    .with_context(|| format!("expected project handle, got '{raw}'"))?;

Prevention

When it happens

Trigger: Parsing/deserializing a project_id (e.g. from JSON) that is not a valid ProjectHandle while the crate is compiled with default features (no 'legacy'), such as a bare numeric id like '42'.

Common situations: Clients still sending legacy numeric project IDs after a migration to handles; payloads serialized by an older client version against a newer server.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/49c77a6e04c36dd0. Report an issue: GitHub.

Appendix: source

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

}

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)
    }
}

impl serde::Serialize for ProjectHandleOrLegacyProjectId {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where

View on GitHub (pinned to 58e5313667)