DEV Community

pm25coder
pm25coder

Posted on Edited on

My clone said 0.3.23. GitHub said 0.3.33. Neither one errored.

My clone said 0.3.23. GitHub said 0.3.33. Neither one errored.

I asked my own checkout which version was newest and it answered v0.3.23. I asked GitHub the same question and it answered v0.3.33, published 2026-10-29T15:26:42Z. Then I checked whether my checkout had that tag at all: git rev-parse -q --verify refs/tags/v0.3.33 exited 1, and the clone holds 124 tags with nothing newer than v0.3.23.

The part I want to write down is that a minute before, git fetch origin had exited 0. A successful fetch feels like evidence that you now know what the project has. It is evidence about the remote you fetched from, and about nothing else. My origin is rewritten by a url.<path>.insteadOf rule to a local directory, and that directory's tags stop ten version bumps early, so the fetch was honest and complete and still left me answering with a number from 2026-10-20.

That is the shape of the mistake I keep making: two refs move on different clocks, both print a version, and whichever one I happen to run answers a question I did not ask.

Three questions, three refs

"Which version is the project on?" splits into three questions that have three different answers, and only the first one is what people usually mean.

What you want to know The ref that answers it What it printed on my repo
What shipped? the commit the release tag points at v0.3.33 → 06a001a, published 2026-10-29T15:26:42Z
What is merged? the default branch head 98468f4 on master, committed 2026-10-30T09:15:22Z
What do I have locally? your own clue newest tag v0.3.23, 124 tags, no v0.3.33

On my repo the first two differ by 6 commits touching 22 files. That gap is the whole point: those 6 commits are merged and released to nobody. If the bug you are hitting lives in one of them, "the release is latest" is true and useless to you.

The probe, and a second repo so the numbers are not about me

Fifteen lines, gh only, no clone required:

import json, subprocess, sys

def gh(path):
    r = subprocess.run(["gh", "api", path], capture_output=True)
    if r.returncode != 0:
        raise SystemExit(r.stderr.decode("utf-8", "replace").strip())
    return json.loads(r.stdout.decode("utf-8", "replace"))

repo = sys.argv[1]
branch = gh(f"repos/{repo}")["default_branch"]
head = gh(f"repos/{repo}/commits/{branch}")            # what is merged
rel = gh(f"repos/{repo}/releases/latest")              # what shipped
tag = gh(f"repos/{repo}/git/ref/tags/{rel['tag_name']}")
sha, kind = tag["object"]["sha"], tag["object"]["type"]
if kind == "tag":                                       # annotated: deref one hop
    sha = gh(f"repos/{repo}/git/tags/{sha}")["object"]["sha"]
gap = gh(f"repos/{repo}/compare/{sha}...{head['sha']}")
print(head["sha"][:7], head["commit"]["committer"]["date"], "on", branch)
print(rel["tag_name"], sha[:7], rel["published_at"], f"[tag type={kind}]")
print(gap["ahead_by"], "commits,", len(gap.get("files") or []), "files not in the release")
Enter fullscreen mode Exit fullscreen mode

I ran it on my repo and then on a repository I do not own, because a measurement that only works on the repo you control is a measurement of that repo. Here is the verbatim output from both:

REPO argszero/emrg
 head      98468f4 2026-10-30T09:15:22Z (master)
 released  v0.3.33 06a001a 2026-10-29T15:26:42Z [tag object type=tag]
 gap       6 commits, 22 files not in the release
 assets    11
REPO openai/openai-agents-python
 head      cd26bbb 2026-10-27T19:22:01Z (main)
 released  v0.24.0 17d994b 2026-10-15T02:20:12Z [tag object type=commit]
 gap       39 commits, 92 files not in the release
Enter fullscreen mode Exit fullscreen mode

Twelve days after its last release, that second repository had 39 merged commits that no release contains. So the gap is not a small project's slack; it is how releases work. A release is a moment someone chose, not a mirror of the branch.

Three things that will bite the script

The tag is not always a commit. On my repo v0.3.33 is an annotated tag: the ref points at a tag object (50ba882) that points at a commit (06a001a). On the other repo it is lightweight and points straight at a commit. So git rev-parse <tag> returns two different kinds of object depending on the repository, and comparing that output against git rev-parse origin/main compares a tag object with a commit. Even on the same commit they would differ. Ask for the commit explicitly — git rev-parse v0.3.23^{commit} — or deref as the script above does for the API. Locally, on my annotated tag v0.3.23, plain rev-parse gave me f36ae3f and ^{commit} gave me c2e8f77; both are correct answers to two different questions.

One release has three timestamps. On my repo: the tagged commit was authored at 15:02:15Z, the tag object was created at 15:13:44Z, and the release is published at 15:26:42Z. Those are 24 minutes apart, which is harmless — but if you are sorting "which of these two projects moves faster" by release date, you are comparing a person's tag-clicking habit with someone else's CI schedule.

ahead_by counts commits, not your fix. 92 files means there is a gap; it does not mean the specific thing you care about is in it. The compare endpoint returns the file list, and the file list is capped at 300 entries, so on a gap bigger than that you get a truncated answer that looks complete. Read the commit messages for the area you care about, or ask the endpoint about the single path you are chasing.

What this reading cannot tell you

It will not tell you whether a release is safe to take, and it says nothing about quality — a big gap can mean a well-tested trunk or a project that does not cut releases. It also cannot tell you what "the release" even is on a given project: my repo publishes 11 build assets, the other publishes none and ships through a package index instead. Same API field, two different meanings.

The one thing it does answer, reliably, is the question that started this: has anything been merged since the thing I installed. If the answer is zero, stop looking upstream and read your own code. If it is not zero, the next question is not "is main newer" — it is whether the file you are debugging appears in that compare, and that you can check in one request.


One of the two repositories in the table is mine, which is worth stating so you can discount the self-reference. The other one is a project I have no relationship with; I picked it because it ships often and I wanted the second arm to be someone else's numbers.

Top comments (0)