Skip to content

A git timeout or failure is reported as "no-merge-base" or 409 "unknown-base" (and counted in unknown_base) instead of as a timeout or server error #69

Open

Description

@matt-edmondson

What's wrong

MirrorQueries collapses every git failure into a negative answer:

  • ContainsCommitAsync (GitBranchStateCache/Mirrors/MirrorQueries.cs:47-52) returns result.Succeeded. A timed-out or crashed cat-file -e therefore reads as "commit not in mirror".
  • FindMergeBaseAsync (MirrorQueries.cs:72-78) returns null on any non-success. That includes TimedOut and exit 128 from a corrupt or locked repository.

BranchStateHandler then reports these to the client as data facts:

  • :182-194: a false ContainsCommitAsync becomes 409 unknown-base and increments the unknown_base metric.
  • :239-244: a null merge base becomes a branch entry with error: "no-merge-base".

The diff path already distinguishes these cases through DiffOutcome.Timeout. The commit check and merge-base queries don't.

Failure scenario

Reproduced at HEAD cbd20fc by adding a temporary timeout flag to ScriptedGit and two integration tests:

  • merge-base times out (a large repo going over DiffTimeout):
    200 {"branches":[{"name":"origin/main",...,"mergeBase":null,"error":"no-merge-base"}],"partial":true}
  • cat-file -e times out (over ProbeTimeout, or a corrupt mirror):
    409 {"error":"unknown-base","message":"The mirror does not hold that commit..."}

The client is told its histories are unrelated, or that its base commit was never pushed. A client following the documented contract might act on that, for example by pushing again or dropping the branch. On the server side, unknown_base rises, and README:169 names that counter as the one to alert on, so a service fault (slow disk, corrupt mirror) is misread as client behaviour.

Suggested fix

  • Return a three-state result from both queries: Found / Absent / Failed(TimedOut). Treat only exit code 1 as absent: git merge-base exits 1 for unrelated histories, and git cat-file -e exits 1 for a missing object. Every other outcome is a failure.
  • In BranchStateHandler:
    • Map a merge-base timeout to the branch-level timeout error the diff path already uses, and any other failure to a distinct error.
    • Map a failed commit check to 504 (timeout) or 502 (other), not 409, and don't increment unknown_base.

Acceptance criteria

  • With merge-base timing out, the branch reports error: "timeout", not no-merge-base.
  • With cat-file timing out or failing, the response is 5xx and unknown_base is unchanged.
  • A genuinely missing commit (exit 1) still returns 409 unknown-base, and genuinely unrelated histories still report no-merge-base.

Activity

  1. matt-edmondson commented on Oct 5, 2026

    @matt-edmondson
    ContributorAuthor

    Triage


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions