CodeCrew CLI reference¶
Every gh codecrew verb: what it takes, what it reads, what it writes, what
it prints, what it refuses, and what it exits with. The protocol these verbs
implement is SPEC.md; the refusal-code catalogue is
SPEC §10, which every code below links to.
Every verb is safe to run by any role from any repo in the project: the pointer file resolves the hub.
- Invocation
- Exit status
- Output channels
- Common refusals
- Verbs:
init·migrate·status·milestone new·milestone close·milestone strike·milestone evidence·task new·task start·task finish·checkpoint·role·roles diff·roles show·roles sync·identity new·identity webhook·identity token·version
Invocation¶
Installed as a gh extension the command is gh codecrew; the binary built
from source takes the same arguments without the gh prefix. gh 2.50.0 or
later must be on PATH.
--help or -h anywhere in the arguments prints the verb list on stderr and
exits 0, before the verb runs. gh codecrew help prints the same list on
stdout. --version and -v are version. An unknown verb or subcommand
prints the verb list on stderr and exits 1.
An issue reference — the <ref> argument of task start, task finish and
checkpoint — is N, #N or owner/repo#N; a bare number resolves against
the repository the verb runs in. A milestone number is <n>, and
milestone close also accepts M<n>. Where a verb takes options as well as
a leading argument, the argument may be given before or after them;
roles diff and roles show want the role name first, roles sync takes
its role names and --dry-run in any order, and task start and
milestone evidence take their argument and nothing else.
Exit status¶
0 when the verb did what it was asked, 1 on every failure — a refused
gate, an unusable flag, an unreachable GitHub, a gh that failed. There is
no exit-code taxonomy: 1 says only "this did not happen". --dry-run
follows the same rule and exits 1 when it reports a gate that would refuse.
Output channels¶
stdout carries everything a verb prints as it works: what a caller
consumes — a minted token, role <name>'s identity — and the report or
receipt, with the note: and warning: lines that qualify it. A note: is
advisory and never a failure: it stands beside a verb that goes on to
succeed. Each verb's own section says which lines it writes.
stderr carries the rest. The refusal line, once, as the verb exits 1:
The code is a fixed vocabulary, catalogued in SPEC §10;
the detail is prose for a human and may be reworded in any release. A failure
that is not a gate — an unknown verb or subcommand, a missing argument, a
flag the verb does not define — ends on the same codecrew: prefix with no
refused[...], and exits 1 too: after the verb list for a verb or
subcommand it does not know, and after the flag set's own diagnostic and
usage for most verbs' bad flags (identity webhook prints that diagnostic
and usage on stdout; only its last line is on stderr). --help and -h
print the verb list on stderr and exit 0. The note: lines raised while
the pointer is read — a pointer, or a hub's pointer, carrying no codecrew:
version, and a gh whose version could not be read — which precede anything
the verb itself prints. And identity token's receipt, with its note that an
--installation hint is stale, which are on stderr so that stdout carries
the token alone.
Under --dry-run the gate lines and the plan are stdout; a dry run that ends
in a refusal prints that one line on stderr like any other.
Common refusals¶
Every verb that loads and validates the working repo's .codecrew/config.yml
can raise these, before its own work begins. The verbs that neither require
nor validate a pointer, and so raise none of these, are version, help,
init, migrate, identity token and identity webhook; init and
migrate raise the layout codes themselves, and identity token reads a
pointer only when one is there, to prefer the hub's owner.
| Code | Condition |
|---|---|
LAYOUT_LEGACY |
The repo is still on the protocol 1.x layout: a root .codecrew.yml, or a root roles/ holding one of the five contracts, with no .codecrew/config.yml. The detail names what was found and gh codecrew migrate. |
PROTOCOL_MISMATCH |
The pointer's protocol major differs from the one this binary implements — the local pointer, or the hub's on a spoke's fetch. |
IDENTITY_UNTYPED |
A routing row's identity carries no type prefix; the detail names the row and the four forms. |
SPOKE_ROUTING |
A spoke's pointer carries a roles: block; the hub carries the one routing table. |
HUB_UNREADABLE |
In a spoke: the hub's pointer could not be fetched or parsed, so no role resolves. Routing fails closed. |
GH_TOO_OLD |
The installed gh is below 2.50.0, checked once, up front. |
GH_UNREACHABLE |
gh never reached GitHub — no route, no DNS, no credentials. Never folded into another condition. |
A hub reads its own pointer from disk and resolves roles with no network.
version, help, and roles show / roles diff / roles sync in a hub
need no network at all.
gh codecrew init¶
Scaffolds a repository into a CodeCrew project and creates the protocol's
labels. Hub mode — the default — writes the pointer with the full ~-routed
roles table, a ROADMAP.md seed, the role contracts embedded in the
installed release under .codecrew/roles/, and a blank
.codecrew/roles/<role>.local.md extension beside each. Spoke mode writes
the pointer alone of those. Both modes write the entry point,
.codecrew/AGENTS.md, and the two root files that reach it: an AGENTS.md
naming the path and importing it, and a CLAUDE.md importing AGENTS.md.
Existing files are kept and reported. It then commits exactly the files it
wrote and ensures the three cc: labels exist. Rerunning it writes no file
it already wrote, commits nothing when it wrote nothing, and creates no label
it already created.
Options
| Option | Argument | Default | Effect |
|---|---|---|---|
--hub |
owner/repo, or self |
self |
Spoke mode: the pointer names that hub and nothing else is scaffolded. self is hub mode. |
Reads. The working directory, which must be the repository root; the
repository's default branch and its pull-request requirement, through gh;
the repository's existing labels.
Writes. .codecrew/config.yml, .codecrew/AGENTS.md, AGENTS.md,
CLAUDE.md, and in hub mode ROADMAP.md, .codecrew/roles/<role>.md and
.codecrew/roles/<role>.local.md — each only when it is absent. One commit
of exactly those paths, on the current branch, or on codecrew-bootstrap cut
from the default branch when the default branch requires pull requests; it
never pushes. The commit is housekeeping, and its body names the tool that
defined it, as SPEC §4 requires — the invocation
(--hub only in spoke mode), the release gh codecrew version prints
(dev on a source build), the protocol the binary implements, each file
written, and the trailer. No line passes 100 characters: the tool line
wraps at a space, and an owner/repo too long for a line of its own —
GitHub allows 140 characters — is split just after the slash, the
repository name continuing on the next line:
chore: scaffold codecrew
Scaffolded by gh codecrew init --hub myorg/platform (gh-codecrew v2.1.1, protocol 2.1):
.codecrew/AGENTS.md
.codecrew/config.yml
AGENTS.md
CLAUDE.md
Housekeeping (SPEC §4); no task.
Past 20 files the list is a count. Outside a git repository nothing is
committed: init prints the git init, git commit and gh repo create to
run, the commit carrying the same body as a second, single-quoted -m; so
does every note: that leaves a commit to make by hand. In GitHub: the missing cc:task, cc:milestone and
cc:needs-decision labels, with the protocol's colour and description; an
existing label is left exactly as it is. Everything it prints as it works is
on stdout: a line per file written or kept, the commit, a line per label,
each note:, the next: lines, and last an action needed block naming any
kept root entry point that does not reach .codecrew/AGENTS.md, with the
lines to add to it. It reads no pointer, so no pointer note reaches stderr; a
refusal, or a bad flag's diagnostic and usage, does
(Output channels).
Before the first task. The next: lines say where the project goes from
here — in hub mode, that every seat is routed to the operator and the
milestone new to run; in spoke mode, the hub its tasks attach to — and, in
both modes, the one requirement nothing init writes can meet: task finish
requires at least one check reporting on every pull request and refuses
NO_CHECKS without one
(SPEC §8: the gate cannot be satisfied
by absence), so a repository without a workflow on pull_request adds one
before its first task. init writes no workflow and reads none, so the lines
are printed whatever the repository holds; task new
and task start say it again, as a note:, while
the repository still has none.
Refusals. LAYOUT_LEGACY — the repo is on the 1.x
layout; gh codecrew migrate moves it. A directory that is not the
repository root, and a scaffold that cannot be written, exit 1 without a
code. A label step that cannot reach GitHub is a note:, not a refusal, and
the scaffold stands; so is a directory that is not yet a git repository, and
the labels arrive on the rerun once it is one.
Exit status. 0 when the scaffold is complete, including a rerun that writes nothing; 1 on a refusal or an unusable argument.
gh codecrew init # this repo is the hub
gh codecrew init --hub myorg/platform # a spoke pointing at that hub
gh codecrew migrate¶
Moves a repository from the protocol 1.x layout to 2.0. git mv takes
.codecrew.yml to .codecrew/config.yml and, out of a root roles/, the
five role contracts and their <role>.local.md extensions into
.codecrew/roles/, removing the emptied directory. The pointer is then
rewritten in place: codecrew: "2.1", the protocol the binary implements; a
coordinator row when the table declares none; and every identity typed by
asking GitHub what each bare 1.0 login is. Comments, blank lines and key
order are kept. It writes .codecrew/AGENTS.md when the repo has none, and
does for the root AGENTS.md and CLAUDE.md what init does. Every
refusal is raised before anything is written. A repo already on the 2.0
layout says so, moves nothing and commits nothing — a pointer naming an
earlier minor, such as "2.0", is current and left as it is (SPEC §5) —
and the label step still runs.
Options
| Option | Argument | Default | Effect |
|---|---|---|---|
--dry-run |
none | off | Prints every file step and every label creation and restyle; writes nothing. |
Reads. The working directory, which must be the repository root of a git
repository; .codecrew.yml; a root roles/, and only when it already holds
one of the ten CodeCrew names; GitHub, for the account behind each bare 1.0
identity (users/<login>, then users/<login>[bot]).
Writes. The moved and rewritten files above, in one commit of exactly
those paths on the current branch; it never pushes. The commit's subject is
chore: migrate codecrew to the 2.0 layout, and its body, in the shape
init writes (SPEC §4), names the tool and lists
each move and each file written:
Migrated by gh codecrew migrate (gh-codecrew v2.1.1, protocol 2.1):
.codecrew.yml -> .codecrew/config.yml, rewritten
roles/qa.md -> .codecrew/roles/qa.md
.codecrew/AGENTS.md
Housekeeping (SPEC §4); no task.
With a detached HEAD the files move and a note: prints that commit,
body included, to make by hand. In GitHub: the missing
cc: labels created, and the existing ones restyled to the protocol's colour
and description, each reported; every other label untouched. Everything it
prints as it works is on stdout: a line per step, each note:, and last an
action needed block for a kept root entry point that does not reach the
instructions. It reads no pointer, so no pointer note reaches stderr; a
refusal, or a bad flag's diagnostic and usage, does
(Output channels).
--dry-run. The same steps in the same order, the same refusals, and the
same label lines; nothing written.
Refusals. BOTH_LAYOUTS — the 1.x and 2.0 layouts
overlap. FOREIGN_ROLES_DIR — a CodeCrew-owned root
roles/ holds an entry outside the ten names.
MIGRATION_UNSUPPORTED — the pointer's protocol major
is not 1. SPOKE_ROUTING — a 1.x spoke's pointer
carries a roles: block. IDENTITY_UNRESOLVED — a
bare identity answers to nothing, to both a user and an App, or to an
organization. GH_UNREACHABLE — the lookup could not
reach GitHub at all. A directory that is not a git repository, or not its
root, exits 1 without a code. A label step that cannot reach GitHub is a
note:; the rerun completes it.
Exit status. 0 when the repo is on the 2.0 layout, including a rerun that
moves nothing; 1 on a refusal. --dry-run exits with the first refusal it
reports.
gh codecrew migrate --dry-run # every step, nothing written
gh codecrew migrate # the move, in one local commit
gh codecrew status¶
Reports where the project is: the open milestones and their tasks, each
task's inferred state, and every raised gate — on tasks and on milestone
issues alike, the latter marked (milestone). A task in progress or in
review names its holder, taken from the login in its latest **Started by**
record and falling back to the first assignee only when nothing records a
start. It then reports the stale task branches of the repository it runs in —
every task/<n>-… branch on the remote whose task issue is closed — with the
delete-or-keep verdict the next milestone close would give it. With no open
milestone it says so in place of the board and the gates. Last, in a hub,
a line for every role contract that is not the embedded one: contract
missing: for a contract the release carries and the hub does not, and
contract drift: for one that differs — naming roles sync when the local
file is absent or an earlier release's text, which that verb writes, and
roles diff <role> when it is a fork. A spoke holds no contracts and prints
none of these. In hub and spoke alike, one line for .codecrew/AGENTS.md
when it is not the embedded scaffold: agents file missing: or agents file
drift:, naming roles sync for an absent file or a release's scaffold and
roles diff .codecrew/AGENTS.md for the project's own.
Under each milestone it prints struck: <ID> — decision <link> for every
requirement struck by a recorded Decision, read and verified exactly as
milestone close counts strikes; a note: for a struck or reinstated line
whose link does not verify, naming DECISION_UNRECORDED; and a note: for a
bold ID struck through in the body with no strike behind it, which is still a
requirement.
Options. None.
Reads. The pointer and, from a spoke, the hub's; the hub's open
milestones, their sub-issues, labels and comments, and the comments a strike
links to; the running repository's
remote task/<n>-… branches and one issue per branch; the repository's
delete-branch-on-merge setting; in a hub, the local .codecrew/roles/
contracts, for the contract lines.
Writes. Nothing. The report and every note: line it carries — the
delete-on-merge setting, the contract lines, a branch listing it could not
read or could not finish — go to stdout.
Refusals. The common ones. Nothing in the report
refuses: an ID that is not the milestone's own prints as a line and the
report carries on, a branch whose task cannot be read is a note: and is
left, a listing that held more than one page says it is partial, and a
repository that could not be read at all replaces the branch report with a
note: — the report's silence means clean and nothing else.
Exit status. 0 when the report printed; 1 on a common refusal.
gh codecrew milestone new¶
Creates a milestone tracking issue in the hub from the template, labelled
cc:milestone and titled M<n>: <title>. Each --requirement becomes a
bold-ID line under ## Requirements, numbered M<n>-R1, R2, … in the
order given — the section the close gate reads. The number n is derived
twice: before creating, as one past the highest M<k>: title across the
hub's label-filtered milestone listing and its newest unfiltered issues; and
after creating, when both listings are read again and the number must be the
new issue's alone. Another issue already carrying the prefix has the new
issue renumbered to the next free number, title and M<n>-R<k> IDs, printed
as a renumbered: line.
Options
| Option | Argument | Default | Effect |
|---|---|---|---|
--title |
text | none; required | The milestone's title. A title carrying an M<k> prefix that disagrees with the derived number is refused; one that agrees is stripped. |
--goal |
text | _To be written._ |
The ## Goal paragraph. |
--requirement |
text | none | One requirement, repeatable. The CLI numbers them; text that brings its own ID is refused. |
--dry-run |
none | off | Prints the number, title and requirement IDs the milestone would get; creates nothing. |
Reads. The pointer and, from a spoke, the hub's; the hub's milestone listing and its newest issues.
Writes. One issue in the hub, labelled cc:milestone. No file: the
milestone's ROADMAP.md row is added, Done, by its document PR. On stdout:
the issue reference, the title, the requirement IDs counted, any
renumbered: line, and a note: when the requirements section it wrote
yields no bold ID.
--dry-run. The number, title and requirement IDs; no issue.
Refusals. MILESTONE_NUMBER_TAKEN — the issue was
created and another milestone holds its M<n>: prefix, and the renumbering
did not settle it; the detail names both issues and the hand fix. A missing
--title, a title whose own M<k> prefix disagrees, and a --requirement
carrying an ID exit 1 without a code. Plus the common
refusals.
Exit status. 0 when the issue exists as described; 1 otherwise.
gh codecrew milestone new --title "Greeting service" --dry-run
gh codecrew milestone new --title "Greeting service" --goal "…" \
--requirement "visiting the app's URL returns a greeting" \
--requirement "the greeting is configurable"
gh codecrew milestone close¶
Closes a milestone once every gate passes, then sweeps the task branches it
leaves behind. The gates, in order: the milestone issue carries no
cc:needs-decision; every task under it is closed; its ## Requirements
section declares at least one bold requirement ID; every ID it declares is
the milestone's own; every requirement's latest QA verdict is satisfied —
only verdicts from the qa role's holder count, and the latest comment
carrying a verdict for an ID wins — unless the requirement is struck; and the
milestone document is on the default branch. A requirement struck by a
recorded Decision (milestone strike) is
terminal and wants no verdict: only the coordinator seat's holder's struck
and reinstated lines count, the latest per ID winning, and each one's
decision link is verified again, so a line posted by hand is held to the
verb's own check. The closing comment names the struck IDs. It then deletes each task branch whose PR merged and which
still sits at the merged commit, or which has no open PR and carries nothing
beyond the default branch, reporting every branch it keeps and why. A second
pass reaches the branches earlier closes left behind, across the hub and
every repo the milestone's tasks name, and names them in the closing comment
under Swept from earlier closes:. It gathers the milestone's
Decision and Deviation comments — the milestone issue's own, its tasks' and
their PRs' — as raw material for the doc-synthesizer.
Options
| Option | Argument | Default | Effect |
|---|---|---|---|
--dry-run |
none | off | Prints every gate in order with its outcome, then every branch either sweep would delete or keep and why, then the closing comment; writes nothing; exits with the first refusal's code. |
Reads. The pointer and, from a spoke, the hub's; the milestone issue, its
sub-issues, their labels and comments; docs/milestones/<n>-*.md on the
hub's default branch; the task/<n>-… branches of the hub and of every repo
the tasks name, and the pull requests whose head each candidate branch is.
Writes. In GitHub: the closing comment on the milestone issue, the issue
closed, and the branches the two sweeps delete. Nothing on disk. The report,
the gate lines and the sweep's note: lines all go to stdout.
--dry-run. The same gates in the same order, the same sweep verdicts
and the same closing comment; nothing written.
Refusals. NOT_FOUND — no open milestone with that
number. MILESTONE_GATED — the milestone issue carries
cc:needs-decision. OPEN_TASKS — tasks under it are
still open. NO_REQUIREMENTS — the ## Requirements
section declares no bold ID. REQUIREMENT_ID_MISMATCH
— an ID declared there is not the milestone's own; the grammar is
M<milestone>-R<k>. DECISION_UNRECORDED — a counted
struck or reinstated line's link does not verify; checked before the
verdicts. VERDICT_MISSING — a requirement that is not
struck has no QA verdict from the qa seat's holder.
VERDICT_UNSATISFIED — the latest verdict on a
requirement is not satisfied. DOC_MISSING — no
docs/milestones/<n>-*.md on the default branch; the doc-synthesizer
delivers it as a housekeeping PR with no task (SPEC §4), and the gate checks
that it is there, not how it arrived. Plus the
common refusals. Nothing in the sweep refuses: a branch
the verb could not read is a note: and is left standing, and a repo
carrying more task branches than one listing holds is swept in part and says
so.
Exit status. 0 when the milestone is closed; 1 on the first refusal,
whose code it exits with under --dry-run too.
gh codecrew milestone strike¶
gh codecrew milestone strike <milestone number> <ID>
--decision <comment URL>
[--reinstate]
[--dry-run]
Strikes a requirement from the milestone's scope by a recorded Decision
(SPEC §4): posts **<ID> — struck.** <comment URL> as a
comment on the milestone issue. It never edits the body. Striking is the
coordination layer's scope change, not QA's. The gates, in order: the
milestone is open; the ID, written in full (M4-R2), is declared under its
## Requirements; and the decision link is an issue-comment URL on the
milestone issue or one of its tasks whose comment carries a **Decision:**
or **Gate resolved:** record naming the ID. The verb checks the record,
not the actor, but milestone close counts only the coordinator seat's
holder's lines, so a caller who does not hold that seat gets a note:
saying so, and the line is posted anyway. An ID already struck posts nothing
and says so, so a repeated run is a no-op.
Options
| Option | Argument | Default | Effect |
|---|---|---|---|
--decision |
URL | none; required | The Decision comment: https://github.com/<owner>/<repo>/issues/<n>#issuecomment-<id>, on the milestone issue or one of its tasks, naming the ID. |
--reinstate |
none | off | Posts **<ID> — reinstated.** <comment URL> instead, under the same checks: the requirement is back in scope and QA verdicts count again. Posts nothing when the ID is not struck. |
--dry-run |
none | off | Prints every gate and the line it would post; writes nothing; exits with the first refusal's code. |
Reads. The pointer and, from a spoke, the hub's; the hub's open milestones and their sub-issues; the milestone issue's body and comments; the comments of the issue the decision link names; the caller's login.
Writes. One comment on the milestone issue, unless the ID is already in
the state asked for. Nothing on disk. On stdout: the line and the issue it
was posted on, or why nothing was posted, and the note: for a caller who
does not hold the coordinator seat.
--dry-run. The same gates in the same order and the line; nothing
written.
Refusals. NOT_FOUND — no open milestone with that
number. REQUIREMENT_UNDECLARED — the ID is not
declared under that milestone's ## Requirements.
DECISION_UNRECORDED — the link is not an
issue-comment URL, names an issue that is neither the milestone nor one of
its tasks, names no comment there, or names a comment with no Decision
naming the ID. A missing --decision, an ID not written in full, or a
missing argument exits 1 without a code. Plus the
common refusals.
Exit status. 0 when the line is posted or there was nothing to post; 1
otherwise, and on the first refusal under --dry-run too.
gh codecrew milestone strike 4 M4-R2 --decision https://github.com/acme/hub/issues/40#issuecomment-123 --dry-run
gh codecrew milestone strike 4 M4-R2 --decision https://github.com/acme/hub/issues/40#issuecomment-123
gh codecrew milestone evidence¶
Walks the milestone's record — the tracking issue and every sub-issue, bodies and comments — and verifies that every citation resolves. A citation is a URL in prose or in a Markdown link outside code; a URL inside an inline code span, a fenced block, or a block indented four columns where it does not continue a paragraph is content and is not checked. It checks the milestone's own requirement-ID grammar before the walk. The milestone resolves whatever its state: a closed one resolves too and is reported as closed before the citation report.
A github.com citation resolves through the REST API under the caller's
auth, private repositories included, when its shape is one of these (a
query such as ?pr=12 is ignored, and so is a fragment the list does not
name):
| Cited page | Resolved as |
|---|---|
{o}/{r} — the repository |
repos/{o}/{r} |
issues/{n}, pull/{n} and its tabs |
repos/{o}/{r}/issues/{n} |
…#issuecomment-{id} on an issue or a pull request |
repos/{o}/{r}/issues/comments/{id} |
pull/{n}#pullrequestreview-{id} |
repos/{o}/{r}/pulls/{n}/reviews/{id} |
pull/{n}#discussion_r{id}, pull/{n}/files#r{id} |
repos/{o}/{r}/pulls/comments/{id} |
commit/{sha}, pull/{n}/commits/{sha}, commits/{ref}, tree/{ref} |
repos/{o}/{r}/commits/{sha or ref} |
blob/{ref}/{path}, tree/{ref}/{path} |
repos/{o}/{r}/contents/{path}?ref={ref} |
compare/{base}...{head} |
repos/{o}/{r}/compare/{base}...{head} |
releases, releases/latest, releases/tag/{tag} |
repos/{o}/{r}/releases, …/latest, …/tags/{tag} |
actions/runs/{run}, …/attempts/{n} |
repos/{o}/{r}/actions/runs/{run}, …/attempts/{n} |
actions/runs/{run}/job/{job} |
repos/{o}/{r}/actions/jobs/{job} |
actions/workflows/{file} |
repos/{o}/{r}/actions/workflows/{file} |
milestone/{n} |
repos/{o}/{r}/milestones/{n} |
Any other github.com citation under a repository the API reads is fetched
by HTTP when the repository is public. When it is private, an anonymous
fetch answers 404 whatever the page holds, so the verb cannot check it: it
prints a warning: naming the path shape (/wiki, say) and does not
refuse. A github.com page under no repository the API reads — an App, a
user — and every other host are fetched by HTTP.
Options. None.
Reads. The pointer and, from a spoke, the hub's; the hub's milestone listing in every state; the milestone issue and its sub-issues, bodies and comments; each cited URL.
Writes. Nothing. The report goes to stdout, including the note: that
says the milestone is closed, a warning: line per unresolved external
citation, and a warning: line per github.com citation in a private
repository whose shape the API cannot resolve.
Refusals. NOT_FOUND — no milestone carries that
number, open or closed. REQUIREMENT_ID_MISMATCH — an
ID under ## Requirements is not the milestone's own, checked before the
walk. EVIDENCE_UNREACHABLE — a github.com citation
does not resolve: the API cannot find what a mapped shape names, or a
public repository's page answers an error. An external citation that does
not resolve, and a private one the verb cannot verify, print a warning:
and do not block. Plus the common refusals.
Exit status. 0 when no github.com citation failed to resolve — every one resolved, or warned as unverifiable; 1 when one did not, or on any other refusal.
gh codecrew task new¶
gh codecrew task new --milestone N --title T
[--repo owner/repo] [--goal G] [--requirements IDs]
[--adopts <ref>[,<ref>]]
Creates a task issue from the template, labelled cc:task, in the spoke
named by --repo or in the current repository, and attaches it to the
milestone as a sub-issue. --adopts names the backlog captures the task
takes up: they are written under an ## Adopts section of the body, each
capture is commented on to say this task carries it, and task finish closes
them after the merge. Every adopted ref must be an open issue and is checked
before anything is created, so a refusal leaves no half-adopted task;
duplicates collapse. The milestone resolves by number from the hub's
open-milestone listing, then from the hub's newest issues regardless of
label, then again after a short wait — three reads in all, and a milestone
found by either fallback is named in the output.
Options
| Option | Argument | Default | Effect |
|---|---|---|---|
--milestone |
number | none; required | The milestone the task links into. |
--title |
text | none; required | The task's title. |
--repo |
owner/repo |
the current repository | The spoke the task issue is created in. |
--goal |
text | _To be written._ |
The ## Goal paragraph. |
--requirements |
comma-separated IDs | None directly. |
The ## Requirements line: the milestone requirement IDs this task serves. |
--adopts |
<ref>[,<ref>] |
none | A backlog issue this task adopts and task finish closes; repeatable, and each value may be a comma-separated list. A bare number resolves against the task's own repo. |
Reads. The pointer and, from a spoke, the hub's; the hub's milestone
listing and newest issues; each adopted issue's state; and, once the task
exists, the target repository's Actions workflows
(GET /repos/{owner}/{repo}/actions/workflows) and the file of each active
one under .github/workflows/, from the default branch.
Writes. In GitHub: one issue in the target repo labelled cc:task,
linked to the milestone as a sub-issue, and one comment on each adopted
capture. Nothing on disk. On stdout: the issue reference, the adoptions
recorded, a note: for a comment that could not be posted or a milestone
found by one of the fallback reads, and the workflow note below.
The workflow note. When the target repository — --repo, or the current
one — has no workflow triggered on pull_request, one note: says that
task finish will refuse NO_CHECKS until a check
reports on the task's pull request, and to add a workflow that runs on
pull_request, on the task's branch or before it, and let it report. No
workflow at all counts, and so does a set of workflows none of which names
pull_request in its on: — as one event, in a list, or as a key of the
map; pull_request_target is a different event and does not count, nor
does a disabled workflow, nor a GitHub-managed one with no file in the
repository. The note is advisory: no refusal, no change to the exit status.
Where the listing cannot be read (403, 404) or a workflow file cannot be read
or parsed, it says nothing.
Refusals. NOT_FOUND — no open milestone with that
number, after all three reads. ADOPT_NOT_OPEN — an
adopted ref could not be read, or is already closed; checked before the task
is created. GH_UNREACHABLE — GitHub could not be
reached. Plus the common refusals. A comment on a capture
that fails once the task exists is a note:; the body carries the link
either way.
Exit status. 0 when the task issue exists and is linked; 1 otherwise.
gh codecrew task start¶
Starts a task: it verifies that the issue is an open cc:task whose ## Plan
section is not empty, posts the **Started by** @<login>. record that makes
the caller the task's owner — the record task finish holds to — assigns the
caller when the routing table types them as a human, and creates the linked
development branch task/<n>-<slug>. An app:-typed caller, and any login
carrying the [bot] suffix, is not assigned: GitHub does not accept a GitHub
App as an issue assignee, and the record is the fact. A caller whose role
routing resolves to a contract that forbids commits — qa, reviewer — gets
no branch. Where it creates the branch, it also reads the branch policy the
task's pull request will meet: each status check a ruleset in force on the
default branch requires, and that no check run or commit status has
reported on the newest pull requests into that branch, is named in a
note: saying no workflow in the repository reports it, so the merge will
block or rely on a bypass until one does. The note is advisory and changes
nothing; where the rules cannot be read — a plan without rulesets answers
404 or 403 — or there is no pull request yet to compare with, it says
nothing. Beside it, the same workflow note as
task new: when the task's repository has no
workflow triggered on pull_request, one note: that task finish will
refuse NO_CHECKS until a check reports on the task's
pull request, and to add a workflow that runs on pull_request and let it
report — silent when any read fails, and no refusal either way.
Options. None.
Reads. The pointer and, from a spoke, the hub's routing table; the task
issue, its labels, its body's ## Plan section; the caller's login; and,
where it creates the branch, the repository's default branch, the rules in
force on it (GET /repos/{owner}/{repo}/rules/branches/{branch}) and, when
they require status checks, the check runs and commit statuses on the heads
of the three newest pull requests into it; and the repository's Actions
workflows (GET /repos/{owner}/{repo}/actions/workflows) with the file of
each active one under .github/workflows/, from the default branch.
Writes. In GitHub: the **Started by** comment on the task issue, the
assignment where the caller is human, and the linked branch. Nothing on disk
— the branch is created on the remote, and the printed line names the
git fetch && git switch that brings it local. On stdout: the branch
created, the local command, the start record, a note: for an assignment
or a branch creation that failed, a note: for each required status
check nothing has reported, and the workflow note.
Refusals. CLOSED — the task issue is already
closed. NOT_A_TASK — the issue is not labelled
cc:task. NO_PLAN — the task's Plan section is
empty. Plus the common refusals. A failed assignment is a
note:.
Exit status. 0 when the record is posted; 1 on a refusal.
gh codecrew task finish¶
The gatekeeper: it checks every gate, merges the closing pull request by
rebase, reads the task back to see that GitHub closed it, closes the
captures the task adopted, deletes the head branch and tidies the clone it
runs in. The gates, in order: the task is open; it
carries no cc:needs-decision; every gate raised on it has a
**Gate resolved:** answer; the caller is the seat that started it — the
login in the latest **Started by** record, the same login with the [bot]
suffix ignored, or the same routed seat, a team-held role being any member; a
pull request closes the task; its CI checks exist and are green; the reviewer
role's holder has approved when that role routes to a distinct principal, and
otherwise any non-doer has; and GitHub's own required-review rule counts the
approval. The merge is a request GitHub decides: the verb asks GitHub's
merge endpoint (PUT /repos/{owner}/{repo}/pulls/{n}/merge, rebase, pinned
to the head commit the gates read), so a ruleset bypass the caller holds is
honoured, and anything GitHub refuses — a rule the caller cannot bypass, a
head that moved after the gates read it — is reported in GitHub's own words
and the verb exits 1 with nothing after the merge done. Before the merge it
prints a note: naming every issue the pull request's closing references
would close besides the task itself; a note: for each status check a
ruleset on the base branch requires that nothing has reported on the pull
request's head, saying no workflow in the repository reports it, so the
merge will block or rely on a bypass until one does; and, when GitHub
reports the pull request BLOCKED while every gate passed, a note: that
the merge is asked of GitHub as the caller, who GitHub lets through on a
ruleset bypass they hold and refuses otherwise, naming the rule types in
force when they can be read. Whether the caller holds a bypass is not read —
only an administrator can — so the note reports the record rather than
predicting the outcome. Where the rules cannot be read — 404 on a plan
without rulesets, 403 — or the head's checks cannot, these notes say
nothing; none of them is a gate. After the
merge it reads the task back rather than assuming its closing keyword closed
it: GitHub closes on the keyword asynchronously, and has been seen not to
act on one present at merge time
(#386). It reads up
to four times, two seconds apart — about six seconds of waiting at most —
and prints <task> closed as soon as a read finds it closed, or, after the
last, note: <task> is still open after the merge: GitHub did not act on
the closing reference; close it by hand with a comment naming PR #<n> and
<merge sha>. The verb does not close the task itself. It then closes each
adopted capture with a comment naming the task, the pull request and the
merge commit, and reads each back the same way: closed <capture> (adopted
by <task>) once it reads closed, or note: <capture> is still open after
the close (adopted by <task>): GitHub did not act on it; close it by hand
with a comment naming PR #<n> and <merge sha>. A read that fails every time
is a note: saying the issue could not be read back. Then it deletes the
head branch, and — in a clone of
the task's own repo that holds the merged branch — fetches with prune,
switches to the default branch if that branch is checked out, fast-forwards
it, and deletes the local task branch. Nothing after the merge refuses.
Options
| Option | Argument | Default | Effect |
|---|---|---|---|
--operator-confirm |
none | off | Solo tier: records an explicit operator confirmation on the pull request in place of a non-doer approval. A crew identity is refused. |
--bypass |
none | off | Merges with the ruleset's administrator bypass when GitHub does not count the recorded approval, recorded as a comment on the pull request. For a human operator the ruleset lists as a bypass actor; a crew identity is refused. The merge is the same request to GitHub's merge endpoint, and GitHub decides eligibility. |
--dry-run |
none | off | Prints every gate in order — ok, refused with its code, not reached, not applicable — then the comments, merge, adopted captures, head deletion and local cleanup it would perform; writes nothing, the clone included; exits with the first refusal's code. |
Reads. The pointer and, from a spoke, the hub's routing table; the task
issue, its labels and comments; the pull requests that close it, their
checks, reviews, review decision, base branch and merge state; the closing
references GitHub parsed from the pull request body; the rules in force on
the base branch (GET /repos/{owner}/{repo}/rules/branches/{branch}) and
the check runs and commit statuses on the pull request's head; after the
merge, the merge commit, and the task and each capture it closed, read back
up to four times two seconds apart; the local clone's branches and remotes.
Writes. In GitHub: the operator-confirmation or bypass comment where one
applies, the rebase merge through GitHub's merge endpoint — GitHub closes the
task on the pull request's closing keyword; the verb writes no close on it —
a comment on each adopted capture and that capture closed, and the head
branch deleted. On disk, and only in a
clone of the task's own repo: a fetch with prune, a switch to the default
branch, a fast-forward of it, and the deletion of the local task branch —
forced, and allowed only when the branch sits at the merge commit or is
contained in the fetched default branch. A branch carrying anything else is
named and kept. Run anywhere else, the local half does nothing and prints
nothing. Everything it prints as it works is on stdout: the gate lines, the
merge, the note: lines before it — the BLOCKED merge, the pull request's
other closing references, a required check nothing has reported — the
read-back's <task> closed or its still-open note:, and every note:
after it.
--dry-run. Every gate and every write above, printed and not performed —
the merge as would merge PR #<n> (rebase); <task> closes via its closing
keyword, since a dry run cannot observe the close and reads nothing back;
the BLOCKED note indented under the bypass actor gate; the same note:
lines about the pull request's other closing references and about a
required check nothing has reported; the same exit code as the real run's
first refusal.
Refusals. CLOSED — the task issue is already
closed. GATED — the task carries cc:needs-decision.
GATE_UNRECORDED — a gate was raised and the label
removed with no **Gate resolved:** comment answering it.
NOT_OWNER — the caller is not the seat that started
the task, or nothing records a start. NO_PR — no open
pull request closes the task.
NO_CHECKS_PERMISSION — the caller's installation
token cannot read the pull request's checks at all.
NO_CHECKS — the pull request reports no checks; a
check reporting skipping is a reported check and satisfies the gate, a
[skip ci] commit produces none at all, and there is no override. A
repository needs a workflow on pull_request before its first task:
init says so in its next: lines, and
task new and task start
in a note: while the repository has none.
CHECKS_PENDING — its checks are still
running. CHECKS_FAILING — a check failed.
NO_HOLDER_REVIEW — the reviewer seat's holder has not
approved. NO_NONDOER_APPROVAL — the reviewer seat is
operator-held and no non-author has approved.
REVIEW_NOT_COUNTED — the protocol's review gate
passed and GitHub's own required-review rule has not; the detail names the
supported paths. SELF_CONFIRM — --operator-confirm
was given by a crew identity. CREW_BYPASS —
--bypass was given by a crew identity. Plus the
common refusals. A merge GitHub refuses is not a
refusal code: the verb prints GitHub's message and exits 1. After the merge nothing refuses: a
capture already closed is reported, one that cannot be closed is a note:,
and so are a task or capture still open after the read-back and a failed
deletion.
Exit status. 0 when the merge happened — a task still open after the
read-back is a note:, not a failure, because the merge is done and the
output must say so; 1 on the first refusal, whose code --dry-run exits with
too, or when GitHub refuses the merge.
gh codecrew task finish 12 --dry-run
gh codecrew task finish 12
gh codecrew task finish 12 --operator-confirm # solo tier, human operator
gh codecrew checkpoint¶
Raises a human gate: it posts the question as a comment and applies
cc:needs-decision, creating the label with the protocol's colour and
description if the repository does not define it yet. The ref is a task, or
the milestone issue when the question is about a requirement and no task
carries it; the comment and the receipt say which. The gate blocks
task finish until a human removes the label and a **Gate resolved:**
comment records the answer.
Options
| Option | Argument | Default | Effect |
|---|---|---|---|
--question |
text | none; required | The judgment call a human must make. Posted verbatim as the comment. |
Reads. The pointer and, from a spoke, the hub's; the referenced issue and the repository's labels.
Writes. In GitHub: the question as a comment on the referenced issue, the
cc:needs-decision label created if absent, and the label applied. Nothing
on disk. On stdout: the receipt naming the issue and that the gate is
raised, and a note: for a label creation that failed.
Refusals. The common ones. A missing --question or
<ref> exits 1 without a code. A label creation that fails is a note: and
the gate is still raised: applying an unknown label creates it implicitly,
and only the colour is lost.
Exit status. 0 when the gate is raised; 1 otherwise.
gh codecrew role¶
Prints the typed identity holding a role — app:<slug>, user:<login>,
team:<org>/<slug>, or ~ for the operator — on one line. Script-consumable,
and correct from a pointer-only spoke, where the hub's routing table is
fetched at load. With --login it prints instead the handle GitHub will
accept a review request for, and nothing at all for an app: or ~ seat,
neither of which can be requested: the emptiness is the caller's whole
decision.
Options
| Option | Argument | Default | Effect |
|---|---|---|---|
--login |
none | off | Prints the review-requestable handle: the login of a user: seat, <org>/<slug> for a team: seat, and nothing for an app: or ~ seat. |
Reads. The pointer and, from a spoke, the hub's routing table.
Writes. Nothing. One line on stdout, or no line at all under --login for
an unrequestable seat.
Refusals. The common ones — a hub that cannot be read
refuses rather than answering ~. A missing or unknown role name exits 1
without a code.
Exit status. 0 when the role resolved, including the empty --login
output; 1 otherwise.
gh codecrew role reviewer # app:myorg-checker
reviewer=$(gh codecrew role reviewer --login)
[ -n "$reviewer" ] && gh pr create --reviewer "$reviewer"
gh codecrew roles diff¶
Shows how the project's .codecrew/roles/<role>.md differs from the contract
embedded in the installed CLI — or, given the path .codecrew/AGENTS.md, how
that file differs from the scaffold the CLI writes, in a hub or a spoke; its
last line says whether it is a release's scaffold, which roles sync
rewrites, or the project's own, which it never does. A .codecrew/roles/<role>.local.md extension
is never drift. A contract that is still an earlier release's text,
unedited, is brought up to date by roles sync;
one that differs from every release's text is the project's own fork, and
reconciling it is a judgment routed through a task and a pull request, never
an overwrite. The diff's last line says which of the two it is.
Options. None.
Reads. The pointer; the local .codecrew/roles/<role>.md (or
.codecrew/AGENTS.md); the contract (or scaffold) embedded in the binary,
and the table of every release's text it carries.
Writes. Nothing. The diff goes to stdout.
Refusals. The common ones. A role name the CLI does not embed exits 1 without a code.
Exit status. 0 whether or not the contracts differ; 1 on a refusal.
gh codecrew roles show¶
Prints the contract a dispatched session loads: the hub's
.codecrew/roles/<role>.md with its local extensions appended in order — the
hub's, then the spoke's. With --latest it prints the contract embedded in
the installed binary, whole and without extensions.
Options
| Option | Argument | Default | Effect |
|---|---|---|---|
--latest |
none | off | Prints the contract embedded in the installed binary instead of the composed one. |
Reads. The pointer; in a hub, .codecrew/roles/<role>.md and
.codecrew/roles/<role>.local.md from disk; from a spoke, the hub's contract
and extension from its default branch through GitHub, and the spoke's own
extension from disk.
Writes. Nothing. The contract goes to stdout.
Refusals. GH_UNREACHABLE — from a spoke, the
hub's contract could not be fetched. Plus the common
refusals. In a hub the verb reads only local files and needs no network.
Exit status. 0 when the contract printed; 1 on a refusal.
gh codecrew roles sync¶
Brings the hub's role contracts, and .codecrew/AGENTS.md in a hub or a
spoke, to the ones embedded in the installed CLI, where that needs no
judgment. With no arguments it takes everything the repository holds: in a
hub every embedded contract and the agents file, in a spoke the agents file
alone. A role list selects those contracts and leaves the agents file
alone; its path, .codecrew/AGENTS.md, selects it. The agents file is
measured against the scaffold init writes and every earlier release's,
exactly as a contract is, and is never stamped. Each contract — every embedded role, or the
roles named — is read stamp-stripped, with line endings ignored, and is one
of four things: the embedded text, kept; absent, written; an earlier
release's text, unedited, written; or anything else, a fork, which refuses
the whole run before anything is written. A .codecrew/roles/<role>.local.md
extension is never read, written or committed. The result is one local
commit, never pushed, for a pull request on the housekeeping path
(SPEC §4): the tool defined the target, and the commit
says so.
A written file keeps its form: one that carried init's provenance stamp,
or was absent, is written stamped as init writes it; one kept unstamped, as
a hub's own contracts are, stays unstamped.
Options
| Option | Argument | Default | Effect |
|---|---|---|---|
<role> |
a role name, repeatable | every embedded role, and the agents file | Limits the run to the roles named, so the others can be synced past a fork; the agents file is not among them. In a spoke there are no roles to name. |
.codecrew/AGENTS.md |
none | included when no target is named | Adds the agents file to a run that names roles, or is the whole run when named alone. |
--dry-run |
none | off | Prints every line the run would print, the commit and the branch it would go on; writes nothing. |
Reads. The pointer; the hub's .codecrew/roles/<role>.md files and
.codecrew/AGENTS.md; the contracts and the scaffold embedded in the
binary, and the table of every release's text it carries; the local git repository — its current branch and the
default branch as the clone records it (origin/HEAD). No network.
Writes. The files to write, then one commit of exactly those paths
with the subject chore: sync codecrew role contracts to <version> —
chore: sync the codecrew agents file to <version> when the agents file is
all it writes, chore: sync codecrew role contracts and agents file to
<version> when it writes both — and a body, in the shape init writes
(SPEC §4), naming the run with the targets it was
given, the release, the protocol, and each file with what it was:
Written by gh codecrew roles sync qa (gh-codecrew v2.1.1, protocol 2.1):
.codecrew/roles/qa.md: written (was the v2.0.0 text)
Housekeeping (SPEC §4); no task.
Every other staged and unstaged change is left as it was. On the default
branch the commit goes on a new codecrew-roles-sync branch cut from it,
since the housekeeping path is always a pull request; on any other branch it
goes where it is; with a detached HEAD the files are written and a note:
prints the commit to make by hand, body included. It never pushes. Everything it prints is
on stdout.
--dry-run. The same lines with would write and would keep, the
commit and the branch it would go on, and the same refusal; nothing written.
Refusals. CONTRACT_FORKED — a contract among
those named is a fork: the detail names each one with its roles diff, and
the .local.md mechanism for the project's additions, and a forked agents
file beside it. AGENTS_FORKED — no contract is
forked but .codecrew/AGENTS.md is the project's own: the detail names its
roles diff and, in a hub, how to sync the contracts alone. Plus the
common refusals. A role named in a spoke, which holds no
contracts, exits 1 without a code; so does a role name the CLI does not
embed, a hub outside a git repository, and a codecrew-roles-sync branch
left over from an earlier run when it is run on the default branch.
Exit status. 0 when the files were written and committed, or were
already current; 1 on a refusal. --dry-run exits 1 when it reports the
refusal.
gh codecrew roles sync --dry-run # what would be written, and where
gh codecrew roles sync # hub: the contracts and .codecrew/AGENTS.md;
# spoke: the agents file alone; one local commit
gh codecrew roles sync coordinator qa # only these, past a fork elsewhere
gh codecrew identity new¶
gh codecrew identity new <role> --name N
[--owner O] [--with-webhook --webhook-url U]
[--no-route]
[--with-approval-permission]
[--events E,E] [--webhook-secret S]
Mints the role's App identity through the GitHub App manifest flow: it builds
a manifest carrying the role's minimal permission set, serves a one-click
loopback URL for the operator to open, stores the private key GitHub returns,
writes the role's routing into the hub's .codecrew/config.yml in typed form
(identity: app:<slug>), and prints the steps that remain — installing the
App, which is per account, and the optional display polish. Webhooks are off
by default.
Options
| Option | Argument | Default | Effect |
|---|---|---|---|
--name |
App name | none; required | The App's name — a crew member (myorg-coder), not a role. |
--owner |
account | the hub's owner | The account that owns the App. |
--no-route |
none | off | Prints the routing step instead of writing it into the hub's pointer. |
--with-webhook |
none | off | Subscribes the App to protocol traffic; requires --webhook-url. |
--webhook-url |
URL | none | The receiver for --with-webhook deliveries. |
--with-approval-permission |
none | off | Reviewer only: grants contents: write, so the App's approvals satisfy GitHub's required-review rules. |
--events |
comma-separated events | pull_request,pull_request_review |
With --with-webhook: the events to subscribe, validated against the role's permissions. |
--webhook-secret |
secret | none | With --with-webhook: sets the hook secret to the receiver's as soon as the App exists. |
Reads. The pointer and the hub it names; the owner account's type, through GitHub; the manifest conversion GitHub returns at the end of the browser flow.
Writes. On disk: ~/.config/codecrew/<slug>.<date>.private-key.pem and
the credential stub ~/.config/codecrew/<slug>.json; the hub's
.codecrew/config.yml routing row, uncommitted, unless --no-route. In
GitHub: the App itself, and its hook secret when --webhook-secret is given.
On stdout: the loopback URL to open, the stored paths, and the numbered steps
that remain.
Refusals. The common ones. A missing <role> or
--name, a manifest the flags do not make valid, --webhook-secret without
--with-webhook, and a browser flow that does not complete within the hour
exit 1 without a code.
Exit status. 0 when the App exists and its key is stored; 1 otherwise.
gh codecrew identity new implementer --name myorg-coder
gh codecrew identity new reviewer --name myorg-checker --with-approval-permission
gh codecrew identity webhook¶
Reads and sets an existing App's own hook, under the App's own key: --show
prints the URL, the content type, whether a secret is set and the App's
subscribed events; --url and --secret set them; --rotate-secret mints a
new secret, sets it and prints it once. An App hook covers every repository
its installation sees. Event subscriptions cannot be set after creation —
GitHub offers no endpoint — and the verb prints the settings page where they
are ticked.
Options
| Option | Argument | Default | Effect |
|---|---|---|---|
--show |
none | off | Prints the hook's URL, content type, whether a secret is set, and the subscribed events. |
--url |
URL | none | Sets the receiver URL. |
--secret |
secret | none | Sets the hook secret to the receiver's. Never stored, never printed. Exclusive with --rotate-secret. |
--rotate-secret |
none | off | Generates a new secret, sets it, and prints it once. |
--events |
comma-separated events | none | Not settable after creation: the verb prints where they change, and what is subscribed now. |
Reads. The App's private key and credential stub — the environment
bindings first (GITHUB_APP_ID or GITHUB_CLIENT_ID, and GITHUB_PRIVATE_KEY
or GITHUB_PEM as PEM text or a path), else ~/.config/codecrew/<slug>; the
App's own record and hook configuration from GitHub. It reads no pointer and
runs from anywhere.
Writes. In GitHub: the App's hook URL and secret, where those flags are given. Nothing on disk. On stdout: the hook's configuration, and a rotated secret once.
Refusals. NO_CREDENTIALS — nothing to sign with:
no App id and key in the environment, and no key and stub under
~/.config/codecrew/. BAD_CREDENTIALS — GitHub
rejected the App JWT, or knows no App by the id it was signed as.
NO_WEBHOOK — the App was minted without a webhook and
GitHub's API cannot create one; the detail names the settings page. --secret
with --rotate-secret, and --events, exit 1 without a code.
Exit status. 0 when the hook was printed or set; 1 otherwise.
gh codecrew identity webhook myorg-coder --show
gh codecrew identity webhook myorg-coder --url https://example.test/hook --rotate-secret
gh codecrew identity token¶
Mints a short-lived installation token as the App and prints it alone on
stdout, with a receipt on stderr naming the slug, App id, installation and
account it minted for. Credentials come from the environment first, under the
names platforms bind, and otherwise from the key and stub under
~/.config/codecrew/ for the slug. It signs the App JWT, discovers the
installation from the App itself — one installation is taken, several narrow
to the hub's owner — and never writes gh's config. The <slug> argument
selects the key and stub under ~/.config/codecrew/ and is optional when the
environment binds an App id and key. It requires no pointer and runs from
anywhere: a pointer in the directory it runs in is read only to prefer the
hub's owner, and a missing or broken one is no refusal.
Options
| Option | Argument | Default | Effect |
|---|---|---|---|
--installation |
id | GITHUB_INSTALLATION_ID, else none |
A hint, used only when the App can see that installation. |
Reads. GITHUB_APP_ID or GITHUB_CLIENT_ID, and GITHUB_PRIVATE_KEY or
GITHUB_PEM (PEM text or a path), then GITHUB_INSTALLATION_ID; failing
those, ~/.config/codecrew/<slug>.json and its private key. GitHub, for the
App's installations and the token. The pointer, only to learn the hub's owner
for narrowing, and never as a requirement.
Writes. Nothing on disk, and nothing in GitHub beyond the token GitHub mints. The token alone on stdout; the receipt on stderr.
Refusals. NO_CREDENTIALS — no App id and key in
the environment, and no key and stub under ~/.config/codecrew/.
BAD_CREDENTIALS — GitHub rejected the App JWT, or
knows no App by the id it was signed as.
NO_INSTALLATION — the App is installed on no account
its key can see. INSTALLATION_AMBIGUOUS — the App is
installed on several accounts and nothing selects one.
Exit status. 0 when a token was printed; 1 otherwise.
export GH_TOKEN=$(gh codecrew identity token myorg-coder)
gh codecrew identity token myorg-coder --installation 12345678
gh codecrew version¶
Prints the installed release tag and the protocol version it implements, as
<tag> (protocol <major.minor>). A build from source reports dev. The
shape is stable within a major.
Options. None. --version and -v are the same verb.
Reads. Nothing: no pointer, no network, no files.
Writes. Nothing. One line on stdout.
Refusals. None.
Exit status. 0.
Licensed under Apache 2.0.