Skip to content

[Detail Bug] CLI: Inferring GitHub repo from jj remote list includes '(push: ...)' annotation, producing malformed repo IDs #326

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_5f375fe3-a706-4e9a-a6f7-800f2439b3f6/bugs/bug_e23f615d-6124-4d15-b3e0-2d276daae62c

Introduced in #324 by @dbt on Aug 3, 2026

Summary

  • Context: The parse_jj_remote_list function extracts the origin remote URL from jj git remote list output when git is not available.
  • Bug: The function returns the entire line after "origin " without stripping jj's push URL annotation, causing malformed repository identifiers that pass validation and cause confusing error messages.
  • Actual vs. expected: When jj shows different fetch and push URLs (e.g., origin https://github.com/owner/repo.git (push: https://other.com/repo.git)), it should extract just the fetch URL but instead returns the entire string including the annotation.
  • Impact: Users receive confusing error messages like "Repository 'owner/repo.git (push: https:' not found. Make sure you have access to this repository." instead of a helpful "could not infer repository" error. The malformed identifier passes validation and produces garbage output.

Code with Bug

fn parse_jj_remote_list(list: &str) -> Option<String> {
    list.lines()
        .find_map(|line| line.strip_prefix("origin "))
        .map(|url| url.trim().to_string()) // <-- BUG 🔴 doesn't strip push URL annotation
}

Explanation

jj git remote list may append an annotation when fetch and push URLs differ: origin <fetch> (push: <push>). The current parser returns everything after origin , so it includes the (push: ...) suffix.

This malformed URL then flows through repo inference:

  • parse_jj_remote_list() returns https://github.com/owner/repo.git (push: https://...).
  • parse_github_remote_url() strips the https://github.com/ prefix and .git suffix, yielding owner/repo.git (push: https://...).
  • infer_repo_from_remote() returns this as the repo identifier, and downstream validation accepts it (it still has two non-empty /-separated parts), leading to a misleading "repository not found" error for a garbage repo name.

Recommended Fix

Strip the push URL annotation before returning the URL:

fn parse_jj_remote_list(list: &str) -> Option<String> {
    list.lines()
        .find_map(|line| line.strip_prefix("origin "))
        .map(|url| {
            // Strip push URL annotation if present
            let url = url.split(" (push:").next().unwrap_or(url);
            url.trim().to_string()
        })
}

Add a unit test covering annotated output:

#[test]
fn jj_remote_list_with_different_push_url() {
    assert_eq!(
        parse_jj_remote_list("origin https://github.com/owner/repo.git (push: https://other.com/repo.git)"),
        Some("https://github.com/owner/repo.git".to_string()),
    );
}

History

This bug was introduced in commit 9b815d7. The commit added jj (Jujutsu) repository support by creating a new parse_jj_remote_list() function to extract the origin remote URL from jj git remote list output. The bug slipped in because the function only stripped the "origin " prefix and trimmed whitespace, failing to account for the (push: <url>) annotation that jj appends when fetch and push URLs differ—a feature added to jj in December 2025. The author likely tested with typical jj configurations where fetch and push URLs match, missing this edge case.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions