The admin header now says which build it is running:
a5076cc · built 29 Aug 20:36
Why
Minutes after #232 merged, npm run backfill:images in QA failed with tsx: not found — the container was still serving the pre-merge image. The only thing that revealed it was npm happening to echo the old script line. Had the change been anywhere other than a package.json script, the container would have looked healthy while running the wrong code.
The commit answers "is this the code I expect". The build time answers "did my redeploy actually rebuild" — a separate question, and the one that would have caught that.
How the commit gets in
A build stage copies .git and reads the ref with node. No git binary and no apt layer: node:20-bookworm-slim has none, and installing one so the image can print seven characters is a poor trade. .git is copied into the build stage only — verified absent from the final image — so no repository history reaches a deployed container.
Resolution is pure and separately tested across every shape that actually occurs:
detached HEAD holding the object name (what a checkout of a specific ref produces)
symbolic HEAD followed to a loose ref file (a working clone)
the same followed to packed-refs (what a fresh clone commonly has)
peeled ^ lines ignored, so an annotated tag cannot resolve to the wrong commit
every failure path returning unknown
That last one is the one that matters: this runs inside a Docker build, and a version stamp must never be the thing that stops a deploy. An unreadable .git warns loudly and carries on.
Where it is served
GET /api/admin/version, behind requireAdminGate, not folded into /api/config. That endpoint is public — the storefront fetches it on every load — and a commit hash there would tell any visitor exactly which revision of a public repository is deployed. An integration test asserts both halves: the endpoint is gated, and /api/config does not carry the commit. The boundary is the point, not an implementation detail.
Verified in the built image
$ docker run --rm <image> cat dist/buildInfo.json
{ "commit": "a5076cc", "builtAt": "2026-08-29T20:36:09Z" }
$ docker run --rm <image> sh -c "ls -d .git; ls -d /app/.git"
ls: cannot access '.git': No such file or directory
ls: cannot access '/app/.git': No such file or directory
$ curl <running container>/api/admin/version
{"commit":"a5076cc","builtAt":"2026-08-29T20:36:09Z"}
$ curl <running container>/api/config
{"paypalClientId":null,"demoMode":true,"currency":"USD","uploadsBaseUrl":""}
a5076cc matches git rev-parse --short HEAD.
Backend: 296 unit, 263 integration, tsc clean, lint unchanged at six pre-existing warnings. Frontend builds clean, its two pre-existing warnings untouched.
Worth knowing
This makes the running version visible; it does not detect staleness. Comparing what is running against what is on main needs a source of truth about what should be deployed, and is a bigger feature. You still have to look at the header — but now there is something to look at.
In local development it renders nothing. Nothing has been built, so the commit is unknown and the component returns null rather than showing a meaningless stamp.
The admin header now says which build it is running:
```
a5076cc · built 29 Aug 20:36
```
## Why
Minutes after #232 merged, `npm run backfill:images` in QA failed with `tsx: not found` — the container was still serving the pre-merge image. The only thing that revealed it was npm happening to echo the old script line. Had the change been anywhere other than a `package.json` script, the container would have looked healthy while running the wrong code.
The commit answers "is this the code I expect". The build time answers "did my redeploy actually rebuild" — a separate question, and the one that would have caught that.
## How the commit gets in
A build stage copies `.git` and reads the ref with node. No `git` binary and no apt layer: `node:20-bookworm-slim` has none, and installing one so the image can print seven characters is a poor trade. `.git` is copied into the **build stage only** — verified absent from the final image — so no repository history reaches a deployed container.
Resolution is pure and separately tested across every shape that actually occurs:
- detached HEAD holding the object name (what a checkout of a specific ref produces)
- symbolic HEAD followed to a loose ref file (a working clone)
- the same followed to `packed-refs` (what a fresh clone commonly has)
- peeled `^` lines ignored, so an annotated tag cannot resolve to the wrong commit
- every failure path returning `unknown`
That last one is the one that matters: this runs inside a Docker build, and **a version stamp must never be the thing that stops a deploy.** An unreadable `.git` warns loudly and carries on.
## Where it is served
`GET /api/admin/version`, behind `requireAdminGate`, **not** folded into `/api/config`. That endpoint is public — the storefront fetches it on every load — and a commit hash there would tell any visitor exactly which revision of a public repository is deployed. An integration test asserts both halves: the endpoint is gated, and `/api/config` does not carry the commit. The boundary is the point, not an implementation detail.
## Verified in the built image
```
$ docker run --rm <image> cat dist/buildInfo.json
{ "commit": "a5076cc", "builtAt": "2026-08-29T20:36:09Z" }
$ docker run --rm <image> sh -c "ls -d .git; ls -d /app/.git"
ls: cannot access '.git': No such file or directory
ls: cannot access '/app/.git': No such file or directory
$ curl <running container>/api/admin/version
{"commit":"a5076cc","builtAt":"2026-08-29T20:36:09Z"}
$ curl <running container>/api/config
{"paypalClientId":null,"demoMode":true,"currency":"USD","uploadsBaseUrl":""}
```
`a5076cc` matches `git rev-parse --short HEAD`.
Backend: **296 unit**, **263 integration**, tsc clean, lint unchanged at six pre-existing warnings. Frontend builds clean, its two pre-existing warnings untouched.
## Worth knowing
**This makes the running version visible; it does not detect staleness.** Comparing what is running against what is on `main` needs a source of truth about what *should* be deployed, and is a bigger feature. You still have to look at the header — but now there is something to look at.
**In local development it renders nothing.** Nothing has been built, so the commit is `unknown` and the component returns null rather than showing a meaningless stamp.
Closes #233
There was no way to tell which build an environment was running. That is not hypothetical: minutes after #232 merged, `npm run backfill:images` in QA failed with `tsx: not found` because the container was still serving the pre-merge image, and the only thing that revealed it was npm echoing the old script line. Had the change been anywhere other than a package.json script, the container would have looked healthy while running the wrong code.
The header now reads something like `a5076cc · built 29 Aug 20:36`. The commit answers "is this the code I expect"; the build time answers "did my redeploy actually rebuild", which is a different question and the one that would have caught the case above.
The commit is read out of `.git` directly rather than by shelling out, because node:20-bookworm-slim has no git binary and adding an apt layer so the image can print seven characters is a poor trade. `.git` is copied into the build stage only — verified absent from the final image — so no repository history reaches a deployed container.
Resolution is pure and separately tested across every shape that actually occurs: a detached HEAD holding the object name, which is what a checkout of a ref produces; a symbolic HEAD followed to a loose ref file; the same followed to packed-refs, which is what a fresh clone commonly has; peeled `^` tag lines ignored so an annotated tag cannot yield the wrong commit; and every failure path returning `unknown`. That last part is the one that matters most — this runs during a Docker build, and a version stamp must never be the thing that stops a deploy.
Served from a gated /api/admin/version rather than folded into /api/config. That endpoint is public, and a commit hash there would tell any storefront visitor exactly which revision of a public repository is deployed. An integration test asserts the gate and asserts the public config does not carry it, because the boundary is the whole point rather than an implementation detail.
Verified in the built image rather than argued: the stamp inside it reads a5076cc, matching `git rev-parse --short HEAD`, and a running container serves it from /api/admin/version while /api/config returns only what it did before.
Backend: 296 unit, 263 integration, tsc clean, lint unchanged at six pre-existing warnings. Frontend builds clean with its two pre-existing warnings untouched.
Closes#233
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The admin header now says which build it is running:
Why
Minutes after #232 merged,
npm run backfill:imagesin QA failed withtsx: not found— the container was still serving the pre-merge image. The only thing that revealed it was npm happening to echo the old script line. Had the change been anywhere other than apackage.jsonscript, the container would have looked healthy while running the wrong code.The commit answers "is this the code I expect". The build time answers "did my redeploy actually rebuild" — a separate question, and the one that would have caught that.
How the commit gets in
A build stage copies
.gitand reads the ref with node. Nogitbinary and no apt layer:node:20-bookworm-slimhas none, and installing one so the image can print seven characters is a poor trade..gitis copied into the build stage only — verified absent from the final image — so no repository history reaches a deployed container.Resolution is pure and separately tested across every shape that actually occurs:
packed-refs(what a fresh clone commonly has)^lines ignored, so an annotated tag cannot resolve to the wrong commitunknownThat last one is the one that matters: this runs inside a Docker build, and a version stamp must never be the thing that stops a deploy. An unreadable
.gitwarns loudly and carries on.Where it is served
GET /api/admin/version, behindrequireAdminGate, not folded into/api/config. That endpoint is public — the storefront fetches it on every load — and a commit hash there would tell any visitor exactly which revision of a public repository is deployed. An integration test asserts both halves: the endpoint is gated, and/api/configdoes not carry the commit. The boundary is the point, not an implementation detail.Verified in the built image
a5076ccmatchesgit rev-parse --short HEAD.Backend: 296 unit, 263 integration, tsc clean, lint unchanged at six pre-existing warnings. Frontend builds clean, its two pre-existing warnings untouched.
Worth knowing
This makes the running version visible; it does not detect staleness. Comparing what is running against what is on
mainneeds a source of truth about what should be deployed, and is a bigger feature. You still have to look at the header — but now there is something to look at.In local development it renders nothing. Nothing has been built, so the commit is
unknownand the component returns null rather than showing a meaningless stamp.Closes #233