Guide

Use repo-local identity policy with .gitrole

Use .gitrole to declare the preferred Git identity role for a repository and the small set of roles that are allowed there.

A shared repository doesn't say which Git identity belongs there, so a personal role can look fine until the history is full of the wrong author. A .gitrole file in the repo root names the role you normally want and the short list of roles that are still allowed. It doesn't switch you, install a hook, or block a commit. If you haven't saved a role yet, start with Use the right Git identity for this repo.

Pin a repo to one role

When one saved role is the one this repo should use, pin it:

gitrole pin company-main

That writes .gitrole and prints:

pinned role company-main
  file  .gitrole
  default company-main
  allowed company-main

The file on disk is:

{
  "version": 1,
  "defaultRole": "company-main",
  "allowedRoles": [
    "company-main"
  ]
}

Check it:

gitrole resolve
company-main

If resolve prints that name, you can stop. defaultRole has to be in allowedRoles. Pin puts the same role in both, so you don't have a preferred role that the file then rejects.

If .gitrole already exists, pin exits 1 and leaves the file alone. A second run can't merge or widen the list by accident:

error: .gitrole already exists in this repo; gitrole pin will not overwrite or merge existing repo policy

How to read status

When .gitrole exists, gitrole status and gitrole doctor add the policy on top of the commit, remote, and SSH checks. Allowed-but-not-default stays aligned. The role is on the list, so it isn't a warning, and the default is still visible so you can see it isn't the preferred one.

This is gitrole status when the file prefers company-main, also allows maintainer-personal, and the global identity matches maintainer-personal:

maintainer-personal  aligned
  commit Maintainer Name <maintainer@personal.example>
  push  maintainer via github.com-personal
  scope global
  policy allowed role maintainer-personal (default: company-main)

The same repo on one line is policy=ok and overall=aligned. policy=ok on gitrole status --short means the effective role is defaultRole or is listed in allowedRoles. It does not mean the role is the default:

role=maintainer-personal scope=global override=false commit=ok remote=ok auth=ok policy=ok overall=aligned

gitrole doctor uses a different word for that case. The policy check is info, not warn, because info doesn't select overall=warning:

  info policy effective role maintainer-personal is allowed here, but repo defaultRole is company-main

On status --short, policy=warn and exit 2 only when the effective role is outside allowedRoles. policy=na means there is no .gitrole file. A missing file doesn't make status or doctor fail. resolve does fail when the file is absent, because that command's only job is to read it.

Surprises

defaultRole and every allowedRoles entry use the same role-name rules as a saved role: lowercase letters, numbers, -, and _. company-main and agent_bot are valid. client acme, Work, Client, and no-role are not. no-role is reserved because status --short writes it when no saved role matches.

An invalid name doesn't warn and continue. These commands exit 1, write the error to stderr, and print nothing on stdout, including gitrole status --short. The line isn't written until the policy loads, so a bad name can't show up as a role= value:

  • gitrole resolve
  • gitrole resolve --json
  • gitrole status
  • gitrole status --short
  • gitrole doctor
  • gitrole doctor --json

What it doesn't do

The file doesn't switch roles, install a hook, or block git commit. It tells check commit, status, and doctor whether the current role is the one this repo asked for. If an agent should read that before it commits, continue with Use gitrole as an identity preflight for agents and automation.

File format

Edit the file when more than one role is valid. This one prefers company-main and also allows maintainer-personal. gitrole resolve --json prints the same object:

{
  "version": 1,
  "defaultRole": "company-main",
  "allowedRoles": [
    "company-main",
    "maintainer-personal"
  ]
}
FieldMeaning
versionSchema version. Currently 1.
defaultRoleThe role that normally belongs in this repo. It also has to appear in allowedRoles.
allowedRolesRoles that are still valid here.

On gitrole doctor --json, that evaluation is repoPolicy.status: default, allowed, or notAllowed. default and allowed are policy=ok on status --short. notAllowed is policy=warn. The exit codes and the exact failure text are in Machine Readable Contracts.

Invalid names

A defaultRole of client acme looks like this on stderr:

error: repo policy file .gitrole is invalid: defaultRole invalid role name "client acme"; use lowercase letters, numbers, "-" or "_"

An invalid allowed role is named on that field. With defaultRole set to work and Client in allowedRoles:

error: repo policy file .gitrole is invalid: allowedRoles invalid role name "Client"; use lowercase letters, numbers, "-" or "_"

A missing .gitrole file is not this failure. resolve still exits 1 when the file is absent. status and doctor keep working, with policy=na on status --short.