my-github is a single-purpose CLI for fetching issues, issue lists, pull requests, pull request lists, commits, and commit history for a specific ref from the GitHub REST API.
It accepts exactly one JSON object as input and prints JSON as output.
This command is part of the my-cli project, and all build, test, and lint tasks run inside Docker containers.
For rules on using the my-github binary in LLM / agent environments, see docs/my-github/SKILL.md.
- Docker
- GNU Make
You do not need to install Go or golangci-lint locally.
Build from the repository root like this.
make build my-github
make build CMD=my-githubCommon option examples:
make build my-github VERSION=1.0.0
make build my-github GO_VERSION=1.26.1
make build my-github CGO_ENABLED=1
make build my-github GOOS=linux GOARCH=amd64
make build my-github GOPRIVATE='github.com/your-org/*'The output binary is created at bin/my-github.
During the build, ldflags injects a VERSION-git_commit value into main.Version.
./bin/my-github --help
./bin/my-github --dry-run '{"kind":"issue","owner":"cli","repo":"cli","number":123}'
./bin/my-github --version
./bin/my-github -versionTo use it directly from Codex CLI, run the following script from the repository root.
./scripts/install-my-github-codex.shThis script runs make build CMD=my-github, then updates both ~/.codex/bin/my-github and ~/.codex/skills/my-github/*.
If you use a different Codex home, run it like CODEX_HOME=/path/to/codex ./scripts/install-my-github-codex.sh.
Tests run inside the golang:<GO_VERSION> Docker image.
make test my-github
make test CMD=my-githubYou can pass extra options as well.
make test my-github TEST_FLAGS="-v"
make test my-github TEST_FLAGS="-run TestRootCommandFetchesIssue -v"Linting uses golangci-lint inside Docker. The default image is golangci/golangci-lint:v2.9.0.
make lint my-github
make lint CMD=my-githubAdditional option examples:
make lint my-github LINT_FLAGS="--verbose"
make lint my-github LINT_TIMEOUT=10m
make lint my-github GOLANGCI_LINT_VERSION=2.9.0make list-cmds
make print-version
make cleanThe configuration file name is my-github.yaml.
Settings are loaded through src/pkg/config/config.go.
The search paths are checked in this order.
/etc/my-github/my-github.yaml~/.config/my-github.yaml./my-github.yaml
Existing files are merged in that order, and later values override earlier ones.
If no configuration file exists, the following defaults are used.
github.base_url:https://api.github.com/github.timeout:15sgithub.user_agent:my-cli/my-githubgithub.token: emptygithub.by_base_url: empty
The top-level github values are the shared defaults.
Selection starts from github.base_url, and if the request JSON includes base_url or alias, that choice takes precedence.
If there is a github.by_base_url[] entry that matches the selected GitHub instance, its base_url, token, timeout, and user_agent values are applied last.
github.by_base_url[].alias can be selected directly through the request JSON alias field, and base_url matching ignores a trailing /.
Example:
github:
base_url: https://api.github.com/
timeout: 15s
user_agent: my-cli/my-github
by_base_url:
- alias: github.com
base_url: https://api.github.com/
token: "{{ .GITHUB_TOKEN }}"
- alias: example-ghe
base_url: https://ghe.example.com/api/v3/
token: "{{ .GHE_TOKEN }}"
timeout: 30s
user_agent: my-cli/my-github-enterpriseSecret values such as token can also be managed through config templates.
In the example above, the runtime environment variables GITHUB_TOKEN and GHE_TOKEN are read and injected into the token for each matching base URL.
When you use multiple GitHub instances, changing only github.base_url is enough for the matching override to be applied automatically, and alias gives you a quick way to identify each entry.
Pass JSON input in one of the following ways.
./bin/my-github '{"kind":"issue","owner":"cli","repo":"cli","number":123}'./bin/my-github '{"kind":"issue_list","owner":"cli","repo":"cli","limit":10}'echo '{"kind":"commit","owner":"cli","repo":"cli","ref":"trunk"}' | ./bin/my-github./bin/my-github '{"kind":"commit_history","owner":"cli","repo":"cli","ref":"release/1.0","limit":10}'./bin/my-github '{"kind":"pull_request_list","owner":"cli","repo":"cli","limit":10}'./bin/my-github '{"kind":"issue","owner":"cli","repo":"cli","number":123,"alias":"example-ghe"}'./bin/my-github '{"kind":"pull_request","owner":"cli","repo":"cli","number":456,"base_url":"https://ghe.example.com/api/v3"}'Supported flags:
--version,-version,-v--dry-run,-dry-run,-n--help,-help,-h
- Only one JSON object is allowed as input.
- At most one argument may be provided.
- Unknown fields are treated as errors.
kind,owner, andrepoare always required.base_urlandaliasare optional and are used to choose a specificgithub.by_base_urlconfiguration.- If authentication is required, set
github.tokenor the selectedgithub.by_base_url[].tokeninmy-github.yaml. - If you need environment-variable-based secret injection, use a template such as
{{ .GITHUB_TOKEN }}ingithub.tokenorgithub.by_base_url[].token. - If
base_urlandaliasare provided together, they must point to the samegithub.by_base_urlentry.
| Field | Type | Required | Description |
|---|---|---|---|
kind |
string | Yes | Type of object to fetch |
owner |
string | Yes | GitHub owner or org |
repo |
string | Yes | GitHub repository name |
number |
integer | Conditional | Required for issue and pull_request |
ref |
string | Conditional | Required for commit and commit_history. For commit, use a SHA, branch, or tag. For commit_history, a branch name is typical |
limit |
integer | Conditional | Optional for issue_list, pull_request_list, and commit_history. Must be between 1 and 100, and defaults to 30 |
base_url |
string | No | GitHub API base URL to use for this request. If it matches a github.by_base_url entry, that override is applied too |
alias |
string | No | github.by_base_url[].alias value to use for this request |
| Value | Description |
|---|---|
issue |
Fetch an issue |
issue_list |
Fetch the repository issue list |
pull_request |
Fetch a pull request |
pull_request_list |
Fetch the repository pull request list |
commit |
Fetch a commit |
commit_history |
Fetch commit history for a specific ref |
The following aliases are also accepted.
issue-listissuesprpull-requestpr-listpr_listprspull-request-listpullscommit-history
Even if you pass an alias, the output kind value is normalized to issue_list, pull_request, pull_request_list, or commit_history.
Example input:
{
"kind": "issue",
"owner": "cli",
"repo": "cli",
"number": 123
}Rules:
numbermust be an integer greater than or equal to 1.refis not allowed.- If the GitHub API returns a pull request item, it is treated as an error instead of an issue.
Example output:
{
"kind": "issue",
"repository": {
"owner": "cli",
"repo": "cli"
},
"issue": {
"number": 123,
"title": "Issue title",
"state": "open",
"author": "octocat",
"assignees": ["hubot"],
"labels": ["bug", "good first issue"],
"comments": 4,
"created_at": "2026-03-10T12:00:00Z",
"updated_at": "2026-03-11T12:00:00Z",
"closed_at": null,
"url": "https://github.com/cli/cli/issues/123",
"api_url": "https://api.github.com/repos/cli/cli/issues/123",
"body": "Issue body"
}
}closed_at may be omitted or null if the issue is still open.
Example input:
{
"kind": "issue_list",
"owner": "cli",
"repo": "cli",
"limit": 2
}issue-list and issues mean the same thing.
Rules:
numberis not allowed.refis not allowed.limitmust be between 1 and 100, and defaults to 30.- If pull requests are mixed into the GitHub
/issuesresponse, they are filtered out internally.
Example output:
{
"kind": "issue_list",
"repository": {
"owner": "cli",
"repo": "cli"
},
"issue_list": {
"limit": 2,
"issues": [
{
"number": 123,
"title": "Issue title",
"state": "open",
"author": "octocat",
"assignees": ["hubot"],
"labels": ["bug"],
"comments": 4,
"created_at": "2026-03-10T12:00:00Z",
"updated_at": "2026-03-11T12:00:00Z",
"closed_at": null,
"url": "https://github.com/cli/cli/issues/123",
"api_url": "https://api.github.com/repos/cli/cli/issues/123",
"body": "Issue body"
},
{
"number": 122,
"title": "Second issue",
"state": "open",
"author": "hubot",
"assignees": [],
"labels": ["docs"],
"comments": 2,
"created_at": "2026-03-09T12:00:00Z",
"updated_at": "2026-03-10T12:00:00Z",
"closed_at": null,
"url": "https://github.com/cli/cli/issues/122",
"api_url": "https://api.github.com/repos/cli/cli/issues/122",
"body": "Another issue body"
}
]
}
}Example input:
{
"kind": "pull_request",
"owner": "cli",
"repo": "cli",
"number": 456
}pr and pull-request mean the same thing.
Rules:
numbermust be an integer greater than or equal to 1.refis not allowed.
Example output:
{
"kind": "pull_request",
"repository": {
"owner": "cli",
"repo": "cli"
},
"pull_request": {
"number": 456,
"title": "PR title",
"state": "open",
"draft": false,
"merged": false,
"author": "monalisa",
"base_branch": "main",
"base_sha": "base-sha",
"head_branch": "feature",
"head_sha": "head-sha",
"created_at": "2026-03-10T12:00:00Z",
"updated_at": "2026-03-11T12:00:00Z",
"merged_at": null,
"url": "https://github.com/cli/cli/pull/456",
"api_url": "https://api.github.com/repos/cli/cli/pulls/456",
"body": "PR body"
}
}merged_at may be omitted or null if the pull request has not been merged.
Example input:
{
"kind": "pull_request_list",
"owner": "cli",
"repo": "cli",
"limit": 2
}pr-list, pr_list, prs, pull-request-list, and pulls all mean the same thing.
Rules:
numberis not allowed.refis not allowed.limitmust be between 1 and 100, and defaults to 30.
Example output:
{
"kind": "pull_request_list",
"repository": {
"owner": "cli",
"repo": "cli"
},
"pull_request_list": {
"limit": 2,
"pull_requests": [
{
"number": 456,
"title": "PR title",
"state": "open",
"draft": false,
"merged": false,
"author": "monalisa",
"base_branch": "main",
"base_sha": "base-sha",
"head_branch": "feature",
"head_sha": "head-sha",
"created_at": "2026-03-10T12:00:00Z",
"updated_at": "2026-03-11T12:00:00Z",
"merged_at": null,
"url": "https://github.com/cli/cli/pull/456",
"api_url": "https://api.github.com/repos/cli/cli/pulls/456",
"body": "PR body"
}
]
}
}Example input:
{
"kind": "commit",
"owner": "cli",
"repo": "cli",
"ref": "trunk"
}Rules:
refmust not be empty.refcan be a SHA, branch, or tag.numberis not allowed.
Example output:
{
"kind": "commit",
"repository": {
"owner": "cli",
"repo": "cli"
},
"commit": {
"sha": "abc123",
"message": "Commit message",
"author": {
"login": "octocat",
"name": "Octo Cat",
"email": "octo@example.com",
"date": "2026-03-10T12:00:00Z"
},
"committer": {
"name": "Octo Bot",
"email": "bot@example.com",
"date": "2026-03-10T12:01:00Z"
},
"parents": ["parent1", "parent2"],
"stats": {
"additions": 12,
"deletions": 3,
"total": 15
},
"files": [
{
"filename": "README.md",
"status": "modified",
"additions": 10,
"deletions": 2,
"changes": 12,
"patch": "@@ -1 +1 @@\n-old\n+new"
},
{
"filename": "docs/old.md",
"status": "renamed",
"additions": 2,
"deletions": 1,
"changes": 3,
"previous_filename": "docs/legacy.md"
}
],
"url": "https://github.com/cli/cli/commit/abc123",
"api_url": "https://api.github.com/repos/cli/cli/commits/abc123"
}
}The GitHub commit author and the Git author may differ, so author.login may be missing.
files[].patch may be empty or omitted for binary files or when the diff is too large.
Example input:
{
"kind": "commit_history",
"owner": "cli",
"repo": "cli",
"ref": "release/1.0",
"limit": 2
}Rules:
refmust not be empty.refis passed to the GitHub APIshaquery parameter, and a branch name is typically used.limitmust be between 1 and 100, and defaults to 30.numberis not allowed.
Example output:
{
"kind": "commit_history",
"repository": {
"owner": "cli",
"repo": "cli"
},
"commit_history": {
"ref": "release/1.0",
"limit": 2,
"commits": [
{
"sha": "abc123",
"message": "First commit",
"author": {
"login": "octocat",
"name": "Octo Cat",
"email": "octo@example.com",
"date": "2026-03-10T12:00:00Z"
},
"committer": {
"login": "github-actions[bot]",
"name": "GitHub Actions",
"email": "bot@example.com",
"date": "2026-03-10T12:01:00Z"
},
"parents": ["parent1"],
"url": "https://github.com/cli/cli/commit/abc123",
"api_url": "https://api.github.com/repos/cli/cli/commits/abc123"
},
{
"sha": "def456",
"message": "Second commit",
"author": {
"name": "Mona Lisa",
"email": "mona@example.com",
"date": "2026-03-09T10:00:00Z"
},
"committer": {
"name": "Mona Lisa",
"email": "mona@example.com",
"date": "2026-03-09T10:05:00Z"
},
"parents": ["parent2", "parent3"],
"url": "https://github.com/cli/cli/commit/def456",
"api_url": "https://api.github.com/repos/cli/cli/commits/def456"
}
]
}
}Results follow the GitHub API response order and are typically returned from newest commit to oldest.
--dry-run does not call the GitHub API. It prints the request that would be sent as JSON.
The final values computed by combining github.base_url, github.token, github.by_base_url, and the request JSON base_url / alias are also reflected in the dry-run output.
{
"mode": "dry-run",
"http": {
"method": "GET",
"url": "https://api.github.com/repos/cli/cli/issues/123",
"auth": "token"
},
"request": {
"kind": "issue",
"owner": "cli",
"repo": "cli",
"number": 123
}
}auth is one of the following values.
tokennone
The following cases produce an error.
- Invalid JSON syntax
- Input that is not a JSON object
- Input that contains more than one JSON object
- Passing more than one argument
- Including unknown fields
- An invalid
kindvalue - Missing
ownerorrepo - Missing
numberornumber <= 0forissueorpull_request - Providing
refforissueorpull_request - Providing
numberforissue_listorpull_request_list - Providing
refforissue_listorpull_request_list limit < 1orlimit > 100forissue_listorpull_request_list- Missing
refforcommit - Providing
numberforcommit - Missing
refforcommit_history - Providing
numberforcommit_history limit < 1orlimit > 100forcommit_historyaliasdoes not match anygithub.by_base_url[].aliasbase_urlandaliasare both provided but point to differentgithub.by_base_urlentries- The GitHub API returns a 4xx or 5xx response