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
GateGuard's Edit/Write fact-force gate is per-file first-touch for the entire session. Scaffolding or multi-file implementation work produces a long trail of denials (denial #1 … denial #14 …) that re-enter the context window even after the model has already learned the four-fact protocol.
One (or a few) fact-force cycles per session is enough. After that the LLM remembers the pattern; re-injecting the same instruction on every new file is pure context tax and works against the dampening goal of #2142.
Suggested optimal workflow
GateGuard should feel like onboarding the agent once, not like a toll booth on every file.
Session shape (what we want)
t=0 Session starts, user asks for a multi-file feature / scaffold
t=1 Agent investigates (Grep/Read) for real
t=2 FIRST Edit or Write this session
→ Gate DENIES once (full four-fact block)
t=3 Agent states facts once, carefully:
- importers/callers (or "new file; will be called by X")
- public API surface affected
- data schemas / formats if any
- user instruction verbatim
t=4 Agent retries the same tool call → ALLOWED
t=5…N Remaining Edit/Write/MultiEdit on other files in this session
→ ALLOWED without another fact-force cycle
(model already has the protocol + project context in-window)
later Destructive Bash (rm -rf, drop table, force-push, …)
→ still DENY once per distinct command, always
Why this is optimal
Concern
Per-file forever (today)
Once-per-session protocol (proposed)
Investigation quality
High on file 1, ritual on files 2–20
High on the one forced pass; later work uses real context
Context cost
Deny + restate + retry × N files
Deny + restate + retry × 1 (or small N)
Trust / safety
Same first-touch tax on every path
Still forces a deliberate start; destructive stays strict
Aligns with #2142 dampening and reduces turn count
The load-bearing insight: the value of fact-forcing is teaching the agent to investigate before acting, not re-teaching it on every path. After one good cycle, the same session's later Writes are usually the same plan, same schemas, same user instruction — re-gating them is noise.
Concrete defaults (direct recommendation)
GATEGUARD_FACT_FORCE_FULL_DENIALS = 1 # one full four-fact block
GATEGUARD_FACT_FORCE_MAX_DENIALS = 1 # NEW: then allow further first-touches
Optional slightly stricter default for teams that want a short ramp:
FULL_DENIALS = 1
MAX_DENIALS = 3 # full once, two condensed, then free first-touch
Destructive Bash: unchanged (always first-attempt deny per command fingerprint).
Routine Bash: keep once-per-session or default-off (#2573); not part of this proposal.
Agent-side ritual (what the one cycle should look like)
On the single forced Edit/Write deny, the agent should:
Search before inventing — Grep/Glob for importers and same-purpose modules.
Name the contract — public functions/classes, or "new module; caller will be services/ingestion.py".
Show schema only if data is involved — field names + formats with synthetic values; skip with "no data files" otherwise.
Quote the user instruction — one verbatim line/block.
Retry the same tool call — no re-planning theatre.
After that succeeds, treat the protocol as established for the rest of the session. Do not restate the four facts before every subsequent file.
Operator mental model (one sentence)
"GateGuard makes me prove I investigated at the start of the session; it does not charge me again for every file in the same plan."
That is the product behavior operators already expect from the name of GATEGUARD_FACT_FORCE_FULL_DENIALS. Implement MAX_DENIALS (or redefine the existing knob as a hard stop) so the product matches that mental model.
#2142 reduced message size. It did not reduce denial count. Condensed denials still fire, still block the tool, still force a retry turn, and still accumulate in context.
Observed behavior (ECC 2.0.0)
During a greenfield multi-file scaffold (many first Writes of new modules):
[Fact-Forcing Gate] (denial #14 this session) First creation of
…/app/services/chunking.py: briefly state importers/callers,
affected API, data schemas if any, and the user's verbatim instruction,
then retry. (ECC_GATEGUARD=off disables this gate.)
Expectation from the name/docs of GATEGUARD_FACT_FORCE_FULL_DENIALS: after the first full denial, stop (or soft-pass).
Actual: every new path still denies once; only the wording shortens. Denial ordinal keeps climbing.
Hook path: scripts/hooks/gateguard-fact-force.js — Edit/Write branch keys on file_path (isChecked / markCheckedAndCountDenial). getFullDenialBudget() only chooses full vs condensed message; it never allows an unchecked path.
Why this is a product bug, not operator error
Diminishing returns. The first deny forces investigation (importers, schemas, verbatim user instruction). Denials 2–N for different new files usually get a ritual recitation of the same template, not new awareness — especially when building a vertical slice from a handoff doc.
Misleading knob.GATEGUARD_FACT_FORCE_FULL_DENIALS reads like a session budget for fact-forcing. It is only a message-shape budget. Operators set it to 1 expecting "once is enough" and still hit denial 🎉 v1.0.0 Released - Now a Claude Code Plugin #14.
Nuclear recovery only. Condensed messages point at ECC_GATEGUARD=off. There is no graduated "stop first-touch after N denials this session" knob.
Routine Bash is already once-per-session (__bash_session__). Edit/Write has no equivalent session ceiling.
Proposal
Preferred: session-wide first-touch budget that actually stops
Add something like:
GATEGUARD_FACT_FORCE_MAX_DENIALS # default e.g. 3 (or same as FULL_DENIALS)
Semantics:
Denial ordinal
Behavior
1 … FULL_DENIALS
Full four-fact block (current)
FULL_DENIALS+1 … MAX_DENIALS
Condensed one-liner (current dampening)
> MAX_DENIALS
Allow first-touch without denying; optionally one stderr note once per session
Set MAX_DENIALS=0 or FULL_DENIALS=0 + MAX_DENIALS=0 for "never fact-force Edit/Write" without killing the destructive-Bash gate.
Simpler alternative that matches user mental model:
Treat GATEGUARD_FACT_FORCE_FULL_DENIALS as the hard stop after which further first-touches are allowed (not merely condensed). That is a breaking semantic change — so a new MAX_DENIALS env is safer, with docs clarifying the two knobs.
Also good
Document the per-file vs per-session distinction in skills/gateguard/SKILL.md in one sentence: "Edit/Write gates the first touch of each path; setting FULL_DENIALS only shortens later messages, it does not stop denials."
Recovery hint on condensed denies should mention the new budget env, not only ECC_GATEGUARD=off.
Optional: once-per-session "protocol established" key — after one successful fact-present + retry on any file, subsequent first-touches of new files in the same session auto-allow (aggressive; MAX_DENIALS is more tunable).
Non-goals
Do not weaken the destructive-Bash gate.
Do not remove first-touch entirely by default — N=1..3 of real investigation still has value.
Subagent passthrough (parent already gated) can stay as-is.
Summary
GateGuard's Edit/Write fact-force gate is per-file first-touch for the entire session. Scaffolding or multi-file implementation work produces a long trail of denials (
denial #1…denial #14…) that re-enter the context window even after the model has already learned the four-fact protocol.One (or a few) fact-force cycles per session is enough. After that the LLM remembers the pattern; re-injecting the same instruction on every new file is pure context tax and works against the dampening goal of #2142.
Suggested optimal workflow
GateGuard should feel like onboarding the agent once, not like a toll booth on every file.
Session shape (what we want)
Why this is optimal
The load-bearing insight: the value of fact-forcing is teaching the agent to investigate before acting, not re-teaching it on every path. After one good cycle, the same session's later Writes are usually the same plan, same schemas, same user instruction — re-gating them is noise.
Concrete defaults (direct recommendation)
Optional slightly stricter default for teams that want a short ramp:
Destructive Bash: unchanged (always first-attempt deny per command fingerprint).
Routine Bash: keep once-per-session or default-off (#2573); not part of this proposal.
Agent-side ritual (what the one cycle should look like)
On the single forced Edit/Write deny, the agent should:
Grep/Globfor importers and same-purpose modules.services/ingestion.py".After that succeeds, treat the protocol as established for the rest of the session. Do not restate the four facts before every subsequent file.
Operator mental model (one sentence)
That is the product behavior operators already expect from the name of
GATEGUARD_FACT_FORCE_FULL_DENIALS. ImplementMAX_DENIALS(or redefine the existing knob as a hard stop) so the product matches that mental model.Related
GATEGUARD_FACT_FORCE_FULL_DENIALS: after the budget, denials become a condensed one-liner instead of the full four-fact block.#2142 reduced message size. It did not reduce denial count. Condensed denials still fire, still block the tool, still force a retry turn, and still accumulate in context.
Observed behavior (ECC 2.0.0)
During a greenfield multi-file scaffold (many first
Writes of new modules):Operator already had:
{ "env": { "GATEGUARD_BASH_ROUTINE_DISABLED": "1", "GATEGUARD_FACT_FORCE_FULL_DENIALS": "1", "GATEGUARD_EXEMPT_GLOBS": "**/tests/**,**/docs/**,**/*.md,…" } }Expectation from the name/docs of
GATEGUARD_FACT_FORCE_FULL_DENIALS: after the first full denial, stop (or soft-pass).Actual: every new path still denies once; only the wording shortens. Denial ordinal keeps climbing.
Hook path:
scripts/hooks/gateguard-fact-force.js— Edit/Write branch keys onfile_path(isChecked/markCheckedAndCountDenial).getFullDenialBudget()only chooses full vs condensed message; it never allows an unchecked path.Why this is a product bug, not operator error
GATEGUARD_FACT_FORCE_FULL_DENIALSreads like a session budget for fact-forcing. It is only a message-shape budget. Operators set it to1expecting "once is enough" and still hit denial 🎉 v1.0.0 Released - Now a Claude Code Plugin #14.ECC_GATEGUARD=off. There is no graduated "stop first-touch after N denials this session" knob.Routine Bash is already once-per-session (
__bash_session__). Edit/Write has no equivalent session ceiling.Proposal
Preferred: session-wide first-touch budget that actually stops
Add something like:
Semantics:
1 … FULL_DENIALSFULL_DENIALS+1 … MAX_DENIALS> MAX_DENIALSSet
MAX_DENIALS=0orFULL_DENIALS=0+MAX_DENIALS=0for "never fact-force Edit/Write" without killing the destructive-Bash gate.Simpler alternative that matches user mental model:
GATEGUARD_FACT_FORCE_FULL_DENIALSas the hard stop after which further first-touches are allowed (not merely condensed). That is a breaking semantic change — so a newMAX_DENIALSenv is safer, with docs clarifying the two knobs.Also good
skills/gateguard/SKILL.mdin one sentence: "Edit/Write gates the first touch of each path; setting FULL_DENIALS only shortens later messages, it does not stop denials."ECC_GATEGUARD=off.Non-goals
Workaround (today)
{ "env": { "ECC_GATEGUARD": "off" } }or surgically:
{ "env": { "ECC_DISABLED_HOOKS": "pre:edit-write:gateguard-fact-force" } }(
GATEGUARD_FACT_FORCE_FULL_DENIALS=1is not a sufficient workaround for multi-file scaffolds.)Env changes require a new Claude Code session to take effect.
Environment
ecc@ecc), marketplaceaffaan-m/ECCWrites per session)Acceptance criteria
NEdit/Write fact-force denials in a session, further first-touches of new paths are allowed without a deny (configurableN).