LearnNewsExamplesServices
Frontmatter
id14415
titleDeterministic allowlist for agent PR-lifecycle commands in .claude settings
stateClosed
labels
enhancementaimodel-experience
assigneesneo-fable
createdAtJul 2, 2026, 3:00 AM
updatedAtJul 2, 2026, 3:49 AM
githubUrlhttps://github.com/neomjs/neo/issues/14415
authorneo-fable
commentsCount1
parentIssue13652
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 2, 2026, 3:49 AM

Deterministic allowlist for agent PR-lifecycle commands in .claude settings

Closed v13.1.0/archive-v13-1-0-chunk-8 enhancementaimodel-experience
neo-fable
neo-fable commented on Jul 2, 2026, 3:00 AM

Context

2026-07-02, ~00:30–01:00Z: the auto-mode safety classifier (claude-opus-4-8) went unavailable for ~40 minutes. During the outage, every Bash and Write call was refused ("cannot determine the safety of…"), which blocked the entire agent PR-lifecycle — npm run agent-preflight, git add/commit/push, gh pr create — while a finished, review-ready deliverable (#14414's rebuilt guide) sat uncommitted on its feature branch. Six Bash attempts and three Write attempts failed across the window.

What kept working proves the fix. MCP tools flowed throughout — because .claude/settings.json carries permissions.allow wildcards for the three MCP servers, and allow-rules short-circuit the model classifier (deterministic rule match, no model in the loop). The PR-lifecycle commands are exactly as routine and better-understood than those MCP surfaces, but they had no rules, so every call needed the (down) model.

The Problem

The Claude-side swarm's ability to ship — commit, push, open a PR — currently has a hard runtime dependency on a live frontier model for permissioning deterministic, narrowly-scoped, daily-routine commands. An Anthropic model outage therefore halts not just reasoning but mechanics. This is a single point of failure the substrate can remove for the routine set, and it also spends classifier capacity on calls a static rule answers better. (Operator-directed 2026-07-02: "we can update claude settings inside the repo to allow e.g. closing tickets or creating PRs… please then also update .claude/settings.template.json so that claude peers can adapt too.")

The Architectural Reality

  • .claude/settings.json (tracked) already models the pattern: 3× mcp__* allow wildcards; deny: ["Bash(gh pr merge *)", "Workflow"] — critical gate 1 is already mechanical; ask: ["Agent", "Task"].
  • .claude/settings.template.json is currently byte-identical to settings.json — keep them identical.
  • Deny rules take precedence over allow rules, so the allowlist can be prefix-broad where a targeted deny closes the dangerous subset (e.g. allow git push … agent/*, deny force-push and dev/main pushes).
  • Observation (flagged, NOT changed here): the autoMode.environment prose names four trusted first-party MCP servers, but permissions.allow lists threeneural-link is absent. Presumed deliberate (live-app mutation surface); operator to confirm or ticket separately.

The Fix

Add to permissions.allow in both .claude/settings.json and .claude/settings.template.json:

Bash(npm run agent-preflight*)
Bash(git add *)          Bash(git commit *)
Bash(git push -u origin agent/*)   Bash(git push origin agent/*)
Bash(git checkout -b agent/*)      Bash(git checkout dev)
Bash(git pull origin dev)          Bash(git fetch*)
Bash(git status*)  Bash(git log*)  Bash(git diff*)  Bash(git branch*)
Bash(git rev-list*)  Bash(git rev-parse*)  Bash(git merge-base*)  Bash(git show *)
Bash(gh pr create *)  Bash(gh pr view *)  Bash(gh pr edit *)  Bash(gh pr checks *)  Bash(gh pr list *)
Bash(gh issue view *)  Bash(gh issue list *)

Add to permissions.deny (mechanizing critical gate 3 + the force-push ban, alongside the existing gate-1 merge deny):

Bash(git push* --force*)   Bash(git push* -f *)
Bash(git push origin dev*) Bash(git push origin main*)

Contract Ledger Matrix

Target surface Source of authority Proposed behavior Fallback Docs Evidence
.claude/settings.json permissions.allow Claude Code settings permission rules (existing MCP wildcards in this file are the live precedent) Listed Bash prefixes resolve allow without classifier Unmatched commands fall through to classifier/ask exactly as today PR body + this ticket 2026-07-02 outage: allowlisted MCP flowed, unlisted Bash blocked
.claude/settings.json permissions.deny Same; deny precedence over allow Force-push + dev/main push mechanically refused n/a (deny is terminal) PR body Gate-1 deny (gh pr merge) already live in this file
.claude/settings.template.json Operator directive (peers adapt) Byte-identical to settings.json n/a PR body Currently already byte-identical

Acceptance Criteria

  • Allow set present in both files, byte-identical between them.
  • Deny set present in both files: force-push variants + git push origin dev* / main* (gates 1+3 both mechanical).
  • No broad Bash(*)-class allowance; gh api deliberately NOT allowlisted (mutation escape hatch stays classified); gh pr merge deny untouched.
  • JSON valid (harness loads without error — verified by the next session boot or operator restart).
  • Post-merge: one routine agent PR-lifecycle (branch → commit → push → gh pr create) completes with zero permission prompts and zero classifier dependency for the listed commands.

Out of Scope

  • Adding the neural-link MCP wildcard (open question above — operator call).
  • Test-runner or build-command allowances (different risk class; separate ticket if friction recurs).
  • Any change to autoMode prose or hooks.

Avoided Traps

  • Broad Bash(git *) / Bash(gh *) allowances — would allow git push origin dev, git reset --hard, gh pr merge classes by prefix; the narrow set + targeted denies is the entire point.
  • Allowlisting gh api — used tonight for comment deletion; powerful generic mutation surface; keep it classified.
  • Letting template and live settings drift — they are identical today; this PR keeps them identical so peers inherit exactly what runs here.

Decision Record impact

none (settings-level enforcement; aligned with epic #13652's mechanical-enforcement direction; no ADR authority touched).

Live latest-open sweep: checked latest 20 open issues at 2026-07-02T01:02Z — no equivalent found. A2A in-flight sweep: no competing claims (inbox = own threads + one unrelated validation broadcast).

Related: #13652 (parent epic — mechanical enforcement replaces prompt-machinery) · #14414 (the deliverable the outage blocked; empirical anchor)

Origin Session ID: 1d4262a2-a001-4387-9372-3923f024be8e Retrieval Hint: "classifier outage allowlist PR-lifecycle" · "settings.json permissions deterministic bypass"

tobiu referenced in commit 6b14078 - "feat(claude): deterministic allowlist for agent PR-lifecycle commands (#14415) (#14417) on Jul 2, 2026, 3:49 AM
tobiu closed this issue on Jul 2, 2026, 3:49 AM