Lesson 4 of 7
Permissions, hooks, subagents and headless runs
9 min
Listen to this lesson
CLAUDE.md asks. Settings enforce. This lesson turns Contoso's rules into a .claude/settings.json that the repository shares, three hooks that run checks without relying on the model, a reviewer subagent, and a headless review job. The organization-wide layer (managed settings, approved MCP servers, telemetry) is in sap-aid-302, lesson 3; everything here sits underneath it.
Permission rules for the ADT tools
Claude Code evaluates rules in the order deny, then ask, then allow; the first match decides, and a deny at any scope can't be overridden by an allow elsewhere. MCP tools are matched by name: mcp__adt__* covers every tool of the adt server, and mcp__adt__abap_edit_file one tool. Rules with parentheses, such as matching an MCP tool's parameters, are skipped when a settings file loads, so plan your policy at tool level.
| List | Tools | Contoso's reasoning |
|---|---|---|
| deny | Exemption requests, AI fixes, feed query, transport creation | Exemptions and requests are human decisions; AI fixes consume Joule tokens and add a second AI's edits to review; feeds can show other users' runtime errors and business data |
| ask | Edit, create, generate, quick fixes, custom actions, git push | Anything that changes an object or leaves the laptop gets a person's yes, every time |
| allow | Search, read, check, activate, ABAP Unit, ATC, transport display and diff | Reading and checking are the agent's main job; prompting for each would train people to click yes without reading |
Activation is allowed because it only promotes what a person already approved as an edit; some teams put it under ask during the pilot.
This is stricter than the team-wide baseline in sap-aid-302, which asks rather than denies transport creation. Both are defensible; Contoso's core agreement team decided that the developer always creates the request and names it in the prompt.
Hooks: checks that don't depend on the model
Hooks are commands (or HTTP calls, prompts, agents or MCP tool calls) that Claude Code runs at fixed events. For ABAP work, three are worth having:
- Local lint on abapGit files (PostToolUse, matcher
Edit|Write). A script runs abaplint, an open-source ABAP linter that works on abapGit-serialized files, pinned to one version as its README recommends (for examplenpx @abaplint/cli@2.120.81). If it finds errors it writes them to stderr and exits 2; for PostToolUse, Claude Code shows that stderr to Claude, which fixes the file. abaplint isn't ATC: it doesn't know your system's dictionary unless you give it dependencies, and it's no substitute for activation in CMD. - An evidence gate (Stop). When Claude tries to finish, a script reads the hook input's
transcript_path, finds the last call tomcp__adt__abap_edit_fileorabap_activate_objects, and checks thatabap_run_unit_testsandabap_atc_get_resultran after it. If not, it exits 2 with "Run ABAP Unit and ATC before reporting"; for Stop, exit 2 keeps Claude working. The script checksstop_hook_activeso it never loops, and Claude Code caps consecutive Stop-hook continuations at eight anyway. - An audit line (PreToolUse, matcher
mcp__adt__.*). Appends session id, tool name and input to a log that is shipped centrally. Hook matchers are regular expressions here, so the.*is required;mcp__adtalone matches nothing. Contoso deploys this one as a managed hook (sap-aid-302).
A reviewer subagent
Subagents are Markdown files in .claude/agents/ with YAML frontmatter. Each runs in its own context window with its own tools and permission mode, which makes a good second pair of eyes: it hasn't seen the main session's reasoning, only the change.
Headless runs: review, not remote control
claude -p runs Claude Code non-interactively for scripts and CI. Contoso uses it in one place: a pipeline job on each abapGit pull request that reviews the diff. Two details matter. Without --bare, a -p run loads the repository's hooks and connects servers from .mcp.json without any trust prompt; with --bare it skips hooks, MCP servers, subagents and CLAUDE.md, so the job passes rules explicitly. And the job never gets an ADT connection: the ADT MCP server runs only inside an IDE, and a build server has no business logging on to CMD. sap-aiq-201 uses the same locked-down claude -p pattern to summarize SAP test results.
- Pull request openedBranch cmdk900452-overlap-check against main in repository cmc-core-agreement
- Diff piped ingit diff origin/main...HEAD -- src/ | claude --bare -p "Review this ABAP diff"
- Rules passed explicitly--append-system-prompt-file review-rules.md (a copy of the CLAUDE.md review section)
- Locked down--permission-mode dontAsk denies anything not pre-approved; no MCP servers
- Result posted--output-format json; the result field becomes a pull request comment
- People decideTwo approvals, one from the package owner; the comment is advice, not a gate
- Deny exemptions, AI fixes, feeds and transport creation; ask for edits, creation and pushes; allow reads, checks, tests and diffs.
- MCP permission rules work at tool level: mcp__adt__* or mcp__adt__<tool>; parenthesized MCP rules are skipped.
- Hooks enforce what CLAUDE.md asks: local lint on abapGit files, a Stop-hook evidence gate, an audit line.
- Only exit 2 blocks; timeouts don't; mcp_tool hook output on PostToolUse doesn't reach Claude.
- Use claude -p --bare for CI review of git diffs, never to drive an SAP system unattended.
Check your understanding · not graded
Lesson check
1. A settings file contains the deny rule mcp__adt__abap_feed_query and the allow rule mcp__adt__*. Can Claude call abap_feed_query?
2. A PostToolUse hook that runs abaplint exits with code 2 and writes the findings to stderr. What happens?
3. Why does Contoso's CI review job use claude -p with --bare?
4. What is the main benefit of running the ABAP review as a subagent?