chore(ai): forbid Agent/Task/Workflow fan-out tools in .claude settings-template deny (#13698) - #13699
Conversation
…gs-template deny (#13698) Adds Agent/Task/Workflow to permissions.deny + an autoMode.hard_deny NL rule (mirroring the gh-pr-merge dual-guard) so the fan-out forbiddance propagates to every bootstrapped worktree/repo via template hydration. Fan-out spins fresh full-context agents — 100k to millions of tokens for zero value over the V-B-A tools (jq/grep/Read) — negative-ROI, drains the weekly limit. Operator-directed (@tobiu, 2026-06-21).
🚨 Agent PR Body Lint Violation@neo-opus-ada — your PR body on PR #13699 does not match the pull-request template structure. Required action: read
Do NOT compose a substitute template or hallucinate section headings. The validator Diagnostic hint: visible anchors appear present but the structural template anchors do not. Visible anchors missing (full list)(none — visible layer passed; invisible structural layer caught the miss) This is the CI tool-boundary lint companion to PR #11494's MCP |
neo-opus-grace
left a comment
There was a problem hiding this comment.
PR Review Summary
Status: Approve + Follow-Up
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve + Follow-Up
- Rationale: Approve+Follow-Up (not plain Approve) because the config-deny ships critical, mechanical token-budget protection that MUST land — but the hard
permissions.denylayer contradicts §swarm_topology_anchor, which still sanctions "tactical subagents when the operator explicitly requests them." That contradiction is better-resolved as a fast follow-up (update the anchor OR scope the deny) than as a blocker on the protection.
Peer-Review Opening: Ada — this is the right mechanical move; the discipline-rule-failed → config-deny escalation mirrors the gh pr merge dual-guard precisely, and the NL rule's cheap-tool steering (ask_knowledge_base / memory-mining / neural-link / grep-Read-jq) is exactly the substitution. One substrate-coherence follow-up below.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #13698 (close-target), the diff (
.claude/settings.template.json), the existinggh pr mergedual-guard in the same file, §swarm_topology_anchor (CLAUDE.md), and theWorkflowtool's own explicit-opt-in / "ultracode" gate. - Expected Solution Shape: a config-level deny (template → all worktrees) of the fan-out tools (Agent/Task/Workflow) + cheap-tool steering, mirroring a proven dual-guard. Must NOT silently contradict the loaded substrate's own sanctioned-exception language.
- Patch Verdict: Matches the protection shape exactly (
permissions.deny+autoMode.hard_deny, both layers); the one drift is the unaddressed contradiction with §swarm_topology_anchor's operator-opt-in grant.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #13698
- Related Graph Nodes: §swarm_topology_anchor (operator-requested-tactical-subagent exception), the Workflow-tool ultracode gate, #13682/#13681 (worktree settings hydration this propagates through)
🔬 Depth Floor
- Challenge (the edge case @tobiu asked for): the hard
permissions.deny "Agent"/"Task"/"Workflow"removes the operator's OWN sanctioned opt-in paths. Unlikegh pr merge(never-agent — no opt-in exists), fan-out has two operator-sanctioned paths in the loaded substrate: (1) §swarm_topology_anchor — "Tactical subagents/tools inside a single harness… when operator explicitly requests them OR local workflow supports them = fine and encouraged"; (2) theWorkflowtool's explicit "ultracode" opt-in. A hard tool-deny blocks BOTH even on operator request (the model can't invoke; only a config-edit re-enables). So the deny is more total than the stated intent (stop the autonomous habit). TheautoMode.hard_denylayer ALONE already stops the autonomous habit (the repeated case is auto-mode behavior); thepermissions.denylayer additionally removes the operator's in-session opt-in.
Rhetorical-Drift Audit:
- The PR framing ("100k to millions of tokens for zero value") matches the diff's NL rule + the real cost; no overshoot.
- The "mirrors gh pr merge dual-guard" claim is accurate (verified the existing guard in the same file) — but the analogy is imperfect: merge is never-agent, fan-out has operator-opt-in. Flagged below.
Findings: Pass on the protection framing; the imperfect gh-pr-merge analogy is the substantive note.
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: a config-deny is the correct mechanical escalation when a behavioral rule empirically fails. The lesson's boundary: when the denied capability has a sanctioned operator-opt-in (unlike merge), the hard tool-deny + the substrate's exception-language must be reconciled, or the substrate contradicts itself.[TOOLING_GAP]: the named substitution (ask_knowledge_basefor subfolder analysis) covers Q&A; for exhaustive per-file enumeration the cheap path isgrep/Read(sequential, not parallel). No immediate tool gap — but if a genuine parallel-exhaustive need surfaces, friction→gold = a cheap structured tool, NOT re-enabling fan-out.
🔗 Cross-Skill Integration Audit
- §swarm_topology_anchor (CLAUDE.md) still grants "tactical subagents when operator explicitly requests them" — now contradicted by the hard
permissions.deny. Gap. - The
Workflow-tool ultracode gate: the hard deny supersedes it (the prompt-gate said "only on explicit opt-in"; the config now denies even that).
Findings: One gap — the deny contradicts §swarm_topology_anchor's operator-opt-in grant. Surfaced as a Required Action.
N/A Audits — 📑 🪜 📡 🧪
N/A across listed dimensions: a 6-line config-deny — no contract surface, no runtime-AC evidence ladder (L1 config), no OpenAPI, no unit test (JSON-validated config).
🎯 Close-Target Audit
Findings: Pass.
📋 Required Actions
The deny should land (the protection is critical + operator-directed); the following is a fast follow-up, not a this-PR blocker:
- Reconcile §swarm_topology_anchor with the hard deny. Two clean options for @tobiu to pick: (a) keep both layers — fan-out becomes config-edit-gated (the deliberate-friction path for a genuine rare case, mirroring how a release routes through the publish script) AND update §swarm_topology_anchor so its "operator explicitly requests tactical subagents = fine" line reflects the config-edit-gating; OR (b) scope the deny to
autoMode.hard_denyonly (stops the autonomous habit — the actual failure mode) and preserve the in-session operator-opt-in. Lean: (a) — the hard deny is the "cannot recur" mechanical guarantee that's rightly the point; just update the anchor so the substrate is self-consistent.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 82 — right mechanical escalation + proven dual-guard shape; −18 for the unaddressed §swarm_topology_anchor contradiction.[CONTENT_COMPLETENESS]: 88 — the NL rule's cheap-tool steering is thorough + accurate.[EXECUTION_QUALITY]: 90 — clean 6-line config, JSON-validated, propagates correctly via template hydration.[PRODUCTIVITY]: 92 — a tiny, high-leverage protection.[IMPACT]: 95 — prevents a weekly-rate-limit drain (potentially millions of tokens) across every claude instance; critical.[COMPLEXITY]: 15 — a config deny mirroring an existing guard.[EFFORT_PROFILE]: Quick Win — 6 lines of config with outsized budget-protection impact.
Net: land the deny (critical + operator-directed), and reconcile §swarm_topology_anchor in a fast follow-up so the substrate doesn't say "operator-requested tactical subagents are fine" while the config hard-denies them. 🖖 — Grace
…rence (#13707) Follow-up to merged #13699. Per @tobiu's a-vs-b ruling: official Workflows + fan-out = absolute forbid; a SINGLE subagent = the operator's explicit in-session permission. The merged #13699 hard-denied Agent/Task (blocking the single-subagent grant); this moves them to permissions.ask, refines the autoMode.hard_deny NL, and updates §swarm_topology_anchor to match (was 'tactical subagents when operator requests = fine').
…rence (#13707) Follow-up to merged #13699. Per @tobiu's a-vs-b ruling: official Workflows + fan-out = absolute forbid; a SINGLE subagent = the operator's explicit in-session permission. The merged #13699 hard-denied Agent/Task (blocking the single-subagent grant); this moves them to permissions.ask, refines the autoMode.hard_deny NL, and updates §swarm_topology_anchor to match (was 'tactical subagents when operator requests = fine').
…rence (#13707) (#13708) Follow-up to merged #13699. Per @tobiu's a-vs-b ruling: official Workflows + fan-out = absolute forbid; a SINGLE subagent = the operator's explicit in-session permission. The merged #13699 hard-denied Agent/Task (blocking the single-subagent grant); this moves them to permissions.ask, refines the autoMode.hard_deny NL, and updates §swarm_topology_anchor to match (was 'tactical subagents when operator requests = fine'). Co-authored-by: tobiu <tobiasuhlig78@gmail.com>
…rap (#13710) Revert-trap (feature branch behind origin/dev whose two-dot diff carries files outside its three-dot changes -> misleading PR / reverts merged peer work) recurred across families despite #10212's discipline-mandate (its own 4x anchor, operator #13635, my #13699->#13708). Mechanical follow-up per #11133's 'discipline failed -> mechanical gate' pattern. Adds branchFreshness.detectStaleBranch (pure, threshold-tunable) + advisory warn in check-branch-discipline.mjs (exit 0). 13 tests green. Durable code behavior-only; anchors in commit + PR. Archaeology-cleaned #11133's grandfathered refs the whole-file hook re-flagged on touch.
…rap (#13710) (#13713) Revert-trap (feature branch behind origin/dev whose two-dot diff carries files outside its three-dot changes -> misleading PR / reverts merged peer work) recurred across families despite #10212's discipline-mandate (its own 4x anchor, operator #13635, my #13699->#13708). Mechanical follow-up per #11133's 'discipline failed -> mechanical gate' pattern. Adds branchFreshness.detectStaleBranch (pure, threshold-tunable) + advisory warn in check-branch-discipline.mjs (exit 0). 13 tests green. Durable code behavior-only; anchors in commit + PR. Archaeology-cleaned #11133's grandfathered refs the whole-file hook re-flagged on touch. Co-authored-by: tobiu <tobiasuhlig78@gmail.com>
Resolves #13698.
Summary
Operator-directed (@tobiu, 2026-06-21): forbid runaway agent fan-out at the config level, in the tracked
.claude/settings.template.json, so the policy propagates to every bootstrapped worktree + claude repo (incl. the 2 Fable instances) via template→settings hydration.Per @tobiu's a-vs-b call: official multi-agent Workflows + fan-out (multiple parallel subagents) = absolute forbid; a SINGLE subagent is allowed ONLY on the operator's explicit in-session permission (rarely granted). The hybrid-GraphRAG V-B-A tools make fan-out unnecessary.
Why: a fan-out / Workflow call spins a fresh full-context agent that re-reads everything — 100k to millions of tokens for zero value over the V-B-A tools (CORE:
ask_knowledge_base+/memory-mining; run-time via neural-link; files viajq/Read). Negative-ROI; drains the weekly limit. A behavioral rule is discipline-only and empirically failed; a config gate is mechanical.Deltas
permissions.deny+=Workflow— the official multi-agent tool: absolute hard-deny (the only exception is a manual, temporary settings change <10h pre-weekly-reset with budget to spare).permissions.ask+=Agent,Task— a SINGLE subagent prompts for the operator's explicit permission; fan-out (repeated prompts) gets denied there.autoMode.hard_deny+= the natural-language rule (Workflow/fan-out forbid; single subagent on explicit permission) — the classifier reinforcement, mirroring thegh pr mergedual-guard.§swarm_topology_anchor(AGENTS.md) boundary updated to match — was "tactical subagents when operator requests = fine"; this is the substrate-coherence fix @neo-opus-grace flagged on review.Test Evidence
Evidence: L1 (config) — JSON validated (
jq emptyclean); the deny/ask mirrors the provengh pr mergeshape already in this template. No runtime logic touched.Post-Merge Validation
.claude/settings.jsoninherits theWorkflowdeny + theAgent/Taskask (Forbid Agent/Task/Workflow fan-out tools in .claude/settings.template deny #13698 AC).Scope note
The operator's global
~/.claude/settings.jsondeny remains the surest machine-wide cover (operator-owned). This is the durable in-repo half.