1 Commits
Author SHA1 Message Date
clawbot c460b0d6a5 Derive the image version from git; send .git to the build (closes #69)
check / check (push) Successful in 28s
The canonical documents told every repo to exclude .git from the build
context, default ARG VERSION to dev and never run git describe in a
build stage, so an image built from a clone with no build argument
reported dev. .dockerignore now sends .git. The Dockerfile example in
REPO_POLICIES.md installs git, takes the VERSION build argument when one
is given and otherwise git describe --tags --always, and fails when .git
is present but the version is empty, dev or unknown. The checklists, the
Go docs' Makefile comments, the README, and this repo's Dockerfile and
scripts now say the same.

Model: opus-5-5
2026-10-02 00:49:56 +00:00
5 changed files with 38 additions and 61 deletions
+2 -5
View File
@@ -13,11 +13,8 @@
# `/myapp`, never `**/myapp`, which also matches `cmd/myapp/` and
# deletes the package directory from the context.
# .git is sent without its config. Without a VERSION build argument the
# stage that compiles runs `git describe --tags --always` on .git, which
# does not need .git/config; that file can hold a credential, such as a
# password in a remote URL or the token the CI checkout step stores there.
.git/config
# .git is sent: without a VERSION build argument, the stage that compiles
# takes the version from `git describe --tags --always` on it.
# Agent scratch: one full checkout of the repo per in-flight agent.
# Anchored because it occurs once where agents run at the repo root.
+1 -1
View File
@@ -52,7 +52,7 @@ last_modified: 2026-10-02
# where a build stage invokes make, `ARG VERSION` puts it in the
# environment and `?=` defers to it. Otherwise `git describe` runs, in a
# build stage on the `.git` the build context carries.
VERSION ?= $(shell git describe --tags --always)
VERSION ?= $(shell git describe --always --dirty)
GOLDFLAGS += -X main.Version=$(VERSION)
+10 -16
View File
@@ -59,22 +59,16 @@ with your task.
here run anywhere other than the repo root, the anchored entry misses
`services/api/.claude/`: add anchored entries for those directories.
- [ ] If the repo embeds a version in a binary: `.dockerignore` lets `.git` into
the build context. It keeps out `.git/config`, which `git describe` does
not need and which can hold a credential: a password in a remote URL, or
the token the CI checkout step stores there. The stage that compiles has
`git` (the Debian Go image has it; an alpine one needs
`apk add --no-cache git`) and takes the version from the `VERSION` build
argument when one is given, otherwise from `git describe --tags --always`.
That gives the tag on a tagged commit; on a later commit, the tag, the
number of commits since it and the short commit (`v1.2.3-4-gabc1234`); and
the short commit when no tag is reachable. `ARG VERSION` has no default,
and the build fails if the context carries `.git` and the version still
comes out empty, `dev` or `unknown`. A plain `docker build .` with no
build arguments must succeed; a Dockerfile that refuses an empty build
argument drops that refusal and keeps the argument. `script/docker` and
`script/cibuild` pass the version they compute on the host; it takes
precedence. A tag-derived version additionally needs `fetch-depth: 0` on
the CI checkout step, which clones shallow and fetches no tags by default.
the build context. The stage that compiles has `git` (the Debian Go image
has it; an alpine one needs `apk add --no-cache git`) and takes the
version from the `VERSION` build argument when one is given, otherwise
from `git describe --tags --always` (a tag when the commit has one,
otherwise the short commit). `ARG VERSION` has no default, and the build
fails if the context carries `.git` and the version still comes out empty,
`dev` or `unknown`. `script/docker` and `script/cibuild` pass the version
they compute on the host; it takes precedence. A tag-derived version
additionally needs `fetch-depth: 0` on the CI checkout step, which clones
shallow and fetches no tags by default.
- [ ] Gitea Actions workflow in `.gitea/workflows/` runs `script/cibuild` on
push — reference
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitea/workflows/check.yml`
+7 -13
View File
@@ -68,19 +68,13 @@ Template files can be fetched from:
will run them in subdirectories, `services/api/.claude/` needs its own
anchored entry.
- If the image embeds a version in a binary: `.dockerignore` lets `.git`
into the build context. It keeps out `.git/config`, which `git describe`
does not need and which can hold a credential: a password in a remote URL,
or the token the CI checkout step stores there. The stage that compiles
has `git` (the Debian Go image has it; an alpine one needs
`apk add --no-cache git`) and takes the version from the `VERSION` build
argument when one is given, otherwise from `git describe --tags --always`.
That gives the tag on a tagged commit; on a later commit, the tag, the
number of commits since it and the short commit (`v1.2.3-4-gabc1234`); and
the short commit when no tag is reachable. `ARG VERSION` has no default,
and the build fails if the context carries `.git` and the version still
comes out empty, `dev` or `unknown`. A plain `docker build .` with no
build arguments must succeed; a Dockerfile that refuses an empty build
argument drops that refusal and keeps the argument.
into the build context. The stage that compiles has `git` (the Debian Go
image has it; an alpine one needs `apk add --no-cache git`) and takes the
version from the `VERSION` build argument when one is given, otherwise
from `git describe --tags --always` (a tag when the commit has one,
otherwise the short commit). `ARG VERSION` has no default, and the build
fails if the context carries `.git` and the version still comes out empty,
`dev` or `unknown`.
- The Dockerfile carries a `lint` phase and a `test` phase, each invoking
its tool directly rather than through `make` or `script/`, and the final
stage carries a `COPY --from=` of a harmless file from each so the image
+18 -26
View File
@@ -197,13 +197,13 @@ style conventions are in separate documents:
RUN go mod download
COPY . .
# The VERSION build arg when one is given, otherwise
# `git describe --tags --always` on the .git in the build context. With
# .git present, a version that is still empty, dev or unknown fails the
# build: git is missing or could not read the checkout.
# The VERSION build arg when one is given, otherwise the tag or short
# commit from the .git in the build context. With .git present, a
# version that is still empty, dev or unknown fails the build: git is
# missing or could not read the checkout.
ARG VERSION
RUN VERSION="${VERSION:-$(git describe --tags --always)}"; \
if [ -e .git ]; then \
if [ -d .git ]; then \
case "$VERSION" in ""|dev|unknown) \
echo "version is '$VERSION' although .git is present" >&2; \
exit 1 ;; \
@@ -235,20 +235,13 @@ style conventions are in separate documents:
`RUN mkdir -p web/dist && touch web/dist/index.html web/dist/style.css`.
- If the project requires CGO or system libraries for linting (e.g.
`vips-dev`), install them in the lint phase with `apk add`.
- `.dockerignore` lets `.git` into the build context. It keeps out
`.git/config`, which `git describe` does not need and which can hold a
credential: a password in a remote URL, or the token the CI checkout step
stores there. The stage that compiles has `git` (the Debian Go image has
it; an alpine one needs `apk add --no-cache git`) and takes the version
from the `VERSION` build argument when one is given, otherwise from
`git describe --tags --always`. That gives the tag on a tagged commit; on
a later commit, the tag, the number of commits since it and the short
commit (`v1.2.3-4-gabc1234`); and the short commit when no tag is
reachable. `ARG VERSION` has no default, and the build fails if the
context carries `.git` and the version still comes out empty, `dev` or
`unknown`. A plain `docker build .` with no build arguments must succeed;
a Dockerfile that refuses an empty build argument drops that refusal and
keeps the argument.
- `.dockerignore` lets `.git` into the build context. The stage that
compiles has `git` (the Debian Go image has it; an alpine one needs
`apk add --no-cache git`) and takes the version from the `VERSION` build
argument when one is given, otherwise from `git describe --tags --always`
(a tag when the commit has one, otherwise the short commit). `ARG VERSION`
has no default, and the build fails if the context carries `.git` and the
version still comes out empty, `dev` or `unknown`.
- Every repo should have a Gitea Actions workflow (`.gitea/workflows/`) that
runs `script/cibuild` on push, and checks out the repo as its only other step.
@@ -389,13 +382,12 @@ style conventions are in separate documents:
directory, so a repo running agents in subdirectories still ships
`services/api/.claude/` and must add its own anchored entry there.
- **A plain `docker build .` of a clone stamps the version that
`git describe --tags --always` gives**, derived from the `.git` in the build
context as the canonical `Dockerfile` above shows. Without its failure check,
a missing `git` or an unreadable checkout would leave `-X main.Version=` empty
and the build would still exit 0. `script/docker` and `script/cibuild` pass
the version they compute on the host; it takes precedence. They do this
byte-identically across repos:
- **A plain `docker build .` of a clone stamps the tag or short commit**,
derived from the `.git` in the build context as the canonical `Dockerfile`
above shows. Without its failure check, a missing `git` or an unreadable
checkout would leave `-X main.Version=` empty and the build would still
exit 0. `script/docker` and `script/cibuild` pass the version they compute on
the host; it takes precedence. They do this byte-identically across repos:
```sh
# Own line: a failing command substitution inside an argument does not