Skip to content

codeowners-rs generates nondeterministic CODEOWNERS ordering due to invalid comparator in compare_lines #105

@garrettblehm

Description

@garrettblehm

Summary

code_ownership/codeowners-rs can generate different CODEOWNERS line order on different platforms for the same input. In our case, macOS and Linux produced different orderings for two adjacent rules, causing validation to fail even though ownership content was otherwise the same.

The likely root cause is that the custom comparator used during generation is not a valid total ordering.

Affected code

Observed behavior

Two rules with the same owner can swap order between environments. For example, one environment emits:

/foo/**/*bar* @org/example-team
/foo/**/baz/**/* @org/example-team

Another emits:

/foo/**/baz/**/* @org/example-team
/foo/**/*bar* @org/example-team

This causes CODEOWNERS out of date validation failures.

Root cause

The comparator in file_generator.rs is:

pub fn compare_lines(a: &String, b: &String) -> Ordering {
    if let Some((prefix, _)) = a.split_once("**")
        && b.starts_with(prefix)
    {
        return Ordering::Less;
    }
    if let Some((prefix, _)) = b.split_once("**")
        && a.starts_with(prefix)
    {
        return Ordering::Greater;
    }
    a.cmp(b)
}

For the two example lines above:

  • both split on the first "**" to the same prefix: "/foo/"
  • compare_lines(a, b) returns Less
  • compare_lines(b, a) also returns Less

That violates comparator requirements. Once ordering is inconsistent, sort output becomes unstable/nondeterministic across platforms or builds.

Why this is a bug

A sort comparator must define a consistent total order. In particular, if a < b, then b < a must not also be true. This comparator can produce contradictory results for overlapping glob prefixes.

Suggested fix

Replace compare_lines with a comparator that guarantees a total order. Options:

  1. Remove the custom prefix logic and use plain lexical sort if acceptable.
  2. Compute an explicit sort key and compare tuples.
  3. If special glob-priority behavior is needed, define it with a consistent key, for example:
    • path specificity
    • wildcard count
    • path length
    • lexical fallback

But whatever rule is chosen, it needs to be deterministic and symmetric.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions