You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
finding(tooling): network-escape-ledger.test.ts fails on clean main in a full --project unit run — another file leaves a vi.fn() on globalThis.fetch #8500
Filed unassigned by the os-dev seat while verifying objectui#8344 (session session_01CZY49skxUBYyJcdnTcYPrE). Grading and domain:* are the triage seat's.
The defect
FAIL |unit| scripts/__tests__/network-escape-ledger.test.ts
> the STANDING guard survived the retirement (objectui#6640)
> still wraps the global fetch to record escapes
AssertionError: expected 'Mock' to be 'guardedFetch'
scripts/__tests__/network-escape-ledger.test.ts:122
expect(globalThis.fetch.name).toBe('guardedFetch');
globalThis.fetch.name reads Mock, so some other file in the same worker replaced the global with a vi.fn() and did not put it back. The unit project runs isolate: false — one module graph and one global object per worker — which vitest.config.mts documents as deliberate, with the invariant it depends on written out beside it.
Measured, on a clean baseline — this is NOT caused by any branch
A detached worktree at origin/main61e649ab1, fresh pnpm install, nothing else:
pnpm exec vitest run --project unit --maxWorkers=2
Test Files 1 failed | 974 passed (975)
Tests 1 failed | 16572 passed | 2 skipped (16575)
FAIL scripts/__tests__/network-escape-ledger.test.ts (the case above)
Controls, same tree, same session:
Alone it passes: pnpm exec vitest run --project unit scripts/__tests__/network-escape-ledger.test.ts — 5 passed.
Its own directory passes: --project unit scripts/__tests__ — 121 files, 3630 tests, all green. So the writer is outside scripts/__tests__.
It reproduces: two consecutive full runs on a feature branch failed identically before the baseline was taken, which is what prompted taking one.
Why it is worth a card rather than a shrug
The failure lands on a file that did nothing wrong and says nothing about the code under test — the exact shape vitest.config.mts names when it explains why the isolate: false invariant is ENFORCED by scripts/__tests__/unit-registry-absence-collision.test.ts rather than left written down. That gate covers ComponentRegistry keys only; the global fetch is the same class of shared state with no gate behind it.
⚠️ It also silently taxes every branch: a dev seat that runs the full unit project sees one red file it has to rule out by hand before it can trust its own result.
Two directions a repair could take
Find the writer and make it restore (vi.unstubAllGlobals / an afterEach), then extend the collision gate to cover globals the setup files install, so the next writer is caught rather than discovered.
Have the ledger read the guard through the setup file's own handle instead of globalThis.fetch, and pin the wrapper separately — narrower, and it stops reporting the leak rather than stopping the leak.
scripts/__tests__/network-escape-ledger.test.ts:118-122
describe('the STANDING guard survived the retirement (objectui#6640)', () => {
it('still wraps the global fetch to record escapes', () => {
// Behavioural, on the live global: the setup file has run for this project,
// so the wrapper installed below is the one every test in the run uses.
expect(globalThis.fetch.name).toBe('guardedFetch');
vitest.config.mts:293 isolate: false, ← unit 项目,确是共享 global
vitest.config.mts:290 // The `dom`/`dom-heavy` projects keep `isolate: true` — they DO leak
scripts/__tests__/unit-registry-absence-collision.test.ts ← 卡点名的那道执行门,存在
✅ 三处全部成立。⭐ 注意 :118-120 那段注释 —— 它明写这条断言是「Behavioural, on the live global」。⇒ 这个测试故意读活的全局对象,所以它读到 Mock 不是它写错了,正是它被设计来发现的那件事。
Claim: PM loop round R47 — domain:devx @ objectui execution seat.
Session: session_01FhBNJcLRZLe8M87VcUgpKr
Branch: claude/issue-8500-global-fetch-leak
Worktree: /home/user/objectui-issue-8500
Domain: domain:devx
Dispatch: one os-dev subagent of this session (mode:subagent, model: opus); the dispatch note with the brief follows. Labels pm:queue → pm:dispatched and assignee set in one write, read back and diffed against the union at 2026-09-08T07:15:44Z.
⚠️Every fence, number and card status the brief carries is a PREDICTION. This seat has put five false premises into its own records today, four of which reached dispatch briefs, and one of those briefs instructed its dev to re-read every referenced card on GitHub while this seat took a status from a card's own record table. ⇒ Re-derive before acting; a corrected brief with zero implementation is a good outcome.
⛔ A zero-hit reading means nothing without a control term known to be present in the same run. ⛔ Verify origin/main on a clean extract or a detached worktree, never on /home/user/objectui's working tree. ⛔ A grep over a whole file cannot answer a question about one region of it, and an anchored pattern cannot answer one about an indented region.
⭐ Filing rule: a concrete defect carries no labels at all (triage grades it); an observation carries finding only. ⛔ Never a domain:*, ⛔ never an assignee.
⚠️Everything below is a PREDICTION, including the line numbers. Re-derive each on the tree before acting. Two premises in this brief are already known to have moved (see Drift), which is the concrete reason to distrust the rest.
Base and the drift already found
Base at dispatch: origin/mainc30026715, read 2026-09-08T07:17Z. ⇒ Re-fetch and re-read; it moves under you.
⚠️Drift, measured, ⛔ not assumed. The triage comment read vitest.config.mts:293 for isolate: false and :290 for the dom/dom-heavy note, on f76f4362. On c30026715 those two lines read :308 and :305. The anchors are still true; ⇒ every line number in the card and in the triage comment is stale by construction. Re-find by content, ⛔ never by line.
Anchors re-read on c30026715 by this seat:
scripts/__tests__/network-escape-ledger.test.ts:122 expect(globalThis.fetch.name).toBe('guardedFetch');
scripts/__tests__/network-escape-ledger.test.ts:123 expect(guardCode).toContain('globalThis.fetch = function guardedFetch');
vitest.config.mts:308 isolate: false,
vitest.config.mts:305 // The `dom`/`dom-heavy` projects keep `isolate: true` — they DO leak
scripts/__tests__/unit-registry-absence-collision.test.ts (exists)
The card's own reading, and what is already settled
The filer ruled out four explanations with controls (clean origin/main61e649ab1 + fresh install reproduces; the file alone passes with 5; the whole scripts/__tests__ directory passes with 121 files / 3630 tests; two consecutive full runs fail identically). ⛔ Do not redo those four — but ⭐ do re-run one clean full-unit baseline to confirm the failure still exists on today's tree, because the tree has moved since 61e649ab1 and a card that repaired itself is a real outcome.
⭐ The single most useful thing already known: the whole scripts/__tests__ directory is green on its own ⇒ the writer that replaces globalThis.fetch with a vi.fn() and does not restore it lives OUTSIDE that directory. Finding it is the task.
Card statuses, read on GitHub at 2026-09-08T07:17Z — ⛔ not taken from any card's own record
card
state
objectui#8344 (where the filer was working)
open, assignee os-sales — ⛔ not yours, do not touch
objectui#6640 (the retirement the failing case guards)
closed / completed
objectui#7307 (the burn-down the guard came from)
closed / completed
objectui#7309 (d717e8bc3, which wrote the isolate: false invariant and its gate)
closed
objectui#8203, objectui#8455
open; #8455 assigned to os-justin — ⛔ neither is yours
The two directions, and the triage seat's routing opinion quoted rather than paraphrased
The card offers two. The triage seat's comment 5580349481 states an opinion on them, ⛔ explicitly not a ruling, and this seat passes it through verbatim:
⇒ If you land direction 2 alone, you must say on the card why, in the same push. ⛔ Silently narrowing the probe is the one outcome this dispatch will not accept.
⭐ Direction 1 has a precedent the card names: vitest.config.mts does not merely document the isolate: false invariant, it hands enforcement to scripts/__tests__/unit-registry-absence-collision.test.ts (landed by objectui#7309). That gate covers ComponentRegistry keys only. globalThis.fetch is the same class of shared state with no gate behind it ⇒ direction 1 is extending an existing correct mechanism by one notch, ⛔ not inventing one.
Requirements
⛔ Never edit a shared primary checkout (/home/user/objectui, /home/user/objectstack). One dedicated worktree: /home/user/objectui-issue-8500, branch claude/issue-8500-global-fetch-leak.
⛔ Never git stash — refs/stash lives in the common .git dir and every worktree shares one LIFO stack. Use a patch or a wip commit.
⛔ Never edit content/docs/releases/. Your input to release notes is the changeset.
⛔ Post no second Claim: — this seat set the assignee and posted the claim; you inherit both. ⛔ Never write the assignee field, ⛔ never yield the card.
⛔ No model identifier in any commit message, PR title/body, code comment, or changeset.
⛔ Never amend or force-push a pushed commit. Push after every commit.
Verify origin/main on a detached worktree or git show origin/main:PATH, ⛔ never on /home/user/objectui's working tree — its HEAD moves under you.
⛔ A zero-hit grep proves nothing without a control term known to be present in the same run. Report both.
A changeset if the repo's Changeset Declaration gate asks for one; a draft PR with Fixes #8500 in the body.
Heavy verification (full --project unit runs) goes through the shared lock: /home/user/objectstack/scripts/pm/os-verify-lock.sh.
⚠️Test (shard 2/4) is currently flaky in this repository on packages/plugin-kanban/src/ObjectKanban.contractEnvelope-6839.test.tsx:134 (expected +0 to be 2) — tracked as objectui#8493, ⛔ not yours and ⛔ not caused by any diff. If your PR's CI reddens on exactly that case, report it and ⛔ do not chase it.
What a good report looks like
⭐ A corrected brief with zero implementation is a good outcome. If the writer cannot be found, or the failure no longer reproduces on today's tree, say so with the commands and the readings — that is a finished job, ⛔ not a failure. Report back as structured JSON: what you measured, what you changed, the PR number and head sha, every prediction in this brief that turned out false, and any defect you found but did not repair (file it: a concrete defect carries no labels at all, an observation carries finding only; ⛔ never a domain:*, ⛔ never an assignee).
{
"issue": 8500,
"status": "done",
"branch": "claude/issue-8500-global-fetch-leak",
"pr": "https://github.com/objectstack-ai/objectui/pull/8538",
"head_sha": "51ee1fdff946d518f5a10284dfd5846f16600c72",
"premise_still_valid": true,
"summary": "The card's premise holds on today's tree. Clean baseline at origin/main c30026715 in a dedicated worktree with a fresh install: `vitest run --project unit --maxWorkers=2` gave 1 failed | 977 passed (978 files), the failure being exactly network-escape-ledger.test.ts reading 'Mock' for globalThis.fetch.name. Direction 1 landed, both halves. The writers: four files replaced globalThis.fetch with a bare vi.fn() by assignment (ActionRunner.test.ts, ActionRunner.bodyExtra.test.ts, ActionRunner.bodyShape.test.ts, app-shell preview/commitHistory.test.ts) and a fifth left a vi.stubGlobal('localStorage', ...) standing (UndoManager.test.ts) which under isolate:false is undone by whichever LATER file calls vi.unstubAllGlobals() first. All five now stub in and unstub out. The gate: vitest.setup.shared-global-leak-guard.ts arms a per-file afterAll in every isolate:false project that snapshots what the setup files installed, reds the file that left one replaced, and heals the global before throwing so no innocent file inherits it; its detector lives in vitest.setup.shared-global-leak.ts and its wiring plus detector are pinned by scripts/__tests__/shared-global-leak-guard.test.ts. The new guard immediately found a SECOND leak nothing could previously see: scripts/__tests__/unit-registry-absence-collision.test.ts resolves the specifier 'vitest.setup.network-escape-guard' (network-escape-ledger.test.ts imports it by path) into its private module graph after vi.resetModules(), so the guard re-executed and installed a second guardedFetch over the first. Both wrappers carry the same name, so the ledger's name check was blind to it. That gate now snapshots and restores the globals around its measurement. Full unit project after: 979 files / 16678 tests, 0 failures.",
"tests": "All heavy runs through /home/user/objectstack/scripts/pm/os-verify-lock.sh, slot issue-8500-baseline. (1) BASELINE, clean tree at c30026715: VERDICT command-exit 1, 'Test Files 1 failed | 977 passed (978) / Tests 1 failed | 16666 passed | 2 skipped', FAIL network-escape-ledger.test.ts \"AssertionError: expected 'Mock' to be 'guardedFetch'\" at line 122. (2) AFTER, committed tree 51ee1fdff: VERDICT command-exit 0, 'Test Files 979 passed (979) / Tests 16678 passed | 2 skipped (16680)'. (3) @object-ui/console project (it merges this root config): VERDICT command-exit 0, 90 files / 1072 tests passed. First attempt returned exit 99 queue-timeout after 540s (holder pid 27069 running objectui-issue-8479 shard 1/4) and is recorded as NOT MEASURED, not as a red; re-run with the same slot acquired immediately. (4) Targeted repro before the fix: 'vitest run --project unit --maxWorkers=1 packages/core/src/actions/__tests__/ActionRunner.bodyShape.test.ts scripts/__tests__/network-escape-ledger.test.ts' reproduced the failure in two files; note the outcome is ORDER dependent and the sequencer is not stable across runs, so the same two-file command also passed on other invocations. That order dependence is the card's own point. ABLATION 1 (the new guard can fail), rebuild not applicable - vitest transforms sources directly, no dist in the path. Mutation proved on disk BEFORE the run: git hash-object fd231cdce6bd91c8a053106be2f977b43ccfd538 moved to 8daf94e000f2754ba01a128b11d415e5e587f6fe, and the anchored marker counts inverted in both directions (stub-form 1 to 0, raw-form 0 to 1, unstub 1 to 0). Run: 'Error: Shared-global leak: this test FILE left a global replaced. - globalThis.fetch: expected function guardedFetch, found function Mock', 'Test Files 1 failed (1) / Tests 7 passed (7)' - the file's own 7 tests still pass, only the leak reds, and it reds on the WRITER rather than on a later innocent file. Restore under trap and proved BY STATE: blob back to fd231cdc, 'git diff HEAD' empty. ABLATION 2 (the wiring pin is not vacuous): vitest.config.mts blob 37c1635d93a5c8370f914ba3c4035c69cd191d40 to ce5850098c35eeeb7ab801d7ba2636dbcce5ba66, armed-count 2 to 1 while base-count held at 3. shared-global-leak-guard.test.ts went red on exactly the two wiring assertions, 'Tests 2 failed | 9 passed (11)'. Restored, blob back to 37c1635d, 'git diff HEAD' empty. A first draft of this script died on a shell quoting error BEFORE the mutation step; the tree was verified untouched and the script re-run - reported because the aborted attempt is part of the record. GATES: pnpm type-check:vitest-setup exit 0 (it first exited 2 with four TS2882 - its program resolves four workspace packages through their published declarations, so the four had to be BUILT first; turbo build of their closures, 15/15 successful, then exit 0). pnpm type-check:scripts exit 0. turbo run type-check --filter @object-ui/core --filter @object-ui/app-shell 31/31. node scripts/check-changeset-presence.mjs exit 1 before the changeset and exit 0 after. node scripts/check-changeset-no-major.mjs exit 0. node scripts/check-governed-queue-guard.mjs --test over the 11 changed paths: NOT GOVERNED. Control-character sweep with grep -naP over all changed files: no hits, control term 'isolate' matched 5 times in vitest.config.mts in the same run. Every exit code captured by redirecting to a file first, never through a pipe. LINT, narrowed and declared: pnpm lint:root exit 0 (32 warnings, 0 errors); turbo run lint --filter @object-ui/core --filter @object-ui/app-shell 3/3 successful (2925 warnings, 0 errors); eslint --no-inline-config over the 10 changed files exit 0, 9 warnings 0 errors, all pre-existing no-explicit-any on lines this diff does not touch. Invariance: eslint.config.js declares no parserOptions.project and no projectService, so type-aware linting is off and this diff cannot move the verdict on any file it does not contain. Counts read on 51ee1fdff, the final commit. The repo-wide 'eslint .' sweep is CI's. SCOPE declared to CI: the dom / dom-heavy / dist projects were not run locally. Nothing in the diff reaches them - the config change is confined inside the unit project's own object, the two new root files are referenced only from that project's setupFiles, and all five edited test files are unit-project files.",
"mcp_calls": "0 - every GitHub read and write went through repo-scoped REST (curl); the probe GET /repos/objectstack-ai/objectui/issues/8500 returned 200, so the REST channel was used throughout and no MCP GitHub tool was called",
"false_predictions_in_the_brief": [
"PARTLY FALSE, and it changed the work: 'the whole scripts/__tests__ directory is green on its own, therefore the writer lives OUTSIDE that directory' - the brief calls this the single most useful thing already known. It is true of the vi.fn() writers, and it is INCOMPLETE. A second writer lives INSIDE scripts/__tests__: unit-registry-absence-collision.test.ts re-executes vitest.setup.network-escape-guard in its private module graph and installs a second guardedFetch over the first. The filer's directory control could never see it, because the replacement carries the same function NAME - the only thing the ledger test compares. It is fixed in this PR.",
"The line numbers held. Re-derived by content on c30026715: isolate: false at vitest.config.mts:308, the dom/dom-heavy note at :305, the fetch assertion at network-escape-ledger.test.ts:122 and the guardCode assertion at :123, and unit-registry-absence-collision.test.ts exists. The base sha was still c30026715 at worktree time.",
"The brief's framing 'some other file replaced the global with a vi.fn()' named ONE mechanism; there were two more. A vi.stubGlobal left standing (UndoManager.test.ts) leaks differently - the value is undone by a LATER file's vi.unstubAllGlobals(), because that registry is shared across files under isolate: false - and the doubled wrapper above leaks a third way. All three are the same invariant.",
"Not in the brief and worth correcting for future dispatches: a two-file targeted repro of this card is ORDER dependent and Vitest's sequencer is not stable between runs on this tree, so the same command passes and fails on different invocations. Only the full project run is a reliable reading."
],
"open_questions": [],
"out_of_scope_findings": [
"filed as #8537 (no labels, no assignee): the network-escape guard's afterEach - the half its own header calls 'the afterEach that fails ANY escape in ANY file' - registers only for the FIRST test file each worker runs in the unit project. Measured with two byte-identical escaping files run in one worker: only the one that ran first reds, the second escapes silently and passes; each reds when run alone. Mechanism: Vitest re-executes setupFiles per test file even under isolate: false, but a module those setup files IMPORT is evaluated once per worker, and the guard is imported by vitest.setup.base.ts rather than being a setupFiles entry. Not repaired here: the repair turns every escape now invisible in the unit project red at once and that count has not been measured, so it is a separate blast radius for triage. The new guard in this PR sidesteps the mechanism by being a real setup file."
]
}
ACCEPT — PR #8538, reviewed against the tree, ⛔ not against the report.domain:devx @ objectui execution seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R47, 2026-09-08T08:33Z. Head 51ee1fdff, base 90a9aab32, 11 files.
The routing question this card carried, answered the way triage asked
Triage warned: ⛔ direction 2 alone "消掉本仓目前唯一一个能发现「有人换了全局 fetch 不还」的信号" — take it only with the gate half of direction 1. ⭐ Direction 1 landed, both halves, so the warning never had to bite:
the writers hand the global back — four files that assigned a bare vi.fn() over globalThis.fetch (ActionRunner.test.ts, .bodyExtra, .bodyShape, preview/commitHistory.test.ts) and one that left a vi.stubGlobal('localStorage', …) standing (UndoManager.test.ts);
the gate exists — vitest.setup.shared-global-leak-guard.ts + its detector vitest.setup.shared-global-leak.ts, wired into the unit project's setupFileslast, pinned by scripts/__tests__/shared-global-leak-guard.test.ts.
⭐ The UndoManager case is a mechanism the brief did not name and this seat had not considered: under isolate: false the vi stub registry is shared across files, so a stub left standing is undone by whichever later file calls vi.unstubAllGlobals() first — restoring a value that file never installed. Same invariant, third leak shape.
⭐ The finding that makes this PR worth more than its card: a leak nothing could see
The new guard immediately caught a second leak insidescripts/__tests__ — the directory the card's own control had cleared. unit-registry-absence-collision.test.ts resolves vitest.setup.network-escape-guard into a private module graph after vi.resetModules(), so the guard re-executes and installs a secondguardedFetch over the first.
⇒ Both wrappers are named guardedFetch, so the ledger's globalThis.fetch.name check was structurally blind to it. ⚠️ And it is pinned: the detector's own test asserts the case where expected and found are bothfunction guardedFetch and a leak is still reported (shared-global-leak-guard.test.ts:200). That is the blind spot closed by content, ⛔ not by intention.
What this seat verified itself
claim
reading
the config change is confined to the unit project
✔ one hunk, @@ -302,11 +302,38 @@, entirely inside the isolate: false block; isolate: occurs at :329 (false) and :422 (true) and the guard path appears once, at :335
the guard is a real setup file, not an import
✔ setupFiles: [ base, …shared-global-leak-guard ], guard last, with the measured reason in the docblock — a module a setup file imports is evaluated once per worker, so its hooks attach to that worker's first test file only
it heals before it throws
✔ stated and implemented, so the file that leaked is the only one that reds — ⛔ no cascade onto innocent files
the pin is not vacuous
✔ it carries its own positive control (armings > 0, "the config must mention the guard at all"), asserts identity restoration (toBe(original), "the exact value, not an equivalent one"), and asserts baseline.length === WATCHED_GLOBALS.length with at least one watched global actually defined
the test arithmetic is self-consistent
✔ baseline 978 files / 16666 passed + 1 failed; after 979 files / 16678 passed. +1 file = the new pin; +12 tests = the 11 in the new pin plus the one that was failing. ⇒ the numbers reconcile exactly
⚠️ Corrections to the report, ⛔ neither of which changes the verdict
The summary says the guard is armed "in every isolate: false project". There is exactly one such project (unit). True, but it reads as plural coverage that does not exist.
Ablation 2's "armed-count 2 to 1" does not match the config, where the guard path occurs once. The ablation still demonstrated what it needed to — the pin went red on exactly the two wiring assertions — so this is a bookkeeping slip in the prose, ⛔ not in the evidence.
⭐ Both ablations were run the right way regardless: mutation proved on disk by blob hash and inverted marker counts before the gate ran, restore proved by state (git diff HEAD empty, hash back to the HEAD blob), ⛔ never by an exit code. And the dev reported that a first draft of one ablation script died on a shell quoting error before the mutation step — ⭐ reporting an aborted attempt nobody would have seen is the behaviour that makes the rest of the evidence worth reading.
CI, and the one red that is not this diff's
28 checks completed success, 3 skipped — including all four Test (shard N/4) — and Bundle Analysis failed. ⛔ That is objectui#8541, ⛔ not this PR: this PR's own base commit 90a9aab32 fails the same gate on main with no PR diff at all, and the failing leg is a per-chunk gzip ceiling on apps/console's eager closure, which nothing in this diff (test files, test setup, vitest.config.mts, a changeset) is in the build graph of. It is ⛔ not a required check. Stated in full on the PR at 08:24Z.
⚠️ Hot-file note
PR #8486 (card objectui#8366) also modifies vitest.config.mts, at @@ -7,6 +7,54 @@ against this PR's @@ -302,11 +302,38 @@ — ~295 lines apart, ⇒ ⛔ no textual conflict predicted, but whichever lands second needs its branch updated.
⇒ ACCEPT. Ready-flip, guard, queue and landing probe follow.
Armed — PR #8538 is in the merge queue.domain:devx @ objectui execution seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R47.
step
reading (UTC)
CI on 51ee1fdff
28 completed success, 3 skipped, all four Test (shard N/4) green. One failure: Bundle Analysis — objectui#8541, ⛔ not this diff, ⛔ not a required check (its base commit fails the same gate on main with no PR diff)
ready-flip
ready_for_review08:32:52Z
Governed Surface Queue Guard, re-run after the flip
completed/success @ 08:33:11Z — ⛔ the pre-flip run was not accepted in its place
auto-merge
SQUASH, armed 08:33Z
queue
added_to_merge_queue@ 08:33:18Z, confirmed from the timeline event, ⛔ not from the auto_merge field
⭐ Its shard 2 now runs against a main that carries the objectui#8493 repair (90a9aab32), so the flake that dequeued three PRs today should no longer be in play. ⚠️ If it dequeues on that case anyway, that is a new fact about 90a9aab32 and it goes to objectui#8493 with the run id — ⛔ not treated as the old defect persisting.
Landing probe will be scoped to the guard's own wiring — the setupFiles entry in vitest.config.mts, installSharedGlobalLeakGuard, and the pinned assertion that a same-named replacement is still reported as a leak — with a firing control and a nonsense control on a re-fetched origin/main.
Landing probe, on a re-fetchedorigin/main (19d86fe9e)
probe
expect
read
vitest.setup.shared-global-leak-guard referenced in vitest.config.mts
> 0
2 (the setupFiles entry and the comment naming its pin)
installSharedGlobalLeakGuard in the guard setup file
> 0
3
installSharedGlobalLeakGuard in the detector module
> 0
2
snapshotGlobals in unit-registry-absence-collision.test.ts — the second leak's repair
> 0
2
⭐ the blind-spot pin: expected: 'function guardedFetch', found: 'function guardedFetch'
> 0
1
⭐ firing control — isolate: false in vitest.config.mts
> 0
1
⭐ nonsense control — zzq-not-a-real-token-8500
0
0
⇒ The zero is a reading, not a broken pattern.
What is now on main
The unit project's second shared-state invariant is enforced rather than written down: a global the setup files install must be the same value at the end of a file as it was at the start of it. The guard reds the file that leaked and heals the global before throwing, so the leak no longer cascades onto innocent files — which was the whole failure shape.
⭐ And the pin that survives longest is the one on the blind spot. The second leak this PR fixed — a re-executed setup module installing a secondguardedFetch over the first — was invisible to globalThis.fetch.name because both wrappers carry the same name. That case is now pinned by content on main: a leak is reported when expected and found are bothfunction guardedFetch. ⇒ The next same-named replacement cannot hide the way this one did for however long it had been there.
Activity
os-zhuang commentedon Sep 8, 2026
分诊路由 —
domain:devx·pm:queue·priority:p2· type Bug⛔ 本席是分诊席(
claude-opus-5):不认领、不派发、不写码、不合并、不裁决决策箱卡。以下只是定级与路由。复核 —— 三处锚点在
origin/main f76f4362上重取✅ 三处全部成立。⭐ 注意
:118-120那段注释 —— 它明写这条断言是「Behavioural, on the live global」。⇒ 这个测试故意读活的全局对象,所以它读到Mock不是它写错了,正是它被设计来发现的那件事。⭐ 这张卡最值钱的地方是它的三个对照,本席逐条说为什么它们把结论钉死了
卡没有停在「full run 时红」,而是把可能的解释一条条排掉:
origin/main61e649ab1+ fresh install 上复现scripts/__tests__目录 121 文件 / 3630 测试全绿⇒ 结论「同一 worker 里另有文件把
globalThis.fetch换成vi.fn()且没还回去」是推出来的,不是猜的。⛔ 承接者不必重做这四条,但应当重跑一次干净基线确认它还在(本席未跑测试,只验了代码锚点)。定级 —
priority:p2⛔ 不是 p3,理由有两条,第二条更重要:
unit的开发席会看到一个红文件,必须先人工排除它才能相信自己的结果。一个在干净 main 上就红的测试,会把「红 = 我改坏了」这个信号稀释掉。⛔ 不抬 p1:无产品运行期影响,纯测试基建;且 CI 的分片方式可能让它在 CI 上不复现(卡只测了本地 full run)。
承接提示 —— 卡给的两条方向不等价,本席指出取舍,⛔ 不代选
卡自己已经把话说明白了,本席只把它的判断显式化:
vitest.config.mts之所以把isolate: false的不变量交给unit-registry-absence-collision.test.ts执行而不是只写在注释里,正是因为这一类失败。那道门今天只覆盖ComponentRegistry的键;globalThis.fetch是同一类共享状态,身后没有门。⇒ 方向 1 是把已有的正确做法推广一格,不是发明新机制。车道 —
domain:devx按 2026-08-21 裁定「按修复落点分三流」:落点是
scripts/__tests__/**+vitest.config.mts+ 那道碰撞门 —— 工程面(测试基建),⇒domain:devx。与 #8203 / #8455 / #8404 等同判。⛔ 不判
domain:ui:症状文件是scripts/下的自检,写入者也在测试树里,⛔ 与任何发布库的产品行为无关。type = Bug
vitest.config.mts把isolate: false连同它所依赖的不变量一起写了下来,而树上存在一个违反该不变量的写入者 ⇒ 违背已声明契约,Bug。Generated by Claude Code
claude commentedon Sep 8, 2026
Claim: PM loop round R47 —
domain:devx @ objectuiexecution seat.Session:
session_01FhBNJcLRZLe8M87VcUgpKrBranch:
claude/issue-8500-global-fetch-leakWorktree:
/home/user/objectui-issue-8500Domain:
domain:devxDispatch: one
os-devsubagent of this session (mode:subagent,model: opus); the dispatch note with the brief follows. Labelspm:queue→pm:dispatchedand assignee set in one write, read back and diffed against the union at 2026-09-08T07:15:44Z.⛔ A zero-hit reading means nothing without a control term known to be present in the same run. ⛔ Verify
origin/mainon a clean extract or a detached worktree, never on/home/user/objectui's working tree. ⛔ A grep over a whole file cannot answer a question about one region of it, and an anchored pattern cannot answer one about an indented region.⭐ Filing rule: a concrete defect carries no labels at all (triage grades it); an observation carries
findingonly. ⛔ Never adomain:*, ⛔ never an assignee.Generated by Claude Code
claude commentedon Sep 8, 2026
Dispatch note — objectui#8500, round R47.
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, 2026-09-08T07:18Z. Oneos-devsubagent (mode:subagent,model: opus), branchclaude/issue-8500-global-fetch-leak, worktree/home/user/objectui-issue-8500.Base and the drift already found
Base at dispatch:
origin/mainc30026715, read 2026-09-08T07:17Z. ⇒ Re-fetch and re-read; it moves under you.vitest.config.mts:293forisolate: falseand:290for thedom/dom-heavynote, onf76f4362. Onc30026715those two lines read:308and:305. The anchors are still true; ⇒ every line number in the card and in the triage comment is stale by construction. Re-find by content, ⛔ never by line.Anchors re-read on
c30026715by this seat:The card's own reading, and what is already settled
The filer ruled out four explanations with controls (clean
origin/main61e649ab1+ fresh install reproduces; the file alone passes with 5; the wholescripts/__tests__directory passes with 121 files / 3630 tests; two consecutive full runs fail identically). ⛔ Do not redo those four — but ⭐ do re-run one clean full-unitbaseline to confirm the failure still exists on today's tree, because the tree has moved since61e649ab1and a card that repaired itself is a real outcome.⭐ The single most useful thing already known: the whole
scripts/__tests__directory is green on its own ⇒ the writer that replacesglobalThis.fetchwith avi.fn()and does not restore it lives OUTSIDE that directory. Finding it is the task.Card statuses, read on GitHub at 2026-09-08T07:17Z — ⛔ not taken from any card's own record
os-sales— ⛔ not yours, do not touchd717e8bc3, which wrote theisolate: falseinvariant and its gate)os-justin— ⛔ neither is yoursThe two directions, and the triage seat's routing opinion quoted rather than paraphrased
The card offers two. The triage seat's comment
5580349481states an opinion on them, ⛔ explicitly not a ruling, and this seat passes it through verbatim:⇒ If you land direction 2 alone, you must say on the card why, in the same push. ⛔ Silently narrowing the probe is the one outcome this dispatch will not accept.
⭐ Direction 1 has a precedent the card names:
vitest.config.mtsdoes not merely document theisolate: falseinvariant, it hands enforcement toscripts/__tests__/unit-registry-absence-collision.test.ts(landed by objectui#7309). That gate coversComponentRegistrykeys only.globalThis.fetchis the same class of shared state with no gate behind it ⇒ direction 1 is extending an existing correct mechanism by one notch, ⛔ not inventing one.Requirements
/home/user/objectui,/home/user/objectstack). One dedicated worktree:/home/user/objectui-issue-8500, branchclaude/issue-8500-global-fetch-leak.git stash—refs/stashlives in the common.gitdir and every worktree shares one LIFO stack. Use a patch or a wip commit.content/docs/releases/. Your input to release notes is the changeset.Claim:— this seat set the assignee and posted the claim; you inherit both. ⛔ Never write the assignee field, ⛔ never yield the card.origin/mainon a detached worktree orgit show origin/main:PATH, ⛔ never on/home/user/objectui's working tree — its HEAD moves under you.Changeset Declarationgate asks for one; a draft PR withFixes #8500in the body.--project unitruns) goes through the shared lock:/home/user/objectstack/scripts/pm/os-verify-lock.sh.Test (shard 2/4)is currently flaky in this repository onpackages/plugin-kanban/src/ObjectKanban.contractEnvelope-6839.test.tsx:134(expected +0 to be 2) — tracked as objectui#8493, ⛔ not yours and ⛔ not caused by any diff. If your PR's CI reddens on exactly that case, report it and ⛔ do not chase it.What a good report looks like
⭐ A corrected brief with zero implementation is a good outcome. If the writer cannot be found, or the failure no longer reproduces on today's tree, say so with the commands and the readings — that is a finished job, ⛔ not a failure. Report back as structured JSON: what you measured, what you changed, the PR number and head sha, every prediction in this brief that turned out false, and any defect you found but did not repair (file it: a concrete defect carries no labels at all, an observation carries
findingonly; ⛔ never adomain:*, ⛔ never an assignee).决策分析四轴 — 若你需要在两个方向之间给出建议,按此四轴分析
每个方案必须沿四条固定评估轴分析,这是决策分析的核心原则,不是可选项:
还是投机性能力面?判据要求实测(谁在写这个键、谁在读、
示例应用与真实部署的用法),「读起来像有用」不作数。这条轴会改变结论,不是陪衬。
contract-first),临时补丁式选项要明说长期代价。
契约收紧(严格 schema、publish 时响亮拒绝)优于消费端宽容(
??回退、静默容错)——宽容恰是 AI 批量犯错被掩盖的温床;声明即强制,绝不让 AI 声明一个运行时不兑现的能力。
「我们是一个创业项目,应该先专注于核心能力」):能力扩张默认从紧,
无拉动的声明面按 implementation-first 处置,已发布零消费的能力不因沉没成本获得豁免。
过渡也从紧 —— 创业阶段不渐进(维护者 2026-08-27 裁,逐字:「项目在创业阶段,
用户也很少,短期不考虑渐进。」):废弃别名/拼写与能力退役默认立即退休,
不设分阶段窗口、不留双拼写宽限;staged 选项仅凭具名外部用户证据才可呈报为推荐。
推荐意见必须基于这四条轴给出理由;四轴冲突时如实呈现权衡,
交维护者拍板。
Generated by Claude Code
claude commentedon Sep 8, 2026
os-dev-report
{ "issue": 8500, "status": "done", "branch": "claude/issue-8500-global-fetch-leak", "pr": "https://github.com/objectstack-ai/objectui/pull/8538", "head_sha": "51ee1fdff946d518f5a10284dfd5846f16600c72", "premise_still_valid": true, "summary": "The card's premise holds on today's tree. Clean baseline at origin/main c30026715 in a dedicated worktree with a fresh install: `vitest run --project unit --maxWorkers=2` gave 1 failed | 977 passed (978 files), the failure being exactly network-escape-ledger.test.ts reading 'Mock' for globalThis.fetch.name. Direction 1 landed, both halves. The writers: four files replaced globalThis.fetch with a bare vi.fn() by assignment (ActionRunner.test.ts, ActionRunner.bodyExtra.test.ts, ActionRunner.bodyShape.test.ts, app-shell preview/commitHistory.test.ts) and a fifth left a vi.stubGlobal('localStorage', ...) standing (UndoManager.test.ts) which under isolate:false is undone by whichever LATER file calls vi.unstubAllGlobals() first. All five now stub in and unstub out. The gate: vitest.setup.shared-global-leak-guard.ts arms a per-file afterAll in every isolate:false project that snapshots what the setup files installed, reds the file that left one replaced, and heals the global before throwing so no innocent file inherits it; its detector lives in vitest.setup.shared-global-leak.ts and its wiring plus detector are pinned by scripts/__tests__/shared-global-leak-guard.test.ts. The new guard immediately found a SECOND leak nothing could previously see: scripts/__tests__/unit-registry-absence-collision.test.ts resolves the specifier 'vitest.setup.network-escape-guard' (network-escape-ledger.test.ts imports it by path) into its private module graph after vi.resetModules(), so the guard re-executed and installed a second guardedFetch over the first. Both wrappers carry the same name, so the ledger's name check was blind to it. That gate now snapshots and restores the globals around its measurement. Full unit project after: 979 files / 16678 tests, 0 failures.", "tests": "All heavy runs through /home/user/objectstack/scripts/pm/os-verify-lock.sh, slot issue-8500-baseline. (1) BASELINE, clean tree at c30026715: VERDICT command-exit 1, 'Test Files 1 failed | 977 passed (978) / Tests 1 failed | 16666 passed | 2 skipped', FAIL network-escape-ledger.test.ts \"AssertionError: expected 'Mock' to be 'guardedFetch'\" at line 122. (2) AFTER, committed tree 51ee1fdff: VERDICT command-exit 0, 'Test Files 979 passed (979) / Tests 16678 passed | 2 skipped (16680)'. (3) @object-ui/console project (it merges this root config): VERDICT command-exit 0, 90 files / 1072 tests passed. First attempt returned exit 99 queue-timeout after 540s (holder pid 27069 running objectui-issue-8479 shard 1/4) and is recorded as NOT MEASURED, not as a red; re-run with the same slot acquired immediately. (4) Targeted repro before the fix: 'vitest run --project unit --maxWorkers=1 packages/core/src/actions/__tests__/ActionRunner.bodyShape.test.ts scripts/__tests__/network-escape-ledger.test.ts' reproduced the failure in two files; note the outcome is ORDER dependent and the sequencer is not stable across runs, so the same two-file command also passed on other invocations. That order dependence is the card's own point. ABLATION 1 (the new guard can fail), rebuild not applicable - vitest transforms sources directly, no dist in the path. Mutation proved on disk BEFORE the run: git hash-object fd231cdce6bd91c8a053106be2f977b43ccfd538 moved to 8daf94e000f2754ba01a128b11d415e5e587f6fe, and the anchored marker counts inverted in both directions (stub-form 1 to 0, raw-form 0 to 1, unstub 1 to 0). Run: 'Error: Shared-global leak: this test FILE left a global replaced. - globalThis.fetch: expected function guardedFetch, found function Mock', 'Test Files 1 failed (1) / Tests 7 passed (7)' - the file's own 7 tests still pass, only the leak reds, and it reds on the WRITER rather than on a later innocent file. Restore under trap and proved BY STATE: blob back to fd231cdc, 'git diff HEAD' empty. ABLATION 2 (the wiring pin is not vacuous): vitest.config.mts blob 37c1635d93a5c8370f914ba3c4035c69cd191d40 to ce5850098c35eeeb7ab801d7ba2636dbcce5ba66, armed-count 2 to 1 while base-count held at 3. shared-global-leak-guard.test.ts went red on exactly the two wiring assertions, 'Tests 2 failed | 9 passed (11)'. Restored, blob back to 37c1635d, 'git diff HEAD' empty. A first draft of this script died on a shell quoting error BEFORE the mutation step; the tree was verified untouched and the script re-run - reported because the aborted attempt is part of the record. GATES: pnpm type-check:vitest-setup exit 0 (it first exited 2 with four TS2882 - its program resolves four workspace packages through their published declarations, so the four had to be BUILT first; turbo build of their closures, 15/15 successful, then exit 0). pnpm type-check:scripts exit 0. turbo run type-check --filter @object-ui/core --filter @object-ui/app-shell 31/31. node scripts/check-changeset-presence.mjs exit 1 before the changeset and exit 0 after. node scripts/check-changeset-no-major.mjs exit 0. node scripts/check-governed-queue-guard.mjs --test over the 11 changed paths: NOT GOVERNED. Control-character sweep with grep -naP over all changed files: no hits, control term 'isolate' matched 5 times in vitest.config.mts in the same run. Every exit code captured by redirecting to a file first, never through a pipe. LINT, narrowed and declared: pnpm lint:root exit 0 (32 warnings, 0 errors); turbo run lint --filter @object-ui/core --filter @object-ui/app-shell 3/3 successful (2925 warnings, 0 errors); eslint --no-inline-config over the 10 changed files exit 0, 9 warnings 0 errors, all pre-existing no-explicit-any on lines this diff does not touch. Invariance: eslint.config.js declares no parserOptions.project and no projectService, so type-aware linting is off and this diff cannot move the verdict on any file it does not contain. Counts read on 51ee1fdff, the final commit. The repo-wide 'eslint .' sweep is CI's. SCOPE declared to CI: the dom / dom-heavy / dist projects were not run locally. Nothing in the diff reaches them - the config change is confined inside the unit project's own object, the two new root files are referenced only from that project's setupFiles, and all five edited test files are unit-project files.", "mcp_calls": "0 - every GitHub read and write went through repo-scoped REST (curl); the probe GET /repos/objectstack-ai/objectui/issues/8500 returned 200, so the REST channel was used throughout and no MCP GitHub tool was called", "false_predictions_in_the_brief": [ "PARTLY FALSE, and it changed the work: 'the whole scripts/__tests__ directory is green on its own, therefore the writer lives OUTSIDE that directory' - the brief calls this the single most useful thing already known. It is true of the vi.fn() writers, and it is INCOMPLETE. A second writer lives INSIDE scripts/__tests__: unit-registry-absence-collision.test.ts re-executes vitest.setup.network-escape-guard in its private module graph and installs a second guardedFetch over the first. The filer's directory control could never see it, because the replacement carries the same function NAME - the only thing the ledger test compares. It is fixed in this PR.", "The line numbers held. Re-derived by content on c30026715: isolate: false at vitest.config.mts:308, the dom/dom-heavy note at :305, the fetch assertion at network-escape-ledger.test.ts:122 and the guardCode assertion at :123, and unit-registry-absence-collision.test.ts exists. The base sha was still c30026715 at worktree time.", "The brief's framing 'some other file replaced the global with a vi.fn()' named ONE mechanism; there were two more. A vi.stubGlobal left standing (UndoManager.test.ts) leaks differently - the value is undone by a LATER file's vi.unstubAllGlobals(), because that registry is shared across files under isolate: false - and the doubled wrapper above leaks a third way. All three are the same invariant.", "Not in the brief and worth correcting for future dispatches: a two-file targeted repro of this card is ORDER dependent and Vitest's sequencer is not stable between runs on this tree, so the same command passes and fails on different invocations. Only the full project run is a reliable reading." ], "open_questions": [], "out_of_scope_findings": [ "filed as #8537 (no labels, no assignee): the network-escape guard's afterEach - the half its own header calls 'the afterEach that fails ANY escape in ANY file' - registers only for the FIRST test file each worker runs in the unit project. Measured with two byte-identical escaping files run in one worker: only the one that ran first reds, the second escapes silently and passes; each reds when run alone. Mechanism: Vitest re-executes setupFiles per test file even under isolate: false, but a module those setup files IMPORT is evaluated once per worker, and the guard is imported by vitest.setup.base.ts rather than being a setupFiles entry. Not repaired here: the repair turns every escape now invisible in the unit project red at once and that count has not been measured, so it is a separate blast radius for triage. The new guard in this PR sidesteps the mechanism by being a real setup file." ] }Generated by Claude Code
claude commentedon Sep 8, 2026
ACCEPT — PR #8538, reviewed against the tree, ⛔ not against the report.
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R47, 2026-09-08T08:33Z. Head51ee1fdff, base90a9aab32, 11 files.The routing question this card carried, answered the way triage asked
Triage warned: ⛔ direction 2 alone "消掉本仓目前唯一一个能发现「有人换了全局 fetch 不还」的信号" — take it only with the gate half of direction 1. ⭐ Direction 1 landed, both halves, so the warning never had to bite:
vi.fn()overglobalThis.fetch(ActionRunner.test.ts,.bodyExtra,.bodyShape,preview/commitHistory.test.ts) and one that left avi.stubGlobal('localStorage', …)standing (UndoManager.test.ts);vitest.setup.shared-global-leak-guard.ts+ its detectorvitest.setup.shared-global-leak.ts, wired into theunitproject'ssetupFileslast, pinned byscripts/__tests__/shared-global-leak-guard.test.ts.⭐ The
UndoManagercase is a mechanism the brief did not name and this seat had not considered: underisolate: falsethevistub registry is shared across files, so a stub left standing is undone by whichever later file callsvi.unstubAllGlobals()first — restoring a value that file never installed. Same invariant, third leak shape.⭐ The finding that makes this PR worth more than its card: a leak nothing could see
The new guard immediately caught a second leak inside
scripts/__tests__— the directory the card's own control had cleared.unit-registry-absence-collision.test.tsresolvesvitest.setup.network-escape-guardinto a private module graph aftervi.resetModules(), so the guard re-executes and installs a secondguardedFetchover the first.⇒ Both wrappers are named⚠️ And it is pinned: the detector's own test asserts the case where expected and found are both
guardedFetch, so the ledger'sglobalThis.fetch.namecheck was structurally blind to it.function guardedFetchand a leak is still reported (shared-global-leak-guard.test.ts:200). That is the blind spot closed by content, ⛔ not by intention.What this seat verified itself
unitproject@@ -302,11 +302,38 @@, entirely inside theisolate: falseblock;isolate:occurs at:329(false) and:422(true) and the guard path appears once, at:335setupFiles: [ base, …shared-global-leak-guard ], guard last, with the measured reason in the docblock — a module a setup file imports is evaluated once per worker, so its hooks attach to that worker's first test file onlyarmings > 0, "the config must mention the guard at all"), asserts identity restoration (toBe(original), "the exact value, not an equivalent one"), and assertsbaseline.length === WATCHED_GLOBALS.lengthwith at least one watched global actually definedisolate: falseproject". There is exactly one such project (unit). True, but it reads as plural coverage that does not exist.⭐ Both ablations were run the right way regardless: mutation proved on disk by blob hash and inverted marker counts before the gate ran, restore proved by state (
git diff HEADempty, hash back to the HEAD blob), ⛔ never by an exit code. And the dev reported that a first draft of one ablation script died on a shell quoting error before the mutation step — ⭐ reporting an aborted attempt nobody would have seen is the behaviour that makes the rest of the evidence worth reading.CI, and the one red that is not this diff's
28 checks completed success, 3 skipped — including all four
Test (shard N/4)— andBundle Analysisfailed. ⛔ That is objectui#8541, ⛔ not this PR: this PR's own base commit90a9aab32fails the same gate onmainwith no PR diff at all, and the failing leg is a per-chunk gzip ceiling onapps/console's eager closure, which nothing in this diff (test files, test setup,vitest.config.mts, a changeset) is in the build graph of. It is ⛔ not a required check. Stated in full on the PR at 08:24Z.PR #8486 (card objectui#8366) also modifies
vitest.config.mts, at@@ -7,6 +7,54 @@against this PR's@@ -302,11 +302,38 @@— ~295 lines apart, ⇒ ⛔ no textual conflict predicted, but whichever lands second needs its branch updated.⇒ ACCEPT. Ready-flip, guard, queue and landing probe follow.
Generated by Claude Code
claude commentedon Sep 8, 2026
Armed — PR #8538 is in the merge queue.
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R47.51ee1fdffTest (shard N/4)green. One failure:Bundle Analysis— objectui#8541, ⛔ not this diff, ⛔ not a required check (its base commit fails the same gate onmainwith no PR diff)ready_for_review08:32:52ZGoverned Surface Queue Guard, re-run after the flipSQUASH, armed 08:33Zadded_to_merge_queue@ 08:33:18Z, confirmed from the timeline event, ⛔ not from theauto_mergefield⭐ Its shard 2 now runs against a⚠️ If it dequeues on that case anyway, that is a new fact about
mainthat carries the objectui#8493 repair (90a9aab32), so the flake that dequeued three PRs today should no longer be in play.90a9aab32and it goes to objectui#8493 with the run id — ⛔ not treated as the old defect persisting.Landing probe will be scoped to the guard's own wiring — the
setupFilesentry invitest.config.mts,installSharedGlobalLeakGuard, and the pinned assertion that a same-named replacement is still reported as a leak — with a firing control and a nonsense control on a re-fetchedorigin/main.Generated by Claude Code
claude commentedon Sep 8, 2026
LANDED —
19d86fe9eonorigin/main.domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R47, 2026-09-08T08:52Z. PR #8538 (card objectui#8500), merged first pass.Landing probe, on a re-fetched
origin/main(19d86fe9e)vitest.setup.shared-global-leak-guardreferenced invitest.config.mtssetupFilesentry and the comment naming its pin)installSharedGlobalLeakGuardin the guard setup fileinstallSharedGlobalLeakGuardin the detector modulesnapshotGlobalsinunit-registry-absence-collision.test.ts— the second leak's repairexpected: 'function guardedFetch', found: 'function guardedFetch'isolate: falseinvitest.config.mtszzq-not-a-real-token-8500⇒ The zero is a reading, not a broken pattern.
What is now on
mainThe
unitproject's second shared-state invariant is enforced rather than written down: a global the setup files install must be the same value at the end of a file as it was at the start of it. The guard reds the file that leaked and heals the global before throwing, so the leak no longer cascades onto innocent files — which was the whole failure shape.⭐ And the pin that survives longest is the one on the blind spot. The second leak this PR fixed — a re-executed setup module installing a second
guardedFetchover the first — was invisible toglobalThis.fetch.namebecause both wrappers carry the same name. That case is now pinned by content onmain: a leak is reported when expected and found are bothfunction guardedFetch. ⇒ The next same-named replacement cannot hide the way this one did for however long it had been there.Generated by Claude Code