gitbutlerapp/gitbutler · error

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

Error message

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

What it means

ProjectHandleOrLegacyProjectId::from_str parses a project_id string into a ProjectHandle, and (when the 'legacy' feature is enabled) also accepts a legacy numeric LegacyProjectId. This error is thrown when the string is neither a valid ProjectHandle nor a valid legacy ProjectId, i.e. the whole parse chain failed.

Solutions

  1. Verify the string is a valid ProjectHandle (correct format/characters)
  2. If using legacy numeric IDs, ensure the value is numeric and the 'legacy' feature is enabled
  3. Confirm the project exists; obtain a fresh project_id via the project listing API

Example fix

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

Strategy: validation

Validate before calling

fn parse_project_id(raw: &str) -> anyhow::Result<ProjectHandleOrLegacyProjectId> {
    anyhow::ensure!(!raw.trim().is_empty(), "project_id must not be empty");
    raw.parse::<ProjectHandleOrLegacyProjectId>().map_err(Into::into)
}

Type guard

fn is_legacy_id(s: &str) -> bool { s.parse::<u64>().is_ok() }

Try / catch

match raw.parse::<ProjectHandleOrLegacyProjectId>() {
    Ok(id) => id,
    Err(e) => return Err(anyhow::anyhow!("bad project_id '{raw}': {e}")),
}

Prevention

When it happens

Trigger: Deserializing or parsing a project_id with a string that is neither a valid handle nor a numeric legacy id — e.g. an empty string, a malformed handle, or a non-numeric legacy id like 'abc123' when feature "legacy" is compiled in.

Common situations: Users passing a human-readable project name containing invalid characters, stale IDs from an older data format, or API callers sending arbitrary strings as project_id.

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/01fca6e49b67a763. Report an issue: GitHub.

Appendix: 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 58e5313667)