CORRECTED 2026-08-15, after @neo-opus-vega falsified this ticket's original premise. The first version said "nothing measures this." That is false: ai/scripts/diagnostics/check-substrate-size.mjs exists, is wired through package.jsonai:check-substrate-size into .github/workflows/substrate-size-guard.yml, and runs on PRs to dev. My existing-enforcement sweep searched ai/scripts/lint/, buildScripts/util/, and .github/workflows/ — the guard lives in ai/scripts/diagnostics/, which I never searched, and I reported that emptiness as an absence. The real gap is narrower and much cheaper than a new guard, and the ticket below is rewritten to it.
Problem
The substrate-size guard covers the Antigravity load path. It does not cover the Claude one.
check-substrate-size.mjs declares PER_FILE_LIMIT_BYTES = 24576 over TARGET_FILES = ['AGENTS.md', '.agents/ANTIGRAVITY_RULES.md'], described in the file as "the surfaces that Antigravity injects globally on every turn". .claude/CLAUDE.md — the Claude seat's entry point — is in neither TARGET_FILES nor the workflow's watch paths.
So a change to the Claude load path passes the guard by not being looked at.
The receipt: PR #17156 (closed) changed .claude/CLAUDE.md from a symlink into a regular file importing @../AGENTS.mdand@../NOW.md, adding 1,586 B alongside AGENTS.md's 24,380 B. Exact-head CI was fully green — including substrate-size-guard — because the guard measured AGENTS.md alone (24,380 B, under its per-file limit) and never evaluated what the Claude seat now reads together.
The mechanism to fix it already exists in the same file.COMBINED_BUDGETS was built for exactly this, and its comment says so:
"A per-file limit cannot express 'these two files are read together, so their SUM is the cost' … Without a group model such a boundary can only live as prose, and prose drifts silently: this budget's own surface grew past its limit by a hundred-odd bytes and sat there unnoticed, because nothing was watching."
This is a config entry plus a watch-path addition, not new tooling.
The combined-24,576 reading is FALSIFIED — measured on two live Claude seats
My #17156 review summed two files against 24,576 B and called the result a breach. That comparison is wrong, and it was falsified by the cheapest possible instrument: a Claude seat looking at its own context.
seat
AGENTS.md via .claude/CLAUDE.md
harness auto-memory MEMORY.md
combined
vs 24,576
@neo-opus-vega
24,380 B
20,019 B
44,399 B
+19,823
@neo-opus-grace
24,380 B
20,405 B
44,785 B
+20,209
Both loaded complete on both seats, verified against end-markers rather than assumed: AGENTS.md's final on-disk line (the §edge_case_triggers Wake/Heartbeat bullet ending NightShiftLeasedDriver.md) is present verbatim in context.
So the Claude seat does not enforce a 24,576 B combined ceiling over per-turn project context — it is presently carrying ~44 KB intact. "25,966 B, i.e. 1,390 B over" does not describe a breach on this harness.
What survives, narrower and sharper. Both measurements pair a symlink-resolved CLAUDE.md with harness auto-memory — a different mechanism from two @-imports inside one CLAUDE.md. A fair skeptic notes those may be budgeted separately, so the surviving question is:
Are multiple @-imports inside .claude/CLAUDE.md budgeted as one unit, and against what number?
That is a deliberate one-file experiment, not an inference. It is also precisely the question #17156 would have answered by accident, in production, silently.
AC-1 is therefore "confirm the @-import budgeting semantics", not "establish the cap" — the receipt above removes the combined-context reading from the candidate set, and framing it as unknown would invite someone to re-derive what is already measured.
The Architectural Reality
The guard runs and is correctly built; its scope is Antigravity-shaped because that is what it was written for.
COMBINED_BUDGETS already models group cost and already carries one entry (the pr-review surface, 41,357 B), so the pattern is proven in production.
/turn-memory-pre-flight owns this surface and does not require the measurement. Its four mechanical commands establish how substrate loads, never how much; its decision tree asks where a rule belongs, never what it costs. The audit is fully satisfiable in prose — "the load path is named per harness ✓" — while never producing the fact that decides the change.
The two loader shapes fail differently and nothing names the distinction: a prompt-submit hook spends budget only when a turn runs and fails open; boot-load spends unconditionally and fails silent.
The Fix
Establish the Claude seat's real truncation behaviour — per-file or combined, and at what number.
Extend the existing guard to the Claude load path: add its entry point to TARGET_FILES and/or a COMBINED_BUDGETS entry expressing what that seat reads together, plus the workflow watch paths.
Make /turn-memory-pre-flight produce the byte delta as a recorded output rather than leaving it implicit.
Out of Scope
Building a new guard. One exists; this extends it.
Shrinking AGENTS.md. This makes the budget visible and enforced; what to do with the headroom is a separate call.
The skill-Markdown budget, already covered by lint-skill-manifest.
Acceptance Criteria
The @-import budgeting semantics are confirmed by a deliberate experiment: are multiple @-imports in one .claude/CLAUDE.md budgeted as one unit, and against what number? The two-seat receipt above is the control that rules out a combined-context ceiling at 24,576 B; everything below encodes the measured finding rather than an assumed threshold.
Wherever the governing number lands, it is written where an author hits it — the [KB_GAP] below is the load-bearing finding, and a cap whose semantics are undocumented is worse than one whose number is, because the number invites arithmetic and the arithmetic looks correct.
check-substrate-size.mjs evaluates the Claude load path, following both the symlink and @-import forms — a check that reads only one form computes the wrong total, and the incident changed the file from one to the other.
.github/workflows/substrate-size-guard.yml watch paths include the Claude entry point, so a change to it triggers the guard at all.
A control reproduces the 2026-08-15 near-miss: .claude/CLAUDE.md importing AGENTS.md plus a 1,586 B second file fails; the symlink-only tree passes.
Headroom is reported, not just pass/fail — AGENTS.md sits ~196 B under its per-file limit today, and a number that close should be visible before it is a breach.
/turn-memory-pre-flight requires the measured byte delta as a recorded output for IN-SCOPE file changes, with the cap and current headroom in the payload rather than left for the author to discover.
The prompt-submit-vs-boot-load distinction is documented where loader changes are reviewed, with the fail-open/fail-silent consequence named.
Evidence class
Live near-miss with a full receipt: PR #17156 at 0fe621a623 (closed), review pullrequestreview-4943821244, ticket #17147 closed not-planned. Measured on dev 2026-08-15 and independently confirmed by @neo-opus-vega: AGENTS.md 24,380 B, NOW.md 1,586 B, file mode 120000 → 100644. Existing enforcement read at source: ai/scripts/diagnostics/check-substrate-size.mjs:14PER_FILE_LIMIT_BYTES, :17TARGET_FILES, :43COMBINED_BUDGETS; .github/workflows/substrate-size-guard.yml:48. Pre-flight gap from .agents/skills/turn-memory-pre-flight/references/turn-memory-pre-flight-workflow.md §Mechanical Pre-Flight Protocol.
🖖 Authored by Grace (Claude Opus 5, Claude Code). Session b17338dd-b474-494f-b08c-683044de2ddb. Corrected after peer falsification by @neo-opus-vega.
tobiu referenced in commit ca5c22b - "feat(build): the substrate guard measures the Claude load path (#17175) (#17764) on Aug 25, 2026, 5:51 PM
Problem
The substrate-size guard covers the Antigravity load path. It does not cover the Claude one.
check-substrate-size.mjsdeclaresPER_FILE_LIMIT_BYTES = 24576overTARGET_FILES = ['AGENTS.md', '.agents/ANTIGRAVITY_RULES.md'], described in the file as "the surfaces that Antigravity injects globally on every turn"..claude/CLAUDE.md— the Claude seat's entry point — is in neitherTARGET_FILESnor the workflow's watch paths.So a change to the Claude load path passes the guard by not being looked at.
The receipt: PR #17156 (closed) changed
.claude/CLAUDE.mdfrom a symlink into a regular file importing@../AGENTS.mdand@../NOW.md, adding 1,586 B alongsideAGENTS.md's 24,380 B. Exact-head CI was fully green — includingsubstrate-size-guard— because the guard measuredAGENTS.mdalone (24,380 B, under its per-file limit) and never evaluated what the Claude seat now reads together.The mechanism to fix it already exists in the same file.
COMBINED_BUDGETSwas built for exactly this, and its comment says so:This is a config entry plus a watch-path addition, not new tooling.
The combined-24,576 reading is FALSIFIED — measured on two live Claude seats
My #17156 review summed two files against 24,576 B and called the result a breach. That comparison is wrong, and it was falsified by the cheapest possible instrument: a Claude seat looking at its own context.
AGENTS.mdvia.claude/CLAUDE.mdMEMORY.mdBoth loaded complete on both seats, verified against end-markers rather than assumed:
AGENTS.md's final on-disk line (the§edge_case_triggersWake/Heartbeat bullet endingNightShiftLeasedDriver.md) is present verbatim in context.So the Claude seat does not enforce a 24,576 B combined ceiling over per-turn project context — it is presently carrying ~44 KB intact. "25,966 B, i.e. 1,390 B over" does not describe a breach on this harness.
What survives, narrower and sharper. Both measurements pair a symlink-resolved
CLAUDE.mdwith harness auto-memory — a different mechanism from two@-imports inside oneCLAUDE.md. A fair skeptic notes those may be budgeted separately, so the surviving question is:That is a deliberate one-file experiment, not an inference. It is also precisely the question #17156 would have answered by accident, in production, silently.
AC-1 is therefore "confirm the
@-import budgeting semantics", not "establish the cap" — the receipt above removes the combined-context reading from the candidate set, and framing it as unknown would invite someone to re-derive what is already measured.The Architectural Reality
COMBINED_BUDGETSalready models group cost and already carries one entry (the pr-review surface, 41,357 B), so the pattern is proven in production./turn-memory-pre-flightowns this surface and does not require the measurement. Its four mechanical commands establish how substrate loads, never how much; its decision tree asks where a rule belongs, never what it costs. The audit is fully satisfiable in prose — "the load path is named per harness ✓" — while never producing the fact that decides the change.The Fix
TARGET_FILESand/or aCOMBINED_BUDGETSentry expressing what that seat reads together, plus the workflow watch paths./turn-memory-pre-flightproduce the byte delta as a recorded output rather than leaving it implicit.Out of Scope
AGENTS.md. This makes the budget visible and enforced; what to do with the headroom is a separate call.lint-skill-manifest.Acceptance Criteria
@-import budgeting semantics are confirmed by a deliberate experiment: are multiple@-imports in one.claude/CLAUDE.mdbudgeted as one unit, and against what number? The two-seat receipt above is the control that rules out a combined-context ceiling at 24,576 B; everything below encodes the measured finding rather than an assumed threshold.[KB_GAP]below is the load-bearing finding, and a cap whose semantics are undocumented is worse than one whose number is, because the number invites arithmetic and the arithmetic looks correct.check-substrate-size.mjsevaluates the Claude load path, following both the symlink and@-import forms — a check that reads only one form computes the wrong total, and the incident changed the file from one to the other..github/workflows/substrate-size-guard.ymlwatch paths include the Claude entry point, so a change to it triggers the guard at all..claude/CLAUDE.mdimportingAGENTS.mdplus a 1,586 B second file fails; the symlink-only tree passes.AGENTS.mdsits ~196 B under its per-file limit today, and a number that close should be visible before it is a breach./turn-memory-pre-flightrequires the measured byte delta as a recorded output for IN-SCOPE file changes, with the cap and current headroom in the payload rather than left for the author to discover.Evidence class
Live near-miss with a full receipt: PR #17156 at
0fe621a623(closed), review pullrequestreview-4943821244, ticket #17147 closed not-planned. Measured ondev2026-08-15 and independently confirmed by @neo-opus-vega:AGENTS.md24,380 B,NOW.md1,586 B, file mode 120000 → 100644. Existing enforcement read at source:ai/scripts/diagnostics/check-substrate-size.mjs:14PER_FILE_LIMIT_BYTES,:17TARGET_FILES,:43COMBINED_BUDGETS;.github/workflows/substrate-size-guard.yml:48. Pre-flight gap from.agents/skills/turn-memory-pre-flight/references/turn-memory-pre-flight-workflow.md§Mechanical Pre-Flight Protocol.🖖 Authored by Grace (Claude Opus 5, Claude Code). Session b17338dd-b474-494f-b08c-683044de2ddb. Corrected after peer falsification by @neo-opus-vega.