Reference
Machine Readable CLI Contracts
Public contract reference for gitrole machine readable CLI output, including status --short, doctor --json, and resolve --json for scripts and automation.
Scripts and agents break when they guess which field is the summary, or when they paraphrase overall as "ok". This page is the contract for structured diagnostic output and the local commit check’s exit codes. Field names, values, and exit codes below are the ones the CLI writes.
Quick start
For a commit or a push check, run:
gitrole status --short
Read overall by name. It is the eighth field. policy is the seventh, so a parser that still treats field seven as the summary is reading policy. overall=aligned exits 0. overall=warning exits 2 and still prints the line. Exit 1 is a failure: the error is on stderr and stdout is empty. If you only need to stop on a warning, you can stop here.
A local role, SSH auth matched, no .gitrole file:
role=work scope=local override=true commit=ok remote=ok auth=ok policy=na overall=aligned
result=$(gitrole status --short)
overall=$(echo "$result" | grep -o 'overall=[^ ]*' | cut -d= -f2)
if [ "$overall" != "aligned" ]; then
echo "repo is not aligned, stopping"
exit 1
fi
Shell prompts call gitrole status --short --offline instead, so they don't open SSH on every redraw. --offline does not emit auth=ok. Setup and the segment glyphs are in Show gitrole in your shell prompt.
gitrole check commit- Local-only saved identity and policy guard. Automate against its exit code; success is quiet.
gitrole status --short- One line. Order is
role scope override commit remote auth policy overall. gitrole doctor --json- Full diagnosis as JSON. HTTPS auth is
infoonly when a pin allows the role and that role has agithubUser. This does not verify HTTPS credentials. Otherwise it iswarn. gitrole resolve --json- The
.gitrolefile as JSON. - Role name format
- Saved role names, and the reserved
role=no-rolesentinel.
gitrole check commit
This command checks local commit identity and repository policy. It accepts no operands, --offline, or JSON mode. It does not resolve push destinations, invoke SSH, check authentication, or write roles, policy, or Git configuration. It can pass without a remote.
| Exit | Meaning | stdout | stderr |
|---|---|---|---|
0 |
Complete saved identity and applicable policy match | Empty | Empty |
2 |
Identity/policy mismatch or unsuitable repository context | Empty | Human-readable explanation |
1 |
Required read, parse, Git operation, or usage failure | Empty | Human-readable explanation |
The command and exit codes are the automation contract; diagnostic wording is not. A required read or parse failure takes precedence over an observed mismatch. No successful result means a commit occurred.
The complete effective author name and email must match the first saved role with that identity, in store order. The committer must equal that same complete identity. No pin is required, but no saved match is exit 2, including a missing or empty role store; the check never creates a store. A matching legacy reserved no-role profile is refused. Duplicate identities do not select a later profile to satisfy policy.
With a valid .gitrole, both default and allowed evaluations pass. notAllowed refuses. An allowed non-default role may pass without a saved default profile. Missing policy is optional; malformed role data anywhere in the store or malformed policy is exit 1. Existing tolerant parsing of legacy blank identities and filtered allowed-list elements remains unchanged; unrelated unusable identities do not veto a valid match. Unreadable files and dangling symlinks are failures, not absence; valid file symlinks may be read.
Mixed configured identity sources refuse. An unborn branch requires a local or worktree override; matching global-only identity can pass in an established repository. A valid unborn branch differs from damaged HEAD, index, config, or unexpected Git results, which fail with 1. Outside a worktree and bare repositories return 2. The check reads the invocation's effective identity, including Git-prepared author values in a hook; a standalone run cannot predict later author arguments.
The optional packaged pre-commit wrapper delegates to this command. It preserves 1/2, normalizes unexpected failures to 1, and appends a fixed bypass disclosure on failure. Missing or incompatible CLI and missing Node never count as a pass. The wrapper is quiet on success. See local hook setup and migration. Existing status fields/exits and the packaged agent skill's strict gate remain unchanged.
gitrole status --short
One line answers whether this repo is aligned to commit or push. --offline is the same line with the live SSH probe skipped.
Signature
gitrole status --short
gitrole status --short --offline
How to read the line
Exactly one line. Eight key=value fields, in this order, separated by single spaces. A pin is the repo's .gitrole file (what gitrole pin writes). It names defaultRole and allowedRoles. See Pin a repo to one role.
No .gitrole file, so policy=na, and SSH auth matched:
role=work scope=local override=true commit=ok remote=ok auth=ok policy=na overall=aligned
HTTPS-only push destination whose pin allows the effective role, and that role has a githubUser. auth=na and overall=aligned together. Exit 0. auth=na here means SSH verification doesn't apply, not that a probe passed:
role=work scope=local override=true commit=ok remote=ok auth=na policy=ok overall=aligned
HTTPS-only push destination with no pin. Since 0.8.0 this is auth=warn and overall=warning, exit 2. gitrole can't tell which GitHub user the push will use:
role=work scope=local override=true commit=ok remote=ok auth=warn policy=na overall=warning
HTTPS-only push destination whose pin doesn't allow the effective role. auth=warn and policy=warn. Exit 2:
role=personal scope=local override=true commit=ok remote=ok auth=warn policy=warn overall=warning
Effective role is outside allowedRoles. policy=warn and overall=warning. Exit 2:
role=client-acme scope=local override=true commit=ok remote=ok auth=ok policy=warn overall=warning
Default push observation
A literal . selected by branch pushRemote, remote.pushDefault, or branch remote is resolved by Git before it is classified as local. If insteadOf or pushInsteadOf rewrites it, status and doctor inspect the rewritten endpoint. An unchanged direct . keeps its local-destination behavior; a configured remote named . uses its configured push URLs.
remote and auth cover the destination of a plain git push, not the fetch origin. Selection follows branch pushRemote, remote.pushDefault, branch remote, sole remote, then origin. A selected value that is not a configured remote is a URL or path, including an scp-style SSH URL, an HTTPS URL, and a relative or absolute local path. Named remotes are resolved with git remote get-url --push --all. Other destinations are resolved by Git in an isolated temporary repository using only the push URL aliases from the original effective configuration. This preserves conditional include behavior without changing the observed repository's configuration. Legacy .git/remotes and .git/branches destinations remain explicitly unverified. insteadOf and pushInsteadOf apply, and the longest match wins. An explicit pushurl is not rewritten by pushInsteadOf; insteadOf still applies to it. A pushInsteadOf rule that matches only some fetch URLs leaves the others out of the push. Every resolved push URL is checked; an unverified endpoint warns. Explicit remote-helper destinations such as ext::command or helper::address are unsupported and warn online and offline; inspection does not execute their helpers. Mixed SSH/HTTPS warns online; offline retains HTTPS pin checks and invokes no SSH.
Online standard OpenSSH inspection includes URL user/port and receive-pack context. Custom Git SSH commands, alternate diagnostic binaries, incomplete configuration, context differences or interactive authentication remain unverified. Inspection may execute configured Match exec commands or DNS lookups. These checks do not prove branch/refspec readiness, remote permission or push success, and do not predict future explicit push arguments.
--offline
--offline skips the live SSH githubUser probe. Field names and order stay the same. auth=na on SSH means the probe was skipped. That na doesn't by itself set overall=warning. --offline does not emit auth=ok.
Local checks still run: commit identity, GIT_AUTHOR_* and GIT_COMMITTER_* overrides, a fresh repo with no local role, remote host compared with the saved githubHost, and .gitrole policy. HTTPS auth is the same with or without --offline, because it uses the repo pin and the saved role's githubUser, not the network.
SSH remote, probe skipped, local checks clean. Exit 0:
role=work scope=local override=true commit=ok remote=ok auth=na policy=na overall=aligned
A live gitrole status --short on that same SSH remote probes GitHub and can report auth=ok or auth=warn instead. The prompt helper doesn't do that. A line that still says auth=ok didn't come from --offline.
Fields
| Field | What it tells you | Values |
|---|---|---|
role | Saved role that matches the current commit identity | role name, or no-role |
scope | Underlying configured author scope, retained under environment overrides | global, local, system, worktree, command, git, mixed, unset |
override | Whether a repo-local Git config is active | true, false |
commit | Commit identity check | ok, warn, na |
remote | All default push destinations against role host expectations | ok, warn, na |
auth | Every supported SSH push account, or HTTPS-only pin checks; mixed online destinations warn | ok, warn, na |
policy | .gitrole against the effective role | ok, warn, na |
overall | Summary | aligned, warning |
| Field | na when |
|---|---|
remote | Not inside a Git repo |
auth | Not inside a Git repo, HTTPS whose pin allows the active role and that role has a githubUser, or --offline when the live SSH probe is skipped |
policy | No .gitrole file |
How na rolls into overall
na means that check doesn't apply. It doesn't by itself set overall=warning or exit 2. policy=na when there is no .gitrole file can sit next to overall=aligned when nothing is warn.
| What is true | overall | Exit |
|---|---|---|
commit, remote, auth, or policy is warn | warning | 2 |
| Working directory is outside a Git repo | warning | 2 |
Those checks are only ok or na, inside a Git repo | aligned | 0 |
Only warn on those checks, or being outside a Git repo, drives overall=warning. auth on SSH is ok or warn from the githubUser probe. --offline doesn't run that probe, so SSH auth is na. On HTTPS, auth=na only when a repo pin allows the active role and that role has a githubUser. No pin, or a pin that doesn't allow the active role, is auth=warn and exit 2.
policy=ok when .gitrole allows the effective role: that role is defaultRole, or it is listed in allowedRoles. policy=warn when the evaluation is notAllowed.
Outside a Git repo, with a global name and email that match no saved role, one run printed this line and exited 2. remote=na and auth=na because there is no work tree. commit=warn because nothing saved matches. Your role and commit follow the identity that is actually configured. overall is still warning outside a repo:
role=no-role scope=global override=false commit=warn remote=na auth=na policy=na overall=warning
Exit codes
| Code | Meaning |
|---|---|
0 | overall=aligned. The line was written to stdout. An HTTPS repo reaches this only when the pin allows the active role, that role has a githubUser, and no other field is warn. |
2 | overall=warning. The line was written to stdout. |
1 | Failure. Error on stderr. Stdout empty. No line. |
When it fails
Exit 1 writes the error to stderr and doesn't print the line. A missing .gitrole file still prints the line, with policy=na.
| Condition | Result |
|---|---|
| Saved role data contains a name outside the role name format | exit 1, empty stdout, stderr error: saved role data is invalid; fix or recreate the roles file |
Saved role is already named no-role | not this failure; the line is still printed. gitrole doctor warns and suggests a rename |
.gitrole is invalid JSON or fails schema validation | exit 1, stderr, empty stdout |
Invalid .gitrole defaultRole or allowedRoles name | exit 1, empty stdout. Policy is loaded before the line is written. See Role name format. |
| Another operational failure before the line is written | exit 1, stderr, empty stdout |
What's stable
Field names, this order, and the value vocabularies above are the contract:
role scope override commit remote auth policy overall
policy is the seventh field. overall is the eighth. Read them by name. Since 0.7.6 the line has eight fields. A parser that treated the seventh field as overall is reading policy. Changing a name, the order, or a vocabulary is a breaking change.
gitrole doctor --json
Use this when the one-line check isn't enough and you need commit identity, repo context, SSH auth, and .gitrole policy as JSON. Don't parse checks[].message. Read overall, commitIdentity, and checks[].status.
Signature
gitrole doctor --json
Example
This illustrative subset shows the single-endpoint JSON shape; additive push/fetch metadata is described below. The saved role work matches the repo-local name and email, origin is git@github.com-work:acme/service.git, the SSH probe returned acme-dev, and there is no .gitrole file, so repoPolicy is omitted. repository.topLevelPath is that repo's absolute path. Yours will differ. Exit 0.
{
"role": {
"name": "work",
"fullName": "Alex Developer",
"email": "alex@work.example",
"sshKeyPath": "~/.ssh/id_work",
"githubUser": "acme-dev",
"githubHost": "github.com-work"
},
"overall": "aligned",
"commitIdentity": {
"fullName": {
"value": "Alex Developer",
"source": "local"
},
"email": {
"value": "alex@work.example",
"source": "local"
}
},
"configuredIdentity": {
"local": {
"fullName": "Alex Developer",
"email": "alex@work.example"
},
"global": {
"fullName": "Example Global Identity",
"email": "global@example.test"
}
},
"scope": {
"effective": "local",
"hasLocalOverride": true
},
"repository": {
"isInsideWorkTree": true,
"hasCommits": true,
"topLevelPath": "/tmp/gitrole-doc-sample/service",
"currentBranch": "main",
"remote": {
"name": "origin",
"url": "git@github.com-work:acme/service.git",
"protocol": "ssh",
"host": "github.com-work",
"owner": "acme",
"repository": "service"
}
},
"sshAuth": {
"ok": true,
"host": "github.com-work",
"githubUser": "acme-dev"
},
"checks": [
{
"status": "ok",
"label": "role",
"message": "commit identity matches saved role work"
},
{
"status": "info",
"label": "remote",
"message": "default push remote origin uses ssh at git@github.com-work:acme/service.git"
},
{
"status": "ok",
"label": "scope",
"message": "selected role work is applied via local config"
},
{
"status": "ok",
"label": "commit",
"message": "effective commit identity matches selected role work"
},
{
"status": "ok",
"label": "host",
"message": "remote host matches role githubHost github.com-work"
},
{
"status": "ok",
"label": "auth",
"message": "SSH auth matches role githubUser acme-dev"
}
]
}
HTTPS auth is info, with message push destination uses HTTPS; SSH auth verification does not apply, only when a repo pin allows the active role and that role has a githubUser. No pin, or a pin that doesn't allow the active role, is warn and exit 2. sshAuth is omitted when no SSH probe runs.
Offline diagnosis
gitrole doctor --offline --json
gitrole doctor --json --offline
Both flag orders return the same DoctorResult shape. Offline mode adds no keys and invokes no SSH commands, including configuration inspection (ssh -G) or an authentication test. Top-level and per-target sshAuth are omitted. A saved role.githubUser is still an expectation, never an observed account.
For each SSH target, the authentication check is info with a message that authentication was skipped. Skipping alone causes no warning. Exit 0 and overall=aligned mean only that local checks have no warnings; neither verifies authentication, repository permission or a future push. A local warn still selects overall=warning and exit 2; usage and operational errors remain exit 1.
Role and effective author/committer checks, fresh-repository warnings, policy, default push resolution and every target's host checks remain active. HTTPS pin checks also remain active for pure and mixed destinations: an allowing repo pin plus a saved githubUser yields info; absent or mismatched pins warn. Offline mode omits the online mixed-authentication blanket warning. Unsupported transports remain local destination warnings, not failed SSH observations.
No authentication history is read or saved. Existing role-store behavior remains: an absent roles file may be initialized. Default online doctor, status, prompt and auth test are unchanged; online SSH inspection may run configured commands, use the network or change SSH state.
Fields
Top-level fields
| Field | What it tells you |
|---|---|
role | Saved role that matches the current commit identity. Omitted if no role matches. |
overall | aligned or warning |
commitIdentity | Effective name and email, plus where each comes from: local, global, system, worktree, command, git, env, or unset |
configuredIdentity | Raw local and global Git config values. This is not the commit identity when an env var overrides it. |
scope | Aggregate view of underlying configured author scope |
repository | Repo context, branch, and parsed remote info |
sshAuth | SSH probe result. Omitted if no SSH probe was run, including HTTPS-only push destinations. |
repoPolicy | .gitrole policy evaluation. Omitted if no policy file exists. |
checks | Ordered list of individual check results |
commitIdentity
| Field | Meaning | Values |
|---|---|---|
fullName.value | Effective commit author name | string, or omitted when unset |
fullName.source | Where the effective name came from | local, global, system, worktree, command, git, env, unset |
email.value | Effective commit author email | string, or omitted when unset |
email.source | Where the effective email came from | local, global, system, worktree, command, git, env, unset |
configuredIdentity
| Field | Meaning |
|---|---|
configuredIdentity.local.fullName | Raw repo-local user.name, if present |
configuredIdentity.local.email | Raw repo-local user.email, if present |
configuredIdentity.global.fullName | Raw global user.name, if present |
configuredIdentity.global.email | Raw global user.email, if present |
scope
| Field | Meaning | Values |
|---|---|---|
effective | Underlying configured author scope, including when effective values come from the environment | local, global, system, worktree, command, git, mixed, unset |
hasLocalOverride | Whether underlying author configuration includes a local or worktree field | true, false |
repository
| Field | Meaning |
|---|---|
isInsideWorkTree | Whether the current working directory is inside a Git work tree |
hasCommits | Whether HEAD exists. Omitted outside a Git repo. |
topLevelPath | Absolute path to the repo root. Omitted outside a Git repo. |
currentBranch | Current branch name, when available |
upstreamBranch | Configured upstream branch, when available |
fetchRemote | Parsed fetch origin, independent of push qualification. |
push | Selected remoteName, optional resolution message and all targets. Each target contains parsed remote, optional sshAuth and unverified message. Singular top-level sshAuth is populated only for one endpoint. |
remote | First effective default push endpoint. Omitted when no destination can be resolved. |
repository.remote
| Field | Meaning | Values |
|---|---|---|
name | Remote name | selected default push remote |
url | Raw remote URL | string |
protocol | Parsed remote protocol | ssh, https, unknown |
host | Parsed remote host | string when parseable |
user | Explicit SSH URL user, when present | string |
port | Explicit supported SSH URL port, when present | integer 1–65535 |
path | SSH repository path as interpreted by Git | string |
owner | Parsed repository owner or org | string when parseable |
repository | Parsed repository name | string when parseable |
sshAuth
| Field | Meaning |
|---|---|
ok | Whether the SSH probe succeeded |
host | SSH host alias or hostname that was probed |
githubUser | GitHub user resolved from the SSH probe, when available |
message | Probe detail when no GitHub user could be resolved |
repoPolicy
| Field | Meaning | Values |
|---|---|---|
version | Policy schema version | currently 1 |
defaultRole | Preferred role for this repo | role name |
allowedRoles | Roles allowed by .gitrole | array of role names |
effectiveRole | Active matched role used for evaluation | role name, or omitted |
status | Policy evaluation result | default, allowed, notAllowed |
status default and allowed are policy=ok on status --short. notAllowed is policy=warn.
checks
Each entry has status, label, and message. info doesn't set overall to warning and doesn't cause exit 2. A warn check does.
| Field | Meaning | Values |
|---|---|---|
status | Per-check result | ok, warn, info |
label | Diagnostic category string | short string such as role, remote, or auth. Not a closed vocabulary. |
message | Human-readable explanation | string. Don't parse this. |
Doctor may append at most one {status: "info", label: "hook", message: "..."} entry for a legacy pre-commit hook or an unknown inspection result. It reads Git's effective hook path without executing or modifying the hook. This uses the existing check shape and status vocabulary, adds no top-level field, and does not change overall or exits. Exact legacy bytes, inactive files, symlink targets, and uncertain text matches receive explanatory wording; do not parse that wording. No finding is not proof of protection or successful migration. See inspection limits and manual review.
When a pin allows the active role and that role has a githubUser, the HTTPS auth entry is info:
{
"status": "info",
"label": "auth",
"message": "push destination uses HTTPS; SSH auth verification does not apply"
}
No pin is warn. With no githubUser on the active role the message is push destination uses HTTPS and no identity pin is configured:
{
"status": "warn",
"label": "auth",
"message": "push destination uses HTTPS and no identity pin is configured"
}
With a githubUser but no allowing .gitrole, the message is push destination uses HTTPS and no repo pin is configured.
A pin that doesn't allow the active role is warn. When both GitHub users are known and differ:
{
"status": "warn",
"label": "auth",
"message": "push destination uses HTTPS; github user thisyearearth does not match pin alex-dev"
}
Otherwise the message is push destination uses HTTPS; active identity does not match pinned role <defaultRole>.
Exit 0 with overall aligned on HTTPS requires the pin to allow the active role, that role to have a githubUser, and no other warn check.
Exit codes
| Code | Meaning |
|---|---|
0 | Diagnosis complete, no warn check. JSON on stdout. |
2 | Diagnosis complete, at least one warn check. JSON on stdout. |
1 | Failure. Error on stderr. No JSON. |
An HTTPS auth check selects exit 2 when it is warn (no pin, or a pin that doesn't allow the active role). It stays info, and doesn't by itself select exit 2, only when the pin allows the active role and that role has a githubUser.
When it fails
Exit 1 writes the error to stderr and doesn't print JSON. A missing .gitrole file still returns JSON. repoPolicy is omitted.
| Condition | Result |
|---|---|
| Saved role data contains a name outside the role name format | exit 1, stderr, no JSON |
Saved role is already named no-role | not this failure. JSON is printed, overall is warning, and a warn check suggests a rename |
.gitrole is invalid JSON or fails schema validation | exit 1, stderr, no JSON |
Invalid .gitrole defaultRole or allowedRoles name | exit 1, stderr, no JSON. See Role name format. |
| Another operational failure before JSON is written | exit 1, stderr, no JSON |
What's stable
The top-level field names are the contract. The meaning of overall, scope, the presence of checks, and the checks[].status vocabulary (ok, warn, info) are stable. Key order is not. checks[].message is descriptive text, not an automation surface. Adding new fields is not a breaking change. Removing or renaming documented top-level fields is.
| Surface | Safe to automate against |
|---|---|
overall | yes |
commitIdentity | yes |
configuredIdentity | yes |
scope | yes |
repository | yes, but prefer presence and absence and documented fields over incidental details |
sshAuth | yes |
repoPolicy | yes |
checks | yes, as an ordered list of results |
| Surface | Guidance |
|---|---|
checks[].message | human-readable text. Don't parse this. |
checks[].label | diagnostic category string. Useful for display, not a closed vocabulary. |
repository.currentBranch | useful context, not the primary contract surface |
repository.topLevelPath | useful context, not the primary contract surface |
Nested fields above are documented for meaning and current shape. Additive changes may happen over time.
gitrole resolve --json
Use this when you need the .gitrole policy and you don't need a diagnosis. status and doctor still run when the file is absent. This command doesn't.
Signature
gitrole resolve --json
Example
gitrole resolve --json with defaultRole work and allowedRoles work and maintainer-personal. Exit 0:
{
"version": 1,
"defaultRole": "work",
"allowedRoles": [
"work",
"maintainer-personal"
]
}
Fields
| Field | What it tells you |
|---|---|
version | Policy schema version. Currently always 1. |
defaultRole | The preferred role for this repo |
allowedRoles | Roles that are valid here. defaultRole is always included. |
Exit codes
| Code | Meaning |
|---|---|
0 | Policy resolved. JSON on stdout. |
1 | Failure. Error on stderr. No JSON. |
When it fails
resolve --json doesn't emit empty success output when .gitrole is missing. It exits 1, writes the error to stderr, and prints no JSON. That is different from status and doctor, which still run without a policy file.
| Condition | Result |
|---|---|
| Not inside a Git repo | exit 1, stderr message, no JSON |
No .gitrole file exists | exit 1, stderr message, no JSON |
.gitrole is invalid JSON or fails schema validation | exit 1, stderr message, no JSON |
invalid defaultRole or allowedRoles name | exit 1, stderr message, no JSON |
What's stable
The field names version, defaultRole, and allowedRoles are the contract. version is 1. Key order is not.
Role name format
Saved role names use this format so machine-readable values such as role= stay unambiguous. The same rules apply to .gitrole defaultRole and every allowedRoles entry.
| Rule | Allowed |
|---|---|
| letters | lowercase a-z only |
| digits | 0-9 |
| separators | -, _ |
| disallowed | spaces, slashes, uppercase, and other punctuation |
| Example | Valid |
|---|---|
work | yes |
personal | yes |
client-acme | yes |
agent_bot | yes |
client acme | no |
Work | no |
my@role | no |
no-role | no. Reserved sentinel. |
Creating a role with an invalid name prints:
error: invalid role name "client acme"; use lowercase letters, numbers, "-" or "_"
no-role matches the shape above, and it is still reserved. status --short writes role=no-role when no saved role matches. gitrole add no-role and gitrole import current --name no-role exit 1, write this to stderr, and print nothing on stdout:
error: role name "no-role" is reserved for the status and prompt sentinel when no saved role matches; choose a different name
A saved role that already uses that name isn't deleted or renamed. gitrole doctor warns and suggests adding the identity under a new name, then gitrole remove no-role. gitrole status still prints its line. The prompt segment still chooses ✓ or ⚠ from the status fields only, so a hand-built aligned line with role=no-role stays gitrole:no-role ✓.
If saved role data already contains a name outside this format, commands that load saved roles fail closed: exit 1, error on stderr, nothing on stdout. That includes gitrole status --short and gitrole doctor --json. The reserved name no-role is not that failure.
An invalid policy name is fail-closed for the same reason: a bad name would otherwise show up as a role= value scripts can't trust. These commands exit 1, write the error to stderr, and write nothing to stdout:
gitrole resolvegitrole resolve --jsongitrole statusgitrole status --shortgitrole doctorgitrole doctor --json
gitrole status --short loads the policy before the one-line contract is written. The field names and order for a valid policy are unchanged.
A defaultRole of client acme produces this stderr line and no stdout:
error: repo policy file .gitrole is invalid: defaultRole invalid role name "client acme"; use lowercase letters, numbers, "-" or "_"
An invalid allowed role is reported 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 for status or doctor. Those commands still run without repo policy when the file is absent. resolve and resolve --json still exit 1 when the file is missing. Valid names such as client-acme and agent_bot still succeed.
What this page doesn't cover
These commands check. They don't switch roles, rewrite remotes, or install hooks. Human-readable gitrole status and gitrole doctor text isn't a parse contract. The prompt segment glyphs are in Show gitrole in your shell prompt.
Effective ordinary-commit identity
Gitrole asks Git for GIT_AUTHOR_IDENT and GIT_COMMITTER_IDENT. Included configuration, author/committer-specific settings, worktree/system/command configuration and environment overrides participate in Git’s precedence. Role matching, current and import current use the effective author. A differing or unavailable committer warns rather than proving alignment. doctor --json additionally reports committerIdentity with the same name/email value/source shape as commitIdentity.
Config includes retain their reported Git scope. git identifies Git-generated fallback values; env identifies environment input. Aggregate scope reports the underlying author configuration; environment overrides retain that convention. hasLocalOverride includes local and worktree configuration. The source and scope vocabularies are expanded: strict consumers accepting only the older values must update. The eight short fields, their order and exits remain unchanged.
This is a snapshot for an ordinary commit in the checked context. A later git commit --author argument or changed config/environment can change identity after the check; Gitrole cannot predict it.
SSH diagnostic message strings are explanatory prose, not reason codes. Human output may summarize additional differences and point to gitrole doctor --json, whose existing message fields retain the full explanation. The human expected label identifies the saved account; JSON keys and structural values are unchanged. Short status retains the same eight fields and exit codes.