zeroclaw-labs/zeroclaw · error

Invalid issue key '{key}'. Expected format: PROJECT-123 (e.g

Error message

Invalid issue key '{key}'. Expected format: PROJECT-123 (e.g. PROJ-42, proj-42)

What it means

Local format validation of an issue key: it must be LETTERS-DIGITS where the letters part is 2+ ASCII alphabetic characters (either case) and the number part is one or more ASCII digits with no leading restrictions beyond being non-empty. Used by get_ticket, comment_ticket, fetch_transitions, transition_ticket, and create_ticket (for parent_key). It fails before any network call, catching malformed keys like 'PROJ', '-42', 'PROJ-12a', or embedded whitespace.

Source

Thrown at crates/zeroclaw-tools/src/jira_tool.rs:1314

    }
}

// ── Input validation ──────────────────────────────────────────────────────────

/// Validates that `issue_key` matches the Jira key format `PROJ-123` or `proj-123`.
/// Prevents path traversal if a crafted key like `../../other` were interpolated
/// directly into the URL.
fn validate_issue_key(key: &str) -> anyhow::Result<()> {
    let valid = key.split_once('-').is_some_and(|(project, number)| {
        !project.is_empty()
            && project.chars().all(|c| c.is_ascii_alphanumeric())
            && !number.is_empty()
            && number.chars().all(|c| c.is_ascii_digit())
    });
    if valid {
        Ok(())
    } else {
        anyhow::bail!(
            "Invalid issue key '{key}'. Expected format: PROJECT-123 (e.g. PROJ-42, proj-42)"
        )
    }
}

/// Validates that `key` matches the Jira project key format. Same character
/// class as the project portion of `validate_issue_key` so the two stay in
/// step.
fn validate_project_key(key: &str) -> anyhow::Result<()> {
    let valid = !key.is_empty() && key.chars().all(|c| c.is_ascii_alphanumeric());
    if valid {
        Ok(())
    } else {
        anyhow::bail!("Invalid project key '{key}'. Expected ASCII alphanumeric, e.g. PROJ")
    }
}

// ── Response shaping ──────────────────────────────────────────────────────────

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Supply the key in PROJECT-123 form, e.g. PROJ-42 or proj-42 (matching is case-insensitive per the message).
  2. If users paste URLs, extract the trailing /browse/PROJ-42 segment before calling the tool.
  3. Trim input and re-check the shape in your own layer (see validation code below).

Example fix

// before
jira.get_ticket("https://myco.atlassian.net/browse/PROJ-42").await?; // -> error

// after
let key = url.trim().rsplit('/').next().unwrap_or(url.trim());
jira.get_ticket(key).await?;
Defensive patterns

Strategy: validation

Validate before calling

// Mirror of the tool's check: 2+ ascii letters, '-', 1+ ascii digits
fn is_valid_issue_key(key: &str) -> bool {
    let Some((letters, digits)) = key.split_once('-') else { return false };
    letters.len() >= 2
        && letters.chars().all(|c| c.is_ascii_alphabetic())
        && !digits.is_empty()
        && digits.chars().all(|c| c.is_ascii_digit())
}
if !is_valid_issue_key(key.trim()) { anyhow::bail!("bad issue key: {key}"); }

Prevention

When it happens

Trigger: Passing a bare project key ("PROJ") instead of an issue key; passing a URL ("https://x/browse/PROJ-42"); keys with non-ASCII or punctuation; passing an issue id number ("12345") instead of the key; whitespace from untrimmed user input.

Common situations: Users pasting full Jira URLs into a CLI/agent; confusion between numeric issue id and issue key; copy-paste artifacts (trailing spaces, unicode dashes) from docs or chat messages; building keys by concatenation where the number came back empty.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/81431e15e6c3ab31. Report an issue: GitHub.