🧭 How toLeadershipIntermediate✨ AI-assisted

How to Write a Coding Agent Policy Your Team Will Follow

WittyTech Daily··5 min read
#coding-agents#github#codeowners#governance

A coding agent policy that lives in a wiki gets read once and ignored forever. One that lives in the merge button gets followed every day. By the end of this guide you'll have a one-page policy plus four controls that enforce it: CODEOWNERS, branch protection set with the gh CLI, secret scanning push protection and a CI check on commit trailers. You need admin rights on a GitHub repository and the gh CLI signed in.

1. Write the one-page coding agent policy

Treat it as an acceptable use policy for AI that fits on one screen. Four sections, each answering one question: what can the agent see, what can it touch, who reviews its output, and who answers for it.

Coding Agent Policy (v1)   Owner: <eng lead>   Review: every quarter

1. Data: what an agent may see
   - OK: this repo's source, public docs, synthetic test data.
   - Never: secrets, .env files, customer data, production dumps.

2. Reach: what an agent may touch
   - Agents work on branches and open PRs. No pushes to main.
   - Changes under infra/, auth/, migrations/ and .github/ need their owners' approval.

3. Review: who checks the output
   - A human other than the person who prompted the agent approves every PR.
   - Agent PRs pass the same tests as any other PR.

4. Attribution: who answers for it
   - Agent-assisted commits keep the agent's Co-authored-by trailer.
   - They also carry a human Signed-off-by. That person owns the change.

Run one test on every line: can you name the control that enforces it? If not, cut the line or label it advice. Steps 2 to 4 build those controls, which is how AI coding agent guidelines stop being decoration.

2. Keep secrets out of the agent's reach

Push protection catches a secret when it is pushed, but by then the agent has already read it. So block the read first. In Claude Code, commit deny rules to .claude/settings.json and every teammate gets them. Other agents have their own equivalent, so check yours.

{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)"
    ]
  }
}

Then turn on secret scanning and push protection for the repository:

gh api --method PATCH repos/<org>/<repo> --input - <<'EOF'
{
  "security_and_analysis": {
    "secret_scanning": { "status": "enabled" },
    "secret_scanning_push_protection": { "status": "enabled" }
  }
}
EOF

Per the push protection docs, a blocked push explains why and stays blocked until the secret is removed. By default anyone with write access can bypass it by giving a reason, so look at delegated bypass if that is too loose. Push protection for repositories requires GitHub Secret Protection.

3. Enforce review with CODEOWNERS and branch protection

CODEOWNERS maps paths to the people who must approve changes there. Save it as .github/CODEOWNERS on your default branch. The last matching pattern wins, so general rules go first.

# .github/CODEOWNERS (the last matching pattern wins)
*                     @<org>/engineering
/infra/               @<org>/platform
/auth/                @<org>/security
/migrations/          @<org>/data-leads
/.github/             @<org>/platform
/.github/CODEOWNERS   @<org>/eng-leads
/.claude/             @<org>/eng-leads
/AGENTS.md            @<org>/eng-leads

Look at the last three lines. An agent should not be able to loosen its own rules, or the ownership file, without a human lead signing off.

Now the AI code review policy, as one call to the branch protection API:

gh api --method PUT repos/<org>/<repo>/branches/main/protection --input - <<'EOF'
{
  "required_status_checks": {
    "strict": true,
    "contexts": ["<your-existing-ci-check>", "agent-attribution"]
  },
  "enforce_admins": true,
  "required_pull_request_reviews": {
    "dismiss_stale_reviews": true,
    "require_code_owner_reviews": true,
    "required_approving_review_count": 1,
    "require_last_push_approval": true
  },
  "restrictions": null
}
EOF

require_code_owner_reviews makes the owners' approval mandatory on their paths. require_last_push_approval means whoever pushed the latest commit cannot be the one who approves it, which ends "I prompted it, so I approved it." enforce_admins removes the admin exemption, and strict forces branches to be up to date before merging.

A PUT replaces the whole rule, so if the branch is already protected, GET repos/<org>/<repo>/branches/main/protection first and merge your existing settings in.

4. Check commit trailers in CI

Policy line 4 is the one GitHub cannot enforce alone. This workflow fails any pull request where a commit has an agent's Co-authored-by trailer but no human Signed-off-by, which is what git commit -s adds.

name: agent-attribution
on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  agent-attribution:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ github.event.pull_request.head.sha }}
      - name: Agent commits need a human sign-off
        env:
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
          HEAD_SHA: ${{ github.event.pull_request.head.sha }}
          AGENT_RE: '^co-authored-by: .*(claude|copilot|cursor|codex|gemini|aider)'
        run: |
          fail=0
          for sha in $(git rev-list --no-merges "$BASE_SHA..$HEAD_SHA"); do
            trailers=$(git log -1 --format=%B "$sha" | git interpret-trailers --parse)
            if grep -qiE "$AGENT_RE" <<< "$trailers" && ! grep -qi '^signed-off-by: ' <<< "$trailers"; then
              echo "::error::${sha:0:7} has an agent trailer but no Signed-off-by. Run: git rebase --signoff origin/main"
              fail=1
            fi
          done
          exit $fail

Edit AGENT_RE to match the tools your team actually uses. Commit the workflow first and let it run once, then add agent-attribution to the required checks in step 3. A required check that has never reported leaves every pull request waiting.

Gotchas

  • The check proves consistency, not authorship. An agent commit with its trailer stripped passes. It catches honest mistakes, and hiding attribution is a conduct problem for the policy owner. Claude Code's attribution.commit setting can change or hide the trailer, and managed settings outrank a developer's own files, so set it there if you need it fixed.
  • Sign off before you ask for review. With dismiss_stale_reviews on, pushing the rebased commits clears existing approvals. Use git push --force-with-lease after the rebase.
  • CODEOWNERS fails quietly. Teams need explicit write access, and a nonexistent team gets no review request at all. The file must also exist on the pull request's base branch.
  • The API has a deprecation. The REST docs mark contexts as deprecated in favor of checks, though their own example still uses contexts. Check the page if the call starts complaining.

Agents will write more of your code every quarter, and when something bad ships, you want the answer to "who approved this?" in the git history instead of in someone's memory.

← More in Leadership