The gate is content-bound and fails closed. Read what it told you, then use the owning command — or stop and say the path is currently unreachable.
What the gate actually is
scripts/protected-state-check.js runs on every pre-commit (lefthook job
protected-state, no glob, so it sees every commit). For each staged file it
calls assertProtectedWriteAllowed, and any file that matches a protected
surface needs a one-time, content-bound Kernel capability issued for the
exact actor, worktree, surface, path, bytes, and source HEAD.
[verified 2026-08-13 — lib/protected-state-authority.js]
Ask the code which surface a path is on rather than guessing:
node -e "console.log(require('./lib/protected-state-surfaces').assertProtectedWriteAllowed('AGENTS.md'))"
Three things that are commonly believed and are wrong
-
FORGE_PROTECTED_STATE_ALLOWED_SURFACESdoes not authorize anything. No runtime code path reads it. It appears only in prose docs and one test fixture.docs/reference/protected-state-surfaces.mdsays so itself at the "Surface-only environment declarations do not authorize protected changes" line, and then contradicts itself further down — the contradiction is a documentation bug, not a hidden door. [verified 2026-08-13] -
The
categories:block in.forge/protected-paths.yamlis not what runs. Runtime enforcement uses a hardcodedPROTECTED_SURFACESlist inlib/protected-state-surfaces.js. The manifest's W1 categories (forge_core,user_protocol,generated_artifacts) are documentation. Solib/**,scripts/**, andskills/**— listed underforge_corein the manifest — are not blocked at runtime today, whileAGENTS.mdis, under the legacy surface idgenerated_harness. [verified 2026-08-13] -
--no-verifyis not the answer. Bypassing hooks is forbidden for agents in this repo, and a hook you route around protects nobody. If the gate is wrong, the gate gets fixed in its own PR. [Forge #9 ×3]
Procedure when you are blocked
- Read the blocked output. It names
path,requiredSurface,reason, andrepairHint. The repair hint is the owning command. - Regenerate through the owning command rather than hand-editing:
lockfiles → the package manager;
.forge/config.yaml→ Forge config/setup; harness mirrors →forge setup;.beads/**→forge migrate --from beadsthen Forge issue commands. - Stage exactly what that command produced. The authorization is bound to the bytes; any later touch invalidates it.
- If no owning command can produce the change you need — stop. File a kernel issue naming the path, the surface, and the missing writer, and say plainly in your report that the change is currently unshippable. Do not invent an exemption, do not add a repo-local carve-out, do not weaken the check to let your own edit through.
Known unreachable path (2026-08-13)
AGENTS.md classifies as generated_harness, and the only command that can
issue a protected-state authorization is forge release generate-npm-workflow.
There is therefore no way to commit an AGENTS.md edit today — including an
edit confined to the USER:START/USER:END block that the product tells users
to write in. Treat that as a filed product bug, not as something to work around.
[verified 2026-08-13 — NPM_WORKFLOW_SOURCE_COMMAND, lib/protected-state-authority.js]
Done when
The commit passes the gate because the owning command produced the bytes — or you have filed the issue and reported the blocker in plain words without shipping a bypass.