git fetch --depth=Ngrafts a complete clone shallow — it writes .git/shallow and cuts history at N even though every object is already present. scripts/ci-detect-docs-only.sh has four consumers in docker-build.yml that disagree on checkout depth (test/migrations/functional-e2e at fetch-depth: 2, build at fetch-depth: 0), and it applied a depth chosen for the first group to all of them.
That grafted build's complete clone on every push to main, so the git describe --tags in the immediately following Compute version and tags step found no reachable tag, and a || echo v0.0.0 fallback that cannot fail turned it into a version. Every :latest image published from main carried InformationalVersion 0.0.0-<sha> instead of 26.x.y-<sha>, from 2026-07-17 (#416) until this change, with nothing red anywhere.
What changed
scripts/ci-detect-docs-only.sh — both fetch sites go through fetch_ref, which passes --depth only when git rev-parse --is-shallow-repository answers true. Any other answer passes none.
.gitea/workflows/docker-build.yml — the non-tag path no longer defaults. A failed git describe fails the job with a diagnostic, so no :latest is published rather than a mislabelled one. build is not a required status context, so this cannot block a merge; the tag path never calls describe, so releases are unaffected.
New guardscripts/tests/test_docs_only_detector_clone_depth.py, a declared clause mutation in mutation_manifest.py, and inventory rows — promoting the detector from NONE to MUTATION.
Docs — docs/ci-cd.md (its prose described the defect in the present tense) and a new record ci.fetch-depth-never-grafts-a-complete-clone.
Measured, not inferred
The live defect in CI: run 2416 / job 10345 (push to main8aeacd534) → INFO_VERSION=0.0.0-8aeacd53.
The causal premise: the repo is public, so the credential-less fetch that runs after actions/checkout's "Removing auth" does succeed — the graft is real. The checkout log also shows tags being fetched, which rules out "the tags were never there".
Against a real anonymous clone of this repository: predecessor detector → grafted, describe fails → 0.0.0-1afad085; fixed detector → complete, describe → 26.14.0-1afad085.
The workflow step body executed on both clones: grafted → exit 1 with a diagnostic and no image; complete → 26.14.0-…; tag path → :prod + 26.14.0, unaffected even on a grafted clone.
The guard witnessed red against origin/main's detector: 4 failed, 2 passed (the two that pass are the negative control and the shallow-unchanged case, both correctly script-independent).
Scope note, corrected by review
The PR arm's --depth=200 was inert, not latently firing: on pull_request the only jobs running this script are the three shallow ones. api-docs/format have a step with id: detect but it runs their own inline diff — an earlier draft of this change read them as consumers and said so in the decision record. The depth is dropped there anyway because the clause is shared and the next fetch-depth: 0 consumer would inherit the graft silently. Likewise, keeping the depth for the shallow three is conservative rather than required: a depth-less fetch into a --depth=2 clone leaves .git/shallow in place, still writes FETCH_HEAD, and still reports the parents the push arm reads.
Verification
scripts/tests full suite: 1237 passed, 2 skipped, on base 736649b3b.
Guard witnessed red against the real predecessor; declared mutation reddens its named proof for the right diagnostic (test_mutation_harness.py 36/36).
decisions_validate.py OK · catalog up to date · check-doc-narrative.py 0 warnings · shellcheck clean · no .cs touched, so the BOM/format gate does not apply.
Two independent cold-review rounds (Claude, isolated worktree, review-only brief). Round 1 found a HIGH (the consumer claim above) and two MEDIUMs; round 2 returned nothing at BLOCKER/HIGH/MEDIUM. No cross-family review this session — Codex was out of quota.
Post-merge
Read InformationalVersion back out of the newly built :latest image and record it in the issue's ## Closing record. That check is deliberately not a ## Done-when box: build only runs on a push to main, so it could never be ticked before the merge it would gate.
fixes #836
`git fetch --depth=N` **grafts a complete clone shallow** — it writes `.git/shallow` and cuts history at N even though every object is already present. `scripts/ci-detect-docs-only.sh` has four consumers in `docker-build.yml` that disagree on checkout depth (`test`/`migrations`/`functional-e2e` at `fetch-depth: 2`, `build` at `fetch-depth: 0`), and it applied a depth chosen for the first group to all of them.
That grafted `build`'s complete clone on every push to `main`, so the `git describe --tags` in the immediately following `Compute version and tags` step found no reachable tag, and a `|| echo v0.0.0` fallback that cannot fail turned it into a version. Every `:latest` image published from `main` carried `InformationalVersion 0.0.0-<sha>` instead of `26.x.y-<sha>`, from 2026-07-17 (#416) until this change, with nothing red anywhere.
## What changed
1. **`scripts/ci-detect-docs-only.sh`** — both fetch sites go through `fetch_ref`, which passes `--depth` only when `git rev-parse --is-shallow-repository` answers `true`. Any other answer passes none.
2. **`.gitea/workflows/docker-build.yml`** — the non-tag path no longer defaults. A failed `git describe` fails the job with a diagnostic, so no `:latest` is published rather than a mislabelled one. `build` is **not** a required status context, so this cannot block a merge; the tag path never calls `describe`, so releases are unaffected.
3. **New guard** `scripts/tests/test_docs_only_detector_clone_depth.py`, a declared clause mutation in `mutation_manifest.py`, and inventory rows — promoting the detector from `NONE` to `MUTATION`.
4. **Docs** — `docs/ci-cd.md` (its prose described the defect in the present tense) and a new record `ci.fetch-depth-never-grafts-a-complete-clone`.
## Measured, not inferred
- **The live defect in CI**: run 2416 / job 10345 (push to `main` `8aeacd534`) → `INFO_VERSION=0.0.0-8aeacd53`.
- **The causal premise**: the repo is public, so the credential-less fetch that runs after `actions/checkout`'s "Removing auth" does succeed — the graft is real. The checkout log also shows tags being fetched, which rules out "the tags were never there".
- **Against a real anonymous clone of this repository**: predecessor detector → grafted, `describe` fails → `0.0.0-1afad085`; fixed detector → complete, `describe` → `26.14.0-1afad085`.
- **The workflow step body executed** on both clones: grafted → `exit 1` with a diagnostic and no image; complete → `26.14.0-…`; tag path → `:prod` + `26.14.0`, unaffected even on a grafted clone.
- **The guard witnessed red** against `origin/main`'s detector: 4 failed, 2 passed (the two that pass are the negative control and the shallow-unchanged case, both correctly script-independent).
## Scope note, corrected by review
The PR arm's `--depth=200` was **inert, not latently firing**: on `pull_request` the only jobs running this script are the three shallow ones. `api-docs`/`format` have a step with `id: detect` but it runs their own inline diff — an earlier draft of this change read them as consumers and said so in the decision record. The depth is dropped there anyway because the clause is shared and the next `fetch-depth: 0` consumer would inherit the graft silently. Likewise, keeping the depth for the shallow three is **conservative rather than required**: a depth-less fetch into a `--depth=2` clone leaves `.git/shallow` in place, still writes `FETCH_HEAD`, and still reports the parents the push arm reads.
## Verification
- `scripts/tests` full suite: **1237 passed, 2 skipped**, on base `736649b3b`.
- Guard witnessed red against the real predecessor; declared mutation reddens its named proof for the right diagnostic (`test_mutation_harness.py` 36/36).
- `decisions_validate.py` OK · catalog up to date · `check-doc-narrative.py` 0 warnings · `shellcheck` clean · no `.cs` touched, so the BOM/format gate does not apply.
- Two independent cold-review rounds (Claude, isolated worktree, review-only brief). Round 1 found a HIGH (the consumer claim above) and two MEDIUMs; round 2 returned nothing at BLOCKER/HIGH/MEDIUM. **No cross-family review this session — Codex was out of quota.**
## Post-merge
Read `InformationalVersion` back out of the newly built `:latest` image and record it in the issue's `## Closing record`. That check is deliberately not a `## Done-when` box: `build` only runs on a push to `main`, so it could never be ticked before the merge it would gate.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01HETQK67q1bEpDZpHLZNsp9
Two cold review rounds; round 2 clean at BLOCKER/HIGH/MEDIUM. Suite 1237 passed on base 736649b3b; guard witnessed red against the real predecessor.
Review-verdict: MERGEABLE @ 58d2c14
Two cold review rounds; round 2 clean at BLOCKER/HIGH/MEDIUM. Suite 1237 passed on base 736649b3b; guard witnessed red against the real predecessor.
Head b8af3bf4a adds only a ruff format pass over the new guard file: one file, ASTs identical to the reviewed 58d2c147a, so the round-2 clean verdict carries. ruff check + format --check pass over all 51 tracked files with the CI-pinned 0.12.11; suite 1237 passed, 2 skipped.
Review-verdict: MERGEABLE @ b8af3bf
Head b8af3bf4a adds only a ruff format pass over the new guard file: one file, ASTs identical to the reviewed 58d2c147a, so the round-2 clean verdict carries. ruff check + format --check pass over all 51 tracked files with the CI-pinned 0.12.11; suite 1237 passed, 2 skipped.
Head 008860c2b fixes a CI-only red in the new guard's fixtures (git identity in a clone). Boundary fix in the test harness only; the shipped script and workflow are byte-identical to the reviewed 58d2c147a. Local run now reproduces the CI condition — verified by removing the identity flags and witnessing the same 'Author identity unknown'.
Review-verdict: MERGEABLE @ 008860c
Head 008860c2b fixes a CI-only red in the new guard's fixtures (git identity in a clone). Boundary fix in the test harness only; the shipped script and workflow are byte-identical to the reviewed 58d2c147a. Local run now reproduces the CI condition — verified by removing the identity flags and witnessing the same 'Author identity unknown'.
`git fetch --depth=N` GRAFTS a complete clone shallow — it writes `.git/shallow`
and cuts history at N even though every object is already present.
`scripts/ci-detect-docs-only.sh` has four consumers in docker-build.yml and they
do not agree on checkout depth: test/migrations/functional-e2e check out
`fetch-depth: 2`, while `build` (and api-docs/format on the PR arm) check out
`fetch-depth: 0`. The script applied a depth chosen for the first group to all of
them.
For `build` that grafted its complete clone on every push to main, so the
`git describe --tags` in the immediately following `Compute version and tags`
step found no reachable tag, and a `|| echo v0.0.0` fallback that cannot fail
turned that into a version: every `:latest` image shipped
`InformationalVersion 0.0.0-<sha>`.
Both fetch sites now go through `fetch_ref`, which passes `--depth` only when
`git rev-parse --is-shallow-repository` answers `true`. That also closes the PR
arm's `--depth=200` — the same defect with a larger N, grafting api-docs/format.
An unknown answer passes no depth: the query fails only where there is no
readable repository, and a depth cannot help there either.
Second half: `Compute version and tags` no longer defaults. A failed
`git describe` on the non-tag path fails the job rather than stamping 0.0.0, so
no `:latest` is published instead of a mislabelled one. `build` is not a required
status context, so this cannot block a merge. The tag path never calls
`describe`, so `:prod`/`:<version>` releases are unaffected — which is also why
they were correctly versioned throughout.
Measured, not inferred:
- The live defect, from run 2416 / job 10345 (push to main 8aeacd534):
`INFO_VERSION=0.0.0-8aeacd53`.
- Against a real anonymous clone of this repository: the predecessor detector
grafts it and `describe` fails -> `0.0.0-1afad085`; the fixed detector leaves
it complete -> `26.14.0-1afad085`.
- The workflow step body executed on both clones: grafted -> exit 1 with a
diagnostic, complete -> `26.14.0-1afad085`, tag path -> `26.14.0` + `:prod`
even on a grafted clone.
New guard `scripts/tests/test_docs_only_detector_clone_depth.py` drives the real
script over real `file://` clones (a plain path clone silently ignores --depth,
so it carries a negative control that requires the fixture to be able to graft at
all). It was witnessed red against the predecessor. Its clause mutation is
declared in mutation_manifest.py, which promotes the detector's guard-inventory
row from NONE to MUTATION; the stated reason it was undeclared — that a
skip-gate script's effect is visible only in a workflow run — was itself wrong.
fixes#836
Decisions-Edit: yes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HETQK67q1bEpDZpHLZNsp9
`ruff format --check` was the only red in `script-tests` — `ruff check` passed on
all 51 tracked files and pytest never ran. Purely cosmetic: the repo's ruff config
allows a wider line than the hand-wrapping used, so several call sites collapse to
one line.
Verified with the version CI pins (ruff 0.12.11) over the same population the job
derives (`git ls-files -z '*.py' '*.pyi' '*.ipynb'` under bash, 51 files): both
`ruff check` and `ruff format --check` pass. Suite unchanged at 1237 passed,
2 skipped.
A follow-up commit rather than an amend: the branch is already pushed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HETQK67q1bEpDZpHLZNsp9
CI red: `git commit -q -m docs on the PR head failed: Author identity unknown`.
The fixtures set `user.email`/`user.name` on the ORIGIN repos, but the PR-arm test
commits in a CLONE, which inherited nothing. A developer checkout has a global
identity and the CI container has none, so the file was green here and red there.
Fixed at the boundary rather than the site: `_GIT_CONF` is passed to EVERY git
invocation in the file, so no repository it creates can depend on ambient config.
It also disarms a global `core.hooksPath` (which would fire this repo's husky hooks
against throwaway fixtures) and `commit.gpgsign` (which would block on a pinentry
prompt no pytest timeout is watching). The now-redundant per-fixture `git config`
calls are removed.
`_env()` additionally points `GIT_CONFIG_GLOBAL`/`GIT_CONFIG_SYSTEM` at os.devnull,
so the ambient configuration is UNAVAILABLE rather than merely overridden.
That alone did NOT reproduce CI, and the first version of this commit claimed it did:
with the identity flags removed the suite still passed here, because a Mac invents an
identity from the OS user and hostname. `user.useConfigOnly=true` is what closes it.
Verified by removing the identity pair and watching the file go red locally with the
same `Author identity unknown` CI produced — so a local run now catches this class
instead of deferring it to the runner.
Gate: ruff check + format --check pass over all 51 tracked files with the CI-pinned
0.12.11; suite 1237 passed, 2 skipped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HETQK67q1bEpDZpHLZNsp9
Head d44d9eb55 is 008860c2b rebased onto e8f80c42c (#883). All six shipped artifacts byte-identical to the reviewed content; the only edit is the guard-inventory summary conflict, resolved by DERIVING the six counts from the table rather than hand-merging two deltas, then verified by test_the_summary_counts_match_the_table. Gate: ruff check + format --check over 52 tracked files, suite 1250 passed 2 skipped.
Review-verdict: MERGEABLE @ d44d9eb
Head d44d9eb55 is 008860c2b rebased onto e8f80c42c (#883). All six shipped artifacts byte-identical to the reviewed content; the only edit is the guard-inventory summary conflict, resolved by DERIVING the six counts from the table rather than hand-merging two deltas, then verified by test_the_summary_counts_match_the_table. Gate: ruff check + format --check over 52 tracked files, suite 1250 passed 2 skipped.
timothy
merged commit 94a3d13495 into main2026-08-30 01:55:30 +02:00
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.
fixes #836
git fetch --depth=Ngrafts a complete clone shallow — it writes.git/shallowand cuts history at N even though every object is already present.scripts/ci-detect-docs-only.shhas four consumers indocker-build.ymlthat disagree on checkout depth (test/migrations/functional-e2eatfetch-depth: 2,buildatfetch-depth: 0), and it applied a depth chosen for the first group to all of them.That grafted
build's complete clone on every push tomain, so thegit describe --tagsin the immediately followingCompute version and tagsstep found no reachable tag, and a|| echo v0.0.0fallback that cannot fail turned it into a version. Every:latestimage published frommaincarriedInformationalVersion 0.0.0-<sha>instead of26.x.y-<sha>, from 2026-07-17 (#416) until this change, with nothing red anywhere.What changed
scripts/ci-detect-docs-only.sh— both fetch sites go throughfetch_ref, which passes--depthonly whengit rev-parse --is-shallow-repositoryanswerstrue. Any other answer passes none..gitea/workflows/docker-build.yml— the non-tag path no longer defaults. A failedgit describefails the job with a diagnostic, so no:latestis published rather than a mislabelled one.buildis not a required status context, so this cannot block a merge; the tag path never callsdescribe, so releases are unaffected.scripts/tests/test_docs_only_detector_clone_depth.py, a declared clause mutation inmutation_manifest.py, and inventory rows — promoting the detector fromNONEtoMUTATION.docs/ci-cd.md(its prose described the defect in the present tense) and a new recordci.fetch-depth-never-grafts-a-complete-clone.Measured, not inferred
main8aeacd534) →INFO_VERSION=0.0.0-8aeacd53.actions/checkout's "Removing auth" does succeed — the graft is real. The checkout log also shows tags being fetched, which rules out "the tags were never there".describefails →0.0.0-1afad085; fixed detector → complete,describe→26.14.0-1afad085.exit 1with a diagnostic and no image; complete →26.14.0-…; tag path →:prod+26.14.0, unaffected even on a grafted clone.origin/main's detector: 4 failed, 2 passed (the two that pass are the negative control and the shallow-unchanged case, both correctly script-independent).Scope note, corrected by review
The PR arm's
--depth=200was inert, not latently firing: onpull_requestthe only jobs running this script are the three shallow ones.api-docs/formathave a step withid: detectbut it runs their own inline diff — an earlier draft of this change read them as consumers and said so in the decision record. The depth is dropped there anyway because the clause is shared and the nextfetch-depth: 0consumer would inherit the graft silently. Likewise, keeping the depth for the shallow three is conservative rather than required: a depth-less fetch into a--depth=2clone leaves.git/shallowin place, still writesFETCH_HEAD, and still reports the parents the push arm reads.Verification
scripts/testsfull suite: 1237 passed, 2 skipped, on base736649b3b.test_mutation_harness.py36/36).decisions_validate.pyOK · catalog up to date ·check-doc-narrative.py0 warnings ·shellcheckclean · no.cstouched, so the BOM/format gate does not apply.Post-merge
Read
InformationalVersionback out of the newly built:latestimage and record it in the issue's## Closing record. That check is deliberately not a## Done-whenbox:buildonly runs on a push tomain, so it could never be ticked before the merge it would gate.🤖 Generated with Claude Code
https://claude.ai/code/session_01HETQK67q1bEpDZpHLZNsp9
Review-verdict: MERGEABLE @
58d2c14Two cold review rounds; round 2 clean at BLOCKER/HIGH/MEDIUM. Suite 1237 passed on base 736649b3b; guard witnessed red against the real predecessor.
Review-verdict: MERGEABLE @
b8af3bfHead
b8af3bf4aadds only a ruff format pass over the new guard file: one file, ASTs identical to the reviewed58d2c147a, so the round-2 clean verdict carries. ruff check + format --check pass over all 51 tracked files with the CI-pinned 0.12.11; suite 1237 passed, 2 skipped.Review-verdict: MERGEABLE @
008860cHead
008860c2bfixes a CI-only red in the new guard's fixtures (git identity in a clone). Boundary fix in the test harness only; the shipped script and workflow are byte-identical to the reviewed58d2c147a. Local run now reproduces the CI condition — verified by removing the identity flags and witnessing the same 'Author identity unknown'.008860c2bbtod44d9eb55fReview-verdict: MERGEABLE @
d44d9ebHead
d44d9eb55is008860c2brebased ontoe8f80c42c(#883). All six shipped artifacts byte-identical to the reviewed content; the only edit is the guard-inventory summary conflict, resolved by DERIVING the six counts from the table rather than hand-merging two deltas, then verified by test_the_summary_counts_match_the_table. Gate: ruff check + format --check over 52 tracked files, suite 1250 passed 2 skipped.timothy referenced this pull request2026-08-30 22:58:57 +02:00