A PR never got a CI run: the workflow triggered only on push to main
and on tags, so the risk/medium+ PR lane merged on local task check
alone (PR #66). pull_request now runs Lint/Test/Vet on the PR head;
build is gated to push and deploy already was, so a PR builds no
image, writes nothing to infra and sees no secrets.
Closes#68
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On 2026-08-25 a deploy job ran with an empty IMAGE_TAG, wrote
`image: localhost:5000/gitea-mcp:` into the infra repo and pushed it.
That is invalid YAML, so Flux failed the ENTIRE apps Kustomization --
every app stopped reconciling for about an hour, including three
unrelated deploys queued behind it.
This job's existing check could not catch it. It greps for
`image: <repo>:${IMAGE_TAG}` after the sed, and with IMAGE_TAG empty that
pattern matches the corrupted line itself, so the verification passes
because of the bug rather than despite it.
Asserting the input up front is the fix: an empty tag now fails the job
before anything is written, let alone committed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011PL5XcG68SvQqz3vGVbWbf
act interpolates a `run:` block INCLUDING its shell comments, so prose
containing Actions expression syntax is evaluated as an expression. A
comment I added yesterday explaining why a fallback was computed in shell
contained that syntax as an example; act tried to evaluate it, failed
with "Unavailable context: a", and the step then wrote NO outputs at all.
In gitea-mcp that emptied `needs.build.outputs.image-tag`, so its deploy
job committed `image: localhost:5000/gitea-mcp:` to the infra repo --
which is not valid YAML, so Flux's apps Kustomization went Ready:False
and NOTHING in k3s/apps reconciled estate-wide until infra 51ceb7c.
The build still went green throughout, because the shared build-image.sh
falls back to `git rev-parse --short HEAD` when SHA_TAG is empty. A
robust default hid a broken input -- worth remembering as a pattern.
Same comment text removed here; the explanation stays, the syntax does not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011PL5XcG68SvQqz3vGVbWbf
Wave 2. The buildctl/skopeo step here was one of nineteen near-identical
copies; the implementation now lives in .gitea/build-image.sh,
byte-identical to mathias/infra's ci-templates/build-image.sh and held
there by a drift gate. Labels, tags and build-args are inputs.
Behaviour unchanged: same registry, same :sha/:latest/version tags, same
revision and source labels.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011PL5XcG68SvQqz3vGVbWbf
The step ran `sudo k3s ctr images pull` / `ctr run` against the host's
containerd. That worked when the runner was the Arch host; since it was
containerized (infra#233) the image has no sudo, no ctr and no containerd
socket, so the step fails with `sudo: command not found`. It only
surfaced now because the buildah wall (infra#241) killed the job before
this step ever ran.
The deploy job's `Verify rollout` and `Confirm pod running new image`
assert the same property against the real workload rather than a 5s
throwaway container, which is why tapir dropped its smoke test under the
same issue.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011PL5XcG68SvQqz3vGVbWbf
Rootless buildah in the containerized gitea-runner pod cannot remap
UID/GID on a fresh base-image pull and has no OCI runtime, so this
Dockerfile's `RUN apk add` / `RUN go build` cannot execute there at all.
The last green run here (2026-07-22) predates the runner's
containerization (infra#233, 2026-07-29), so the build has been latently
broken since.
buildctl hands the build to koala's host buildkitd over the socket the
pod already mounts; skopeo pushes the resulting OCI archive once per tag,
since buildctl has no `buildah push` equivalent. The VERSION build-arg
carries over as `--opt build-arg:VERSION=`, so the ldflags version stamp
is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011PL5XcG68SvQqz3vGVbWbf
Relocate the doc under docs/ and add paths-ignore: ['docs/**'] to the CD push
trigger so documentation-only changes no longer rebuild the image and roll the
pod. Branch pushes with only docs/ changes are skipped; tag (v*) pushes and any
push touching code still deploy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dockerfile takes ARG VERSION (default "dev") and stamps it into
main.version with -X. CD passes --build-arg VERSION=<git tag on v* builds,
else the short sha>, so the running pod logs the actual build instead of
"dev". Verified locally: `-ldflags -X main.version=...` embeds the string.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes#27.
PROJECT.md
- Git section: TBD as the convention. Commit to main, one logical
change per commit, `task check` locally before push, CI is the
quality gate. PRs only for the parallel-agent exception.
- Agent rule 6: rewritten to match.
.gitea/workflows/cd.yml
- Drop the pull_request trigger — vestigial under TBD.
- Drop the `if: github.event_name != 'pull_request'` guard on the
build job (now always true since pull_request no longer fires).
Tag pushes still build (no version gating regression).
- Deploy `if` left alone — already correctly limits deploy to
main pushes, skipping tag-push builds.
.githooks/pre-push (new)
- Runs `task check` before every push. Set up via `task setup:hooks`,
which sets core.hooksPath to the in-repo .githooks dir.
Taskfile.yml
- New `setup:hooks` task to install the pre-push hook on a fresh
clone.
README.md
- Quickstart section showing `task setup:hooks` + the TBD policy.
Derived adapters regenerated via `task context:sync` and committed
in the same commit (single-commit invariant).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aligns with cobalt-dingo reference — the deploy job was missing the
Gitea Actions environment protection so staging approvals/secrets were
not enforced.