Repository navigation
Commit c229223
Part of #16644
Clause-②: no
## What this changes
Every `git` child these files spawn against a **throwaway** repository
now carries an
environment of its own — `gitFreeEnv()` from `scripts/git-env.mjs`, the
blanket `GIT_*`
strip #16624 landed and #16753 applied in Tier A. The judgement is made
**per spawn
point**, not per file: a child that operates on the repository its `cwd`
and arguments
name gets the strip; a child that talks to a remote keeps the ambient
environment and is
marked as doing so.
⚠️ **This is prevention, not a repaired outage.** No failure has been
measured in any of
these files — the card is observation-class and says so. The mechanism
was measured on
#16624: 8,190 paths staged as deleted in a shared index, and `core.bare
= true` written
into the `.git/config` every linked worktree of that clone reads, from a
self-test that
printed ticks throughout. What follows is that shape, reproduced against
a disposable
stand-in and then closed.
### Scope, and why it is wider than the card's Tier B
- **Tier B, 13 files under `scripts/**`** — the card's enumeration,
re-measured on
`origin/main` @ `5d0ee8f60` as still bare.
- **`packages/spec/scripts/build-schemas-check-mode.test.ts` and
`packages/spec/scripts/sharded-artifacts.test.ts`** — added on the
**triage ruling**
(comment 5578727562, acceptance point 4): 「`packages/spec` 两个文件收敛到
`gitFreeEnv()`,`LEAKED_GIT_ENV` 一并删除。⛔ 不要保留"两种都行"的过渡态」. Their
hand-maintained ten-name allowlist of `GIT_*` location variables is
gone; the reason it
goes rather than gains an entry is the card's own: an allowlist has to
be kept level
with git's list, and the key it misses is the key that bites.
- ⛔ **Not touched**: Tier A (`scripts/git-merge-regen.mjs`,
`scripts/check-regen-pending.mjs`) — already done by **#16753**, which
triage split out
of this card as `priority:p1`; and Tier C, which contains
`scripts/pm/**` — the
`domain:skills` lane.
- ⛔ **No repo-wide gate here**, per triage acceptance point 5: it would
land red and get
weakened. It belongs after Tier C, on its own card.
## Per spawn point: local-only or network-touching
`gitFreeEnv()` is applied ONLY to the local-only column. The boundary is
triage's,
fixed verbatim from the card: this container really does carry
`GIT_CONFIG_COUNT` with
`GIT_CONFIG_KEY_0=credential.interactive`,
`GIT_CONFIG_KEY_1/2=url.https://github.com/.insteadOf`
and `GIT_SSL_CAINFO=/root/.ccr/ca-bundle.crt` (read off the box while
writing this), so a
network child stripped of them loses its transport.
| file | local-only spawn points, now stripped | left ambient, and why |
|:--|:--|:--|
| `scripts/ablation-dist-preflight.mjs` | `status --porcelain -z`; `show
HEAD:PATH`; the self-test's `git()` over a mkdtemp corpus | — |
| `scripts/check-adr-0087-registration.mjs` | the `git(args, cwd)`
helper (every argv form); `cat-file --batch` | — (the `fetch
--unshallow` texts are remedy prose, not spawns) |
| `scripts/check-bash32-floor.mjs` | `listPopulation`'s `ls-files -z`;
`fixtureRepo`'s `init -q` and `add -A`; the index-vs-disk control's
`ls-files` | — |
| `scripts/check-changeset-no-major.mjs` | the one `git()` helper:
`init` / `config` / `add` / `commit` / `diff` / `show` / `rev-parse` /
`merge-base`, **and** the #4690 leg's `fetch` whose remote is a local
mkdtemp PATH | — no https/ssh child exists in this file |
| `scripts/check-empty-changeset.mjs` | the one `git()` helper, same
census, two local-path `fetch` legs | — |
| `scripts/check-engine-split-ratio.mjs` | the main `git` helper; the
self-test `g` helper (`init`/`config`/`add`/`log` + two `clone` from a
`file://` URL under its own mkdtemp); the dated `commit`; the `--cwd`
re-entry child | — |
| `scripts/check-nul-bytes.mjs` | `lsFiles`; `repoRoot`; the self-test's
`init`, `config`, `add -A -f`, `ls-files`, `add -A` | — |
| `scripts/check-skill-frame-freshness.mjs` | `rev-parse` / `init` /
`config` / `remote add` / `update-ref` / `commit`, through a `git()`
helper that strips by default | ⛔ **`fetch --quiet --no-tags origin
main` — NETWORK-TOUCHING.** Marked `⛔ AMBIENT ENVIRONMENT ON PURPOSE`,
reached through an explicit `network: true`. Stripped, this gate would
degrade to its offline rung on every run: a warning where a verdict
belongs |
| `scripts/check-type-check-coverage.mjs` | `check-ignore --stdin -z`;
`readIgnoredPaths`' `ls-files --others --ignored`; the self-test's `g` |
— (`tsc` children are not `git`) |
| `scripts/docs-audit/check-drift-comment.mjs` | `GIT_ENV`'s base, so
the fixture `git()` helper and the mapper child both inherit the strip |
— the identity and `GIT_CONFIG_GLOBAL/SYSTEM` pins are DELIBERATE and
re-applied on top of it |
| `scripts/objectui-changeset-digest.mjs` | the `git(cwd, args)` helper;
the `clone` from a `file://` mkdtemp source; `rev-parse HEAD`;
`merge-base --is-ancestor`; and the **six** `bash bump-objectui.sh`
fixture drivers, whose env BASE is now stripped because that script
spawns `git` one frame down | two `bash` capability probes carrying
`BASH_ENV` — they spawn **no** `git` at all, so they are not spawn
points for this card |
| `scripts/objectui-range.mjs` | the `git(cwd, args)` helper; the
end-to-end `node` re-entry child | — |
| `scripts/collect-release-notes.sh` | one `unset` of every ambient
`GIT_*` name, before any child: all four report sections read checkouts
by path, and the self-test's `init`/`add`/`commit`/three `clone`s/one
`fetch` are all under its own `mktemp` root with a `file://` remote | —
the per-command `GIT_AUTHOR_DATE=… git commit` prefixes in the fixture
loop are applied per invocation, after that line, and are deliberately
unaffected |
| `packages/spec/scripts/build-schemas-check-mode.test.ts`,
`…/sharded-artifacts.test.ts` | `HERMETIC_ENV` is now built from
`gitFreeEnv()`; every fixture git and every generator run inside one
operates on an `fs.mkdtemp` directory | — `GIT_CONFIG_GLOBAL` /
`GIT_CONFIG_SYSTEM` / `GIT_CONFIG_NOSYSTEM` stay set on top: they are
the file's own "read no config" pins, not inherited leakage |
## 验收备注
Triage's six acceptance points (comment 5578727562), and what was
measured against each.
**1. 先红后绿, every batch.** A one-time instrument built a **disposable**
stand-in
repository (`git init` + a tracked file + a commit), pointed the
hook-exported location
variables at it, ran each file's temp-repo path, and compared a
fingerprint of the
stand-in's `config`, index (`ls-files`), refs (`for-each-ref`) and
`HEAD` before and
after. ⭐ The stand-in is disposable on purpose: pointing those variables
at this checkout
would not test the incident, it would be the incident, for every agent
on the box — the
same discipline `scripts/git-merge-regen.mjs` states for its own probe.
Two leak shapes were run, because they are not interchangeable and
`scripts/git-env.mjs`
records why: with `GIT_WORK_TREE` also set, git resolves the work tree
to the stand-in and
`add -A` re-adds its own files, so the index comes back unchanged and a
case can pass
proving nothing. `GIT_DIR` alone is the incident's shape.
- **Firing control, per probe**: before any verdict, a plain `git
rev-parse
--absolute-git-dir` in an unrelated directory had to answer with the
stand-in's git dir.
It did, in all 52 probes. A control that does not fire measures nothing.
- **RED, the UNFIXED files** (restored one at a time from the merge base
`5d0ee8f60`,
each proven on disk by comparing `git hash-object` against that blob,
then restored from
`HEAD` and proven restored by an empty `git diff HEAD`): **12 of 13
wrote the
stand-in** under `GIT_DIR` alone — config in 10, index in 11, refs in
10, `HEAD` in 4.
⭐ Three of them (`ablation-dist-preflight`,
`check-adr-0087-registration`,
`docs-audit/check-drift-comment`) **exited 0 while doing it** — #16624's
signature
exactly: a green self-test writing another repository. The 13th,
`check-nul-bytes.mjs`, did not write the stand-in; it exited **1**,
because the leaked
`init` created no repository in the directory it was handed and the
fixture could not be
built. Red in a different key, and stated as such rather than counted as
a write.
- **GREEN, on this branch**: all 13 leave the stand-in
**byte-identical** under BOTH leak
shapes (26 probes), and all 26 child runs **exit 0** — the isolation is
complete enough
that the self-tests still pass with a leaked git environment in the
process.
- Every probe also fingerprinted the real checkout (`status --porcelain`
+ the shared
`config`) before and after. It never moved, in any of the 52 probes, and
the whole-tree
`git status --porcelain` after the red leg was empty.
**2. Negative control.** With those variables absent — ordinary CI and
hand runs — every
changed file behaves as today: 19 invocations (each gate's **bare** call
and its
`--self-test`, both batteries, never `--self-test` alone), all exit 0,
real batteries
observed in the logs (`check-nul-bytes` 75 assertions,
`check-bash32-floor` 177 cases,
`check-type-check-coverage` 55+97+56+28+19+18 cases, `objectui-range`
all checks,
`collect-release-notes` all cases). The two `packages/spec` files: **2
files / 114 tests
passed**, 487s.
**3. Per spawn point, local-only vs network-touching.** The table above.
One
network-touching child in the whole surface, and it keeps the ambient
environment.
**4. `packages/spec` converged, allowlist deleted.** Both files now
build `HERMETIC_ENV`
from `gitFreeEnv()`; the allowlist constant is gone from both and is not
re-spelled in
either file, so a census of the retired shape does not match the
paragraph that explains
it. ⛔ No fourth spelling was introduced. Equivalence is structural
rather than argued: the
ten names the allowlist removed are a strict subset of "every
`GIT_`-prefixed key", so the
new fixture environment is a subset of the old one, and the 114 tests
above pass on it.
**5. No repo-wide gate.** None added.
**6. Closing count, with the card's own classifier — and it is NOT
zero.** Reported in
the report and below: the classifier's spawn probe was run in the
**argv-array** form
(`'git'` plus a separate `init`/`add`/`ls-files` probe), never as a
command string,
because triage's own first measurement was a false negative for exactly
that reason and
「对照不发火的零什么都没测到」.
## Acceptance notes
⚠️ **One finding, filed rather than fixed here:
`packages/create-objectstack/src/template-consistency.test.ts` carries a
THIRD copy of the
retired `GIT_*` allowlist** (same ten names, same `for … delete
env[key]` shape). Triage's
census of that shape on 2026-09-08 found two carriers and named both;
this one is not on
its list. It is left out of this PR deliberately — converging it would
add a package, and
therefore a verification surface, that neither triage nor the dispatch
declared — and it
is reported for the Tier C lap so the "one spelling, not two" ruling can
actually close.
Until it lands, the allowlist shape survives in one file.
Noted, not filed: nothing else.
## Local verification, and what is declared to CI
- `dispatch-gates --commands` re-derived in this worktree against the
real changed set
(15 paths, merge base `5d0ee8f60`, three-dot) → 82 commands; run, with
results in the
report. `pnpm check:pm-dispatch-gates` was detached and waited on with
`tail --pid`, per
its recorded 430–450s prescription.
- ⛔ Declared to CI, not run locally: the 50 artifact-roster families,
the 11
wide-population families, the 5 workflow-valued families and the 5
path-scheduled CI
jobs `dispatch-gates` names as outside its runnable list, and the
repo-wide `pnpm lint`.
- `skip-changeset`: nothing published moves. `packages/spec`'s `files[]`
does not list
`scripts/`, and repo-root `scripts/**` is inside no package.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk)_
---
## PM addendum — the declared surface, corrected (`domain:devx` 执行席,
2026-09-19T04:48Z)
⚠️ **This body undercounts its own surface.** The API reads **17 changed
files**; the body says 16 in one place and "15 paths" in the
verification section. The author declined to PATCH the body under its
write budget and put the reading in the commit message instead, so the
PM records it here rather than leaving a reviewer to reconcile it.
The two files beyond the originally dispatched 13 `scripts/**` + 2
`packages/spec` fixtures, both **forced** rather than chosen:
1. `packages/spec/vitest.repo-tests.json` (+1 line) — **verified by the
PM's own ablation** at 2026-09-19T04:15:38Z: with the line removed
`check-cross-package-test-inputs` exits **1** naming
`scripts/sharded-artifacts.test.ts`; with it, **0**. The blob was
restored to its pristine hash and `git diff HEAD` was clean.
2. `scripts/git-env.d.mts` (43 lines) — the sanctioned shape, not a new
invention. **PM control, read at 2026-09-19T04:47:44Z**:
`packages/spec/scripts/build-schemas-check-mode.test.ts` imports
`git-env.mjs` at `:57` and `check-regen-pending.mjs` at `:58` — adjacent
lines, same file, same TS program, same lane — and only the second had a
sibling `.d.mts`. Nine such mirrors existed before this PR; there are
now ten, and `check-declaration-mirrors` **discovers** its corpus (both
batteries exit 0 on this head).
⛔ The PM has **kept** the mirror rather than reverting it: the two
remedies that avoid a new file are a `@ts-expect-error` suppression and
duplicating the retired allowlist back into the fixtures, and both are
refused — the first trades a red for a suppression, the second reverses
triage's 「收敛到 `gitFreeEnv()`,退役 allowlist」 ruling.
---
_Generated by [Claude Code](https://claude.ai/code)_
---------
Co-authored-by: claude <noreply@anthropic.com>
1 parent 57cfedd commit c229223
17 files changed
Lines changed: 268 additions & 66 deletions
File tree
- packages/spec
- scripts
- scripts
- docs-audit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| 57 | + | |
57 | 58 | | |
58 | 59 | | |
59 | 60 | | |
| |||
232 | 233 | | |
233 | 234 | | |
234 | 235 | | |
235 | | - | |
236 | | - | |
237 | | - | |
238 | | - | |
239 | | - | |
240 | | - | |
241 | | - | |
242 | | - | |
243 | | - | |
244 | | - | |
245 | | - | |
246 | | - | |
247 | | - | |
248 | | - | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
249 | 249 | | |
250 | 250 | | |
251 | 251 | | |
252 | | - | |
253 | | - | |
| 252 | + | |
254 | 253 | | |
255 | 254 | | |
256 | 255 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| 42 | + | |
| 43 | + | |
42 | 44 | | |
43 | 45 | | |
44 | 46 | | |
| |||
411 | 413 | | |
412 | 414 | | |
413 | 415 | | |
414 | | - | |
415 | | - | |
416 | | - | |
417 | | - | |
418 | | - | |
419 | | - | |
420 | | - | |
421 | | - | |
422 | | - | |
423 | | - | |
424 | | - | |
425 | | - | |
426 | | - | |
427 | | - | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
428 | 429 | | |
429 | 430 | | |
430 | 431 | | |
431 | | - | |
432 | | - | |
| 432 | + | |
433 | 433 | | |
434 | 434 | | |
435 | 435 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
211 | 211 | | |
212 | 212 | | |
213 | 213 | | |
| 214 | + | |
214 | 215 | | |
215 | 216 | | |
216 | 217 | | |
| |||
450 | 451 | | |
451 | 452 | | |
452 | 453 | | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
453 | 459 | | |
454 | 460 | | |
455 | 461 | | |
| |||
475 | 481 | | |
476 | 482 | | |
477 | 483 | | |
| 484 | + | |
| 485 | + | |
478 | 486 | | |
479 | 487 | | |
480 | 488 | | |
| |||
714 | 722 | | |
715 | 723 | | |
716 | 724 | | |
717 | | - | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
718 | 728 | | |
719 | 729 | | |
720 | 730 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
365 | 365 | | |
366 | 366 | | |
367 | 367 | | |
| 368 | + | |
368 | 369 | | |
369 | 370 | | |
370 | 371 | | |
| |||
2002 | 2003 | | |
2003 | 2004 | | |
2004 | 2005 | | |
| 2006 | + | |
| 2007 | + | |
| 2008 | + | |
| 2009 | + | |
| 2010 | + | |
2005 | 2011 | | |
2006 | 2012 | | |
2007 | 2013 | | |
2008 | 2014 | | |
2009 | | - | |
| 2015 | + | |
2010 | 2016 | | |
2011 | 2017 | | |
2012 | 2018 | | |
| |||
2035 | 2041 | | |
2036 | 2042 | | |
2037 | 2043 | | |
2038 | | - | |
| 2044 | + | |
2039 | 2045 | | |
2040 | 2046 | | |
2041 | 2047 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
212 | 212 | | |
213 | 213 | | |
214 | 214 | | |
| 215 | + | |
215 | 216 | | |
216 | 217 | | |
217 | 218 | | |
| |||
847 | 848 | | |
848 | 849 | | |
849 | 850 | | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
850 | 856 | | |
851 | 857 | | |
852 | 858 | | |
| |||
953 | 959 | | |
954 | 960 | | |
955 | 961 | | |
956 | | - | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
957 | 966 | | |
958 | 967 | | |
959 | 968 | | |
960 | 969 | | |
961 | | - | |
| 970 | + | |
962 | 971 | | |
963 | 972 | | |
964 | 973 | | |
| |||
1402 | 1411 | | |
1403 | 1412 | | |
1404 | 1413 | | |
1405 | | - | |
| 1414 | + | |
1406 | 1415 | | |
1407 | 1416 | | |
1408 | 1417 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
383 | 383 | | |
384 | 384 | | |
385 | 385 | | |
| 386 | + | |
386 | 387 | | |
387 | 388 | | |
388 | 389 | | |
| |||
469 | 470 | | |
470 | 471 | | |
471 | 472 | | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
472 | 480 | | |
473 | 481 | | |
474 | 482 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
231 | 231 | | |
232 | 232 | | |
233 | 233 | | |
| 234 | + | |
234 | 235 | | |
235 | 236 | | |
236 | 237 | | |
| |||
294 | 295 | | |
295 | 296 | | |
296 | 297 | | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
297 | 303 | | |
298 | | - | |
| 304 | + | |
299 | 305 | | |
300 | 306 | | |
301 | 307 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
| 76 | + | |
76 | 77 | | |
77 | 78 | | |
78 | 79 | | |
| |||
166 | 167 | | |
167 | 168 | | |
168 | 169 | | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
169 | 174 | | |
170 | | - | |
| 175 | + | |
171 | 176 | | |
172 | 177 | | |
173 | 178 | | |
174 | 179 | | |
175 | 180 | | |
176 | | - | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
177 | 186 | | |
178 | 187 | | |
179 | 188 | | |
| |||
298 | 307 | | |
299 | 308 | | |
300 | 309 | | |
301 | | - | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
302 | 315 | | |
303 | 316 | | |
304 | 317 | | |
305 | 318 | | |
306 | 319 | | |
307 | 320 | | |
308 | 321 | | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
309 | 326 | | |
310 | 327 | | |
311 | 328 | | |
| |||
330 | 347 | | |
331 | 348 | | |
332 | 349 | | |
333 | | - | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
334 | 355 | | |
335 | 356 | | |
336 | 357 | | |
| |||
0 commit comments