GitHub

@crvouga/mockingbird-service-github

Stateful GitHub REST API 2026-03-10 emulator for repository, reference and pull-request scenarios.

Parity: GitHub REST repositories, refs and pull requestsInfrastructure239 of 239 operationsIn-process fetchNode servermockingbird-github
$
npm install -D @crvouga/mockingbird-service-github

Playground

Requests go to the real emulator running in this tab. State persists across requests, and the journal shows what it received. A green dot marks operations whose sample succeeds as-is; others need ids from earlier responses.

Loads the real @crvouga/mockingbird-service-github into this tab on first send
https://api.github.com

Send a request to see the emulator's response.

Operations

239 of 239 operations in the vendored contract are emulated. Source-pinned REST subset. See API_EVIDENCE.md for bounded live comparisons and remaining limitations.

MethodPathOperationStatus
GET/repos/{owner}/{repo}repos/getGet a seeded repository
PATCH/repos/{owner}/{repo}PATCH /repos/:owner/:repoPATCH /repos/:owner/:repo
DELETE/repos/{owner}/{repo}DELETE /repos/:owner/:repoDELETE /repos/:owner/:repo
GET/repos/{owner}/{repo}/git/ref/{ref}git/get-refGet a reference
GET/repos/{owner}/{repo}/git/matching-refs/{ref}git/list-matching-refsList matching references
POST/repos/{owner}/{repo}/git/refsgit/create-refCreate a reference
PATCH/repos/{owner}/{repo}/git/refs/{ref}git/update-refUpdate a reference
DELETE/repos/{owner}/{repo}/git/refs/{ref}DELETE /repos/:owner/:repo/git/refs/:ref{.+}DELETE /repos/:owner/:repo/git/refs/:ref{.+}
GET/repos/{owner}/{repo}/pullspulls/listList pull requests
POST/repos/{owner}/{repo}/pullspulls/createCreate a pull request
GET/repos/{owner}/{repo}/pulls/{pull_number}pulls/getGet a pull request
PATCH/repos/{owner}/{repo}/pulls/{pull_number}pulls/updateUpdate a pull request
GET/orgs/{org}/actions/secrets/{secret_name}GET /orgs/:org/actions/secrets/:secret_nameGET /orgs/:org/actions/secrets/:secret_name
PUT/orgs/{org}/actions/secrets/{secret_name}PUT /orgs/:org/actions/secrets/:secret_namePUT /orgs/:org/actions/secrets/:secret_name
DELETE/orgs/{org}/actions/secrets/{secret_name}DELETE /orgs/:org/actions/secrets/:secret_nameDELETE /orgs/:org/actions/secrets/:secret_name
GET/orgs/{org}/hooks/{hook_id}GET /orgs/:org/hooks/:hook_idGET /orgs/:org/hooks/:hook_id
PATCH/orgs/{org}/hooks/{hook_id}PATCH /orgs/:org/hooks/:hook_idPATCH /orgs/:org/hooks/:hook_id
DELETE/orgs/{org}/hooks/{hook_id}DELETE /orgs/:org/hooks/:hook_idDELETE /orgs/:org/hooks/:hook_id
GET/orgs/{org}/members/{username}GET /orgs/:org/members/:usernameGET /orgs/:org/members/:username
DELETE/orgs/{org}/members/{username}DELETE /orgs/:org/members/:usernameDELETE /orgs/:org/members/:username
GET/orgs/{org}/teams/{team_slug}GET /orgs/:org/teams/:team_slugGET /orgs/:org/teams/:team_slug
PATCH/orgs/{org}/teams/{team_slug}PATCH /orgs/:org/teams/:team_slugPATCH /orgs/:org/teams/:team_slug
DELETE/orgs/{org}/teams/{team_slug}DELETE /orgs/:org/teams/:team_slugDELETE /orgs/:org/teams/:team_slug
GET/orgs/{org}/teams/{team_slug}/memberships/{username}GET /orgs/:org/teams/:team_slug/memberships/:usernameGET /orgs/:org/teams/:team_slug/memberships/:username
PUT/orgs/{org}/teams/{team_slug}/memberships/{username}PUT /orgs/:org/teams/:team_slug/memberships/:usernamePUT /orgs/:org/teams/:team_slug/memberships/:username
DELETE/orgs/{org}/teams/{team_slug}/memberships/{username}DELETE /orgs/:org/teams/:team_slug/memberships/:usernameDELETE /orgs/:org/teams/:team_slug/memberships/:username
PUT/orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}PUT /orgs/:org/teams/:team_slug/repos/:owner/:repoPUT /orgs/:org/teams/:team_slug/repos/:owner/:repo
DELETE/orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}DELETE /orgs/:org/teams/:team_slug/repos/:owner/:repoDELETE /orgs/:org/teams/:team_slug/repos/:owner/:repo
GET/repos/{owner}/{repo}/actions/artifacts/{artifact_id}GET /repos/:owner/:repo/actions/artifacts/:artifact_idGET /repos/:owner/:repo/actions/artifacts/:artifact_id
DELETE/repos/{owner}/{repo}/actions/artifacts/{artifact_id}DELETE /repos/:owner/:repo/actions/artifacts/:artifact_idDELETE /repos/:owner/:repo/actions/artifacts/:artifact_id
GET/repos/{owner}/{repo}/actions/runs/{run_id}GET /repos/:owner/:repo/actions/runs/:run_idGET /repos/:owner/:repo/actions/runs/:run_id
DELETE/repos/{owner}/{repo}/actions/runs/{run_id}DELETE /repos/:owner/:repo/actions/runs/:run_idDELETE /repos/:owner/:repo/actions/runs/:run_id
GET/repos/{owner}/{repo}/actions/secrets/{secret_name}GET /repos/:owner/:repo/actions/secrets/:secret_nameGET /repos/:owner/:repo/actions/secrets/:secret_name
PUT/repos/{owner}/{repo}/actions/secrets/{secret_name}PUT /repos/:owner/:repo/actions/secrets/:secret_namePUT /repos/:owner/:repo/actions/secrets/:secret_name
DELETE/repos/{owner}/{repo}/actions/secrets/{secret_name}DELETE /repos/:owner/:repo/actions/secrets/:secret_nameDELETE /repos/:owner/:repo/actions/secrets/:secret_name
GET/repos/{owner}/{repo}/branches/{branch}/protectionGET /repos/:owner/:repo/branches/:branch{.+}/protectionGET /repos/:owner/:repo/branches/:branch{.+}/protection
PUT/repos/{owner}/{repo}/branches/{branch}/protectionPUT /repos/:owner/:repo/branches/:branch{.+}/protectionPUT /repos/:owner/:repo/branches/:branch{.+}/protection
DELETE/repos/{owner}/{repo}/branches/{branch}/protectionDELETE /repos/:owner/:repo/branches/:branch{.+}/protectionDELETE /repos/:owner/:repo/branches/:branch{.+}/protection
PUT/repos/{owner}/{repo}/collaborators/{username}PUT /repos/:owner/:repo/collaborators/:usernamePUT /repos/:owner/:repo/collaborators/:username
DELETE/repos/{owner}/{repo}/collaborators/{username}DELETE /repos/:owner/:repo/collaborators/:usernameDELETE /repos/:owner/:repo/collaborators/:username
GET/repos/{owner}/{repo}/comments/{comment_id}GET /repos/:owner/:repo/comments/:comment_idGET /repos/:owner/:repo/comments/:comment_id
PATCH/repos/{owner}/{repo}/comments/{comment_id}PATCH /repos/:owner/:repo/comments/:comment_idPATCH /repos/:owner/:repo/comments/:comment_id
DELETE/repos/{owner}/{repo}/comments/{comment_id}DELETE /repos/:owner/:repo/comments/:comment_idDELETE /repos/:owner/:repo/comments/:comment_id
GET/repos/{owner}/{repo}/contents/{path}GET /repos/:owner/:repo/contents/:path{.+}GET /repos/:owner/:repo/contents/:path{.+}
PUT/repos/{owner}/{repo}/contents/{path}PUT /repos/:owner/:repo/contents/:path{.+}PUT /repos/:owner/:repo/contents/:path{.+}
DELETE/repos/{owner}/{repo}/contents/{path}DELETE /repos/:owner/:repo/contents/:path{.+}DELETE /repos/:owner/:repo/contents/:path{.+}
GET/repos/{owner}/{repo}/hooks/{hook_id}GET /repos/:owner/:repo/hooks/:hook_idGET /repos/:owner/:repo/hooks/:hook_id
PATCH/repos/{owner}/{repo}/hooks/{hook_id}PATCH /repos/:owner/:repo/hooks/:hook_idPATCH /repos/:owner/:repo/hooks/:hook_id
DELETE/repos/{owner}/{repo}/hooks/{hook_id}DELETE /repos/:owner/:repo/hooks/:hook_idDELETE /repos/:owner/:repo/hooks/:hook_id
POST/repos/{owner}/{repo}/issues/{issue_number}/assigneesPOST /repos/:owner/:repo/issues/:issue_number/assigneesPOST /repos/:owner/:repo/issues/:issue_number/assignees
DELETE/repos/{owner}/{repo}/issues/{issue_number}/assigneesDELETE /repos/:owner/:repo/issues/:issue_number/assigneesDELETE /repos/:owner/:repo/issues/:issue_number/assignees
GET/repos/{owner}/{repo}/issues/{issue_number}/labelsGET /repos/:owner/:repo/issues/:issue_number/labelsGET /repos/:owner/:repo/issues/:issue_number/labels
POST/repos/{owner}/{repo}/issues/{issue_number}/labelsPOST /repos/:owner/:repo/issues/:issue_number/labelsPOST /repos/:owner/:repo/issues/:issue_number/labels
PUT/repos/{owner}/{repo}/issues/{issue_number}/labelsPUT /repos/:owner/:repo/issues/:issue_number/labelsPUT /repos/:owner/:repo/issues/:issue_number/labels
DELETE/repos/{owner}/{repo}/issues/{issue_number}/labelsDELETE /repos/:owner/:repo/issues/:issue_number/labelsDELETE /repos/:owner/:repo/issues/:issue_number/labels
DELETE/repos/{owner}/{repo}/issues/{issue_number}/labels/{name}DELETE /repos/:owner/:repo/issues/:issue_number/labels/:nameDELETE /repos/:owner/:repo/issues/:issue_number/labels/:name
PUT/repos/{owner}/{repo}/issues/{issue_number}/lockPUT /repos/:owner/:repo/issues/:issue_number/lockPUT /repos/:owner/:repo/issues/:issue_number/lock
DELETE/repos/{owner}/{repo}/issues/{issue_number}/lockDELETE /repos/:owner/:repo/issues/:issue_number/lockDELETE /repos/:owner/:repo/issues/:issue_number/lock
GET/repos/{owner}/{repo}/issues/comments/{comment_id}GET /repos/:owner/:repo/issues/comments/:comment_idGET /repos/:owner/:repo/issues/comments/:comment_id
PATCH/repos/{owner}/{repo}/issues/comments/{comment_id}PATCH /repos/:owner/:repo/issues/comments/:comment_idPATCH /repos/:owner/:repo/issues/comments/:comment_id
DELETE/repos/{owner}/{repo}/issues/comments/{comment_id}DELETE /repos/:owner/:repo/issues/comments/:comment_idDELETE /repos/:owner/:repo/issues/comments/:comment_id
GET/repos/{owner}/{repo}/labels/{name}GET /repos/:owner/:repo/labels/:nameGET /repos/:owner/:repo/labels/:name
PATCH/repos/{owner}/{repo}/labels/{name}PATCH /repos/:owner/:repo/labels/:namePATCH /repos/:owner/:repo/labels/:name
DELETE/repos/{owner}/{repo}/labels/{name}DELETE /repos/:owner/:repo/labels/:nameDELETE /repos/:owner/:repo/labels/:name
GET/repos/{owner}/{repo}/milestones/{milestone_number}GET /repos/:owner/:repo/milestones/:milestone_numberGET /repos/:owner/:repo/milestones/:milestone_number
PATCH/repos/{owner}/{repo}/milestones/{milestone_number}PATCH /repos/:owner/:repo/milestones/:milestone_numberPATCH /repos/:owner/:repo/milestones/:milestone_number
DELETE/repos/{owner}/{repo}/milestones/{milestone_number}DELETE /repos/:owner/:repo/milestones/:milestone_numberDELETE /repos/:owner/:repo/milestones/:milestone_number
POST/repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewersPOST /repos/:owner/:repo/pulls/:pull_number/requested_reviewersPOST /repos/:owner/:repo/pulls/:pull_number/requested_reviewers
DELETE/repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewersDELETE /repos/:owner/:repo/pulls/:pull_number/requested_reviewersDELETE /repos/:owner/:repo/pulls/:pull_number/requested_reviewers
GET/repos/{owner}/{repo}/pulls/comments/{comment_id}GET /repos/:owner/:repo/pulls/comments/:comment_idGET /repos/:owner/:repo/pulls/comments/:comment_id
PATCH/repos/{owner}/{repo}/pulls/comments/{comment_id}PATCH /repos/:owner/:repo/pulls/comments/:comment_idPATCH /repos/:owner/:repo/pulls/comments/:comment_id
DELETE/repos/{owner}/{repo}/pulls/comments/{comment_id}DELETE /repos/:owner/:repo/pulls/comments/:comment_idDELETE /repos/:owner/:repo/pulls/comments/:comment_id
GET/repos/{owner}/{repo}/releases/{release_id}GET /repos/:owner/:repo/releases/:release_idGET /repos/:owner/:repo/releases/:release_id
PATCH/repos/{owner}/{repo}/releases/{release_id}PATCH /repos/:owner/:repo/releases/:release_idPATCH /repos/:owner/:repo/releases/:release_id
DELETE/repos/{owner}/{repo}/releases/{release_id}DELETE /repos/:owner/:repo/releases/:release_idDELETE /repos/:owner/:repo/releases/:release_id
GET/repos/{owner}/{repo}/releases/assets/{asset_id}GET /repos/:owner/:repo/releases/assets/:asset_idGET /repos/:owner/:repo/releases/assets/:asset_id
PATCH/repos/{owner}/{repo}/releases/assets/{asset_id}PATCH /repos/:owner/:repo/releases/assets/:asset_idPATCH /repos/:owner/:repo/releases/assets/:asset_id
DELETE/repos/{owner}/{repo}/releases/assets/{asset_id}DELETE /repos/:owner/:repo/releases/assets/:asset_idDELETE /repos/:owner/:repo/releases/assets/:asset_id
GET/{owner}/{repo}/raw/{ref}/{path}GET /:owner/:repo/raw/:ref/:path{.+}GET /:owner/:repo/raw/:ref/:path{.+}
GET/appGET /appGET /app
GET/app/installationsGET /app/installationsGET /app/installations
GET/app/installations/{installation_id}GET /app/installations/:installation_idGET /app/installations/:installation_id
GET/emojisGET /emojisGET /emojis
GET/login/oauth/authorizeGET /login/oauth/authorizeGET /login/oauth/authorize
GET/metaGET /metaGET /meta
GET/octocatGET /octocatGET /octocat
GET/organizationsGET /organizationsGET /organizations
GET/orgs/{org}GET /orgs/:orgGET /orgs/:org
PATCH/orgs/{org}PATCH /orgs/:orgPATCH /orgs/:org
GET/orgs/{org}/actions/secretsGET /orgs/:org/actions/secretsGET /orgs/:org/actions/secrets
GET/orgs/{org}/hooksGET /orgs/:org/hooksGET /orgs/:org/hooks
POST/orgs/{org}/hooksPOST /orgs/:org/hooksPOST /orgs/:org/hooks
GET/orgs/{org}/installationGET /orgs/:org/installationGET /orgs/:org/installation
GET/orgs/{org}/membersGET /orgs/:org/membersGET /orgs/:org/members
GET/orgs/{org}/memberships/{username}GET /orgs/:org/memberships/:usernameGET /orgs/:org/memberships/:username
PUT/orgs/{org}/memberships/{username}PUT /orgs/:org/memberships/:usernamePUT /orgs/:org/memberships/:username
GET/orgs/{org}/teamsGET /orgs/:org/teamsGET /orgs/:org/teams
POST/orgs/{org}/teamsPOST /orgs/:org/teamsPOST /orgs/:org/teams
GET/orgs/{org}/teams/{team_slug}/membersGET /orgs/:org/teams/:team_slug/membersGET /orgs/:org/teams/:team_slug/members
GET/orgs/{org}/teams/{team_slug}/reposGET /orgs/:org/teams/:team_slug/reposGET /orgs/:org/teams/:team_slug/repos
GET/rate_limitGET /rate_limitGET /rate_limit
GET/repos/{owner}/{repo}/actions/artifactsGET /repos/:owner/:repo/actions/artifactsGET /repos/:owner/:repo/actions/artifacts
GET/repos/{owner}/{repo}/actions/jobs/{job_id}GET /repos/:owner/:repo/actions/jobs/:job_idGET /repos/:owner/:repo/actions/jobs/:job_id
GET/repos/{owner}/{repo}/actions/jobs/{job_id}/logsGET /repos/:owner/:repo/actions/jobs/:job_id/logsGET /repos/:owner/:repo/actions/jobs/:job_id/logs
GET/repos/{owner}/{repo}/actions/runsGET /repos/:owner/:repo/actions/runsGET /repos/:owner/:repo/actions/runs
GET/repos/{owner}/{repo}/actions/runs/{run_id}/artifactsGET /repos/:owner/:repo/actions/runs/:run_id/artifactsGET /repos/:owner/:repo/actions/runs/:run_id/artifacts
GET/repos/{owner}/{repo}/actions/runs/{run_id}/jobsGET /repos/:owner/:repo/actions/runs/:run_id/jobsGET /repos/:owner/:repo/actions/runs/:run_id/jobs
GET/repos/{owner}/{repo}/actions/runs/{run_id}/logsGET /repos/:owner/:repo/actions/runs/:run_id/logsGET /repos/:owner/:repo/actions/runs/:run_id/logs
GET/repos/{owner}/{repo}/actions/secretsGET /repos/:owner/:repo/actions/secretsGET /repos/:owner/:repo/actions/secrets
GET/repos/{owner}/{repo}/actions/workflowsGET /repos/:owner/:repo/actions/workflowsGET /repos/:owner/:repo/actions/workflows
GET/repos/{owner}/{repo}/actions/workflows/{workflow_id}GET /repos/:owner/:repo/actions/workflows/:workflow_idGET /repos/:owner/:repo/actions/workflows/:workflow_id
GET/repos/{owner}/{repo}/actions/workflows/{workflow_id}/runsGET /repos/:owner/:repo/actions/workflows/:workflow_id/runsGET /repos/:owner/:repo/actions/workflows/:workflow_id/runs
GET/repos/{owner}/{repo}/branchesGET /repos/:owner/:repo/branchesGET /repos/:owner/:repo/branches
GET/repos/{owner}/{repo}/branches/{branch}GET /repos/:owner/:repo/branches/:branch{.+}GET /repos/:owner/:repo/branches/:branch{.+}
GET/repos/{owner}/{repo}/branches/{branch}/protection/enforce_adminsGET /repos/:owner/:repo/branches/:branch{.+}/protection/enforce_adminsGET /repos/:owner/:repo/branches/:branch{.+}/protection/enforce_admins
GET/repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviewsGET /repos/:owner/:repo/branches/:branch{.+}/protection/required_pull_request_reviewsGET /repos/:owner/:repo/branches/:branch{.+}/protection/required_pull_request_reviews
PATCH/repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviewsPATCH /repos/:owner/:repo/branches/:branch{.+}/protection/required_pull_request_reviewsPATCH /repos/:owner/:repo/branches/:branch{.+}/protection/required_pull_request_reviews
GET/repos/{owner}/{repo}/branches/{branch}/protection/required_status_checksGET /repos/:owner/:repo/branches/:branch{.+}/protection/required_status_checksGET /repos/:owner/:repo/branches/:branch{.+}/protection/required_status_checks
PATCH/repos/{owner}/{repo}/branches/{branch}/protection/required_status_checksPATCH /repos/:owner/:repo/branches/:branch{.+}/protection/required_status_checksPATCH /repos/:owner/:repo/branches/:branch{.+}/protection/required_status_checks
GET/repos/{owner}/{repo}/check-runs/{check_run_id}GET /repos/:owner/:repo/check-runs/:check_run_idGET /repos/:owner/:repo/check-runs/:check_run_id
PATCH/repos/{owner}/{repo}/check-runs/{check_run_id}PATCH /repos/:owner/:repo/check-runs/:check_run_idPATCH /repos/:owner/:repo/check-runs/:check_run_id
GET/repos/{owner}/{repo}/check-runs/{check_run_id}/annotationsGET /repos/:owner/:repo/check-runs/:check_run_id/annotationsGET /repos/:owner/:repo/check-runs/:check_run_id/annotations
GET/repos/{owner}/{repo}/check-suites/{check_suite_id}GET /repos/:owner/:repo/check-suites/:check_suite_idGET /repos/:owner/:repo/check-suites/:check_suite_id
GET/repos/{owner}/{repo}/check-suites/{check_suite_id}/check-runsGET /repos/:owner/:repo/check-suites/:check_suite_id/check-runsGET /repos/:owner/:repo/check-suites/:check_suite_id/check-runs
GET/repos/{owner}/{repo}/collaboratorsGET /repos/:owner/:repo/collaboratorsGET /repos/:owner/:repo/collaborators
GET/repos/{owner}/{repo}/collaborators/{username}/permissionGET /repos/:owner/:repo/collaborators/:username/permissionGET /repos/:owner/:repo/collaborators/:username/permission
GET/repos/{owner}/{repo}/commentsGET /repos/:owner/:repo/commentsGET /repos/:owner/:repo/comments
GET/repos/{owner}/{repo}/commitsGET /repos/:owner/:repo/commitsGET /repos/:owner/:repo/commits
GET/repos/{owner}/{repo}/commits/{commit_sha}/commentsGET /repos/:owner/:repo/commits/:commit_sha/commentsGET /repos/:owner/:repo/commits/:commit_sha/comments
POST/repos/{owner}/{repo}/commits/{commit_sha}/commentsPOST /repos/:owner/:repo/commits/:commit_sha/commentsPOST /repos/:owner/:repo/commits/:commit_sha/comments
GET/repos/{owner}/{repo}/commits/{ref}GET /repos/:owner/:repo/commits/:ref{.+}GET /repos/:owner/:repo/commits/:ref{.+}
GET/repos/{owner}/{repo}/commits/{ref}/check-runsGET /repos/:owner/:repo/commits/:ref{.+}/check-runsGET /repos/:owner/:repo/commits/:ref{.+}/check-runs
GET/repos/{owner}/{repo}/commits/{ref}/check-suitesGET /repos/:owner/:repo/commits/:ref{.+}/check-suitesGET /repos/:owner/:repo/commits/:ref{.+}/check-suites
GET/repos/{owner}/{repo}/compare/{basehead}GET /repos/:owner/:repo/compare/:basehead{.+}GET /repos/:owner/:repo/compare/:basehead{.+}
GET/repos/{owner}/{repo}/contentsGET /repos/:owner/:repo/contentsGET /repos/:owner/:repo/contents
GET/repos/{owner}/{repo}/contents/GET /repos/:owner/:repo/contents/GET /repos/:owner/:repo/contents/
GET/repos/{owner}/{repo}/contributorsGET /repos/:owner/:repo/contributorsGET /repos/:owner/:repo/contributors
GET/repos/{owner}/{repo}/forksGET /repos/:owner/:repo/forksGET /repos/:owner/:repo/forks
POST/repos/{owner}/{repo}/forksPOST /repos/:owner/:repo/forksPOST /repos/:owner/:repo/forks
GET/repos/{owner}/{repo}/git/blobs/{file_sha}GET /repos/:owner/:repo/git/blobs/:file_shaGET /repos/:owner/:repo/git/blobs/:file_sha
GET/repos/{owner}/{repo}/git/commits/{commit_sha}GET /repos/:owner/:repo/git/commits/:commit_shaGET /repos/:owner/:repo/git/commits/:commit_sha
GET/repos/{owner}/{repo}/git/tags/{tag_sha}GET /repos/:owner/:repo/git/tags/:tag_shaGET /repos/:owner/:repo/git/tags/:tag_sha
GET/repos/{owner}/{repo}/git/trees/{tree_sha}GET /repos/:owner/:repo/git/trees/:tree_shaGET /repos/:owner/:repo/git/trees/:tree_sha
GET/repos/{owner}/{repo}/hooksGET /repos/:owner/:repo/hooksGET /repos/:owner/:repo/hooks
POST/repos/{owner}/{repo}/hooksPOST /repos/:owner/:repo/hooksPOST /repos/:owner/:repo/hooks
GET/repos/{owner}/{repo}/hooks/{hook_id}/deliveriesGET /repos/:owner/:repo/hooks/:hook_id/deliveriesGET /repos/:owner/:repo/hooks/:hook_id/deliveries
GET/repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}GET /repos/:owner/:repo/hooks/:hook_id/deliveries/:delivery_idGET /repos/:owner/:repo/hooks/:hook_id/deliveries/:delivery_id
GET/repos/{owner}/{repo}/installationGET /repos/:owner/:repo/installationGET /repos/:owner/:repo/installation
GET/repos/{owner}/{repo}/issuesGET /repos/:owner/:repo/issuesGET /repos/:owner/:repo/issues
POST/repos/{owner}/{repo}/issuesPOST /repos/:owner/:repo/issuesPOST /repos/:owner/:repo/issues
GET/repos/{owner}/{repo}/issues/{issue_number}GET /repos/:owner/:repo/issues/:issue_numberGET /repos/:owner/:repo/issues/:issue_number
PATCH/repos/{owner}/{repo}/issues/{issue_number}PATCH /repos/:owner/:repo/issues/:issue_numberPATCH /repos/:owner/:repo/issues/:issue_number
GET/repos/{owner}/{repo}/issues/{issue_number}/commentsGET /repos/:owner/:repo/issues/:issue_number/commentsGET /repos/:owner/:repo/issues/:issue_number/comments
POST/repos/{owner}/{repo}/issues/{issue_number}/commentsPOST /repos/:owner/:repo/issues/:issue_number/commentsPOST /repos/:owner/:repo/issues/:issue_number/comments
GET/repos/{owner}/{repo}/issues/{issue_number}/eventsGET /repos/:owner/:repo/issues/:issue_number/eventsGET /repos/:owner/:repo/issues/:issue_number/events
GET/repos/{owner}/{repo}/issues/{issue_number}/timelineGET /repos/:owner/:repo/issues/:issue_number/timelineGET /repos/:owner/:repo/issues/:issue_number/timeline
GET/repos/{owner}/{repo}/issues/commentsGET /repos/:owner/:repo/issues/commentsGET /repos/:owner/:repo/issues/comments
GET/repos/{owner}/{repo}/labelsGET /repos/:owner/:repo/labelsGET /repos/:owner/:repo/labels
POST/repos/{owner}/{repo}/labelsPOST /repos/:owner/:repo/labelsPOST /repos/:owner/:repo/labels
GET/repos/{owner}/{repo}/languagesGET /repos/:owner/:repo/languagesGET /repos/:owner/:repo/languages
GET/repos/{owner}/{repo}/milestonesGET /repos/:owner/:repo/milestonesGET /repos/:owner/:repo/milestones
POST/repos/{owner}/{repo}/milestonesPOST /repos/:owner/:repo/milestonesPOST /repos/:owner/:repo/milestones
GET/repos/{owner}/{repo}/milestones/{milestone_number}/labelsGET /repos/:owner/:repo/milestones/:milestone_number/labelsGET /repos/:owner/:repo/milestones/:milestone_number/labels
GET/repos/{owner}/{repo}/pulls/{pull_number}/commentsGET /repos/:owner/:repo/pulls/:pull_number/commentsGET /repos/:owner/:repo/pulls/:pull_number/comments
POST/repos/{owner}/{repo}/pulls/{pull_number}/commentsPOST /repos/:owner/:repo/pulls/:pull_number/commentsPOST /repos/:owner/:repo/pulls/:pull_number/comments
GET/repos/{owner}/{repo}/pulls/{pull_number}/commitsGET /repos/:owner/:repo/pulls/:pull_number/commitsGET /repos/:owner/:repo/pulls/:pull_number/commits
GET/repos/{owner}/{repo}/pulls/{pull_number}/filesGET /repos/:owner/:repo/pulls/:pull_number/filesGET /repos/:owner/:repo/pulls/:pull_number/files
GET/repos/{owner}/{repo}/pulls/{pull_number}/reviewsGET /repos/:owner/:repo/pulls/:pull_number/reviewsGET /repos/:owner/:repo/pulls/:pull_number/reviews
POST/repos/{owner}/{repo}/pulls/{pull_number}/reviewsPOST /repos/:owner/:repo/pulls/:pull_number/reviewsPOST /repos/:owner/:repo/pulls/:pull_number/reviews
GET/repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}GET /repos/:owner/:repo/pulls/:pull_number/reviews/:review_idGET /repos/:owner/:repo/pulls/:pull_number/reviews/:review_id
PUT/repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}PUT /repos/:owner/:repo/pulls/:pull_number/reviews/:review_idPUT /repos/:owner/:repo/pulls/:pull_number/reviews/:review_id
GET/repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/commentsGET /repos/:owner/:repo/pulls/:pull_number/reviews/:review_id/commentsGET /repos/:owner/:repo/pulls/:pull_number/reviews/:review_id/comments
GET/repos/{owner}/{repo}/pulls/commentsGET /repos/:owner/:repo/pulls/commentsGET /repos/:owner/:repo/pulls/comments
GET/repos/{owner}/{repo}/readmeGET /repos/:owner/:repo/readmeGET /repos/:owner/:repo/readme
GET/repos/{owner}/{repo}/releasesGET /repos/:owner/:repo/releasesGET /repos/:owner/:repo/releases
POST/repos/{owner}/{repo}/releasesPOST /repos/:owner/:repo/releasesPOST /repos/:owner/:repo/releases
GET/repos/{owner}/{repo}/releases/{release_id}/assetsGET /repos/:owner/:repo/releases/:release_id/assetsGET /repos/:owner/:repo/releases/:release_id/assets
POST/repos/{owner}/{repo}/releases/{release_id}/assetsPOST /repos/:owner/:repo/releases/:release_id/assetsPOST /repos/:owner/:repo/releases/:release_id/assets
GET/repos/{owner}/{repo}/releases/latestGET /repos/:owner/:repo/releases/latestGET /repos/:owner/:repo/releases/latest
GET/repos/{owner}/{repo}/releases/tags/{tag}GET /repos/:owner/:repo/releases/tags/:tagGET /repos/:owner/:repo/releases/tags/:tag
GET/repos/{owner}/{repo}/tagsGET /repos/:owner/:repo/tagsGET /repos/:owner/:repo/tags
GET/repos/{owner}/{repo}/topicsGET /repos/:owner/:repo/topicsGET /repos/:owner/:repo/topics
PUT/repos/{owner}/{repo}/topicsPUT /repos/:owner/:repo/topicsPUT /repos/:owner/:repo/topics
GET/repositories/{id}GET /repositories/:idGET /repositories/:id
GET/search/codeGET /search/codeGET /search/code
GET/search/commitsGET /search/commitsGET /search/commits
GET/search/issuesGET /search/issuesGET /search/issues
GET/search/labelsGET /search/labelsGET /search/labels
GET/search/repositoriesGET /search/repositoriesGET /search/repositories
GET/search/topicsGET /search/topicsGET /search/topics
GET/search/usersGET /search/usersGET /search/users
GET/settings/applicationsGET /settings/applicationsGET /settings/applications
GET/settings/connections/applications/{client_id}GET /settings/connections/applications/:client_idGET /settings/connections/applications/:client_id
GET/teams/{team_id}GET /teams/:team_idGET /teams/:team_id
GET/teams/{team_id}/membersGET /teams/:team_id/membersGET /teams/:team_id/members
GET/userGET /userGET /user
PATCH/userPATCH /userPATCH /user
GET/user/emailsGET /user/emailsGET /user/emails
GET/user/orgsGET /user/orgsGET /user/orgs
GET/user/reposGET /user/reposGET /user/repos
POST/user/reposPOST /user/reposPOST /user/repos
GET/usersGET /usersGET /users
GET/users/{username}GET /users/:usernameGET /users/:username
GET/users/{username}/followersGET /users/:username/followersGET /users/:username/followers
GET/users/{username}/followingGET /users/:username/followingGET /users/:username/following
GET/users/{username}/hovercardGET /users/:username/hovercardGET /users/:username/hovercard
GET/users/{username}/installationGET /users/:username/installationGET /users/:username/installation
GET/users/{username}/orgsGET /users/:username/orgsGET /users/:username/orgs
GET/users/{username}/reposGET /users/:username/reposGET /users/:username/repos
GET/versionsGET /versionsGET /versions
GET/zenGET /zenGET /zen
PATCH/repos/{owner}/{repo}/check-suites/preferencesPATCH /repos/:owner/:repo/check-suites/preferencesPATCH /repos/:owner/:repo/check-suites/preferences
POST/app/installations/{installation_id}/access_tokensPOST /app/installations/:installation_id/access_tokensPOST /app/installations/:installation_id/access_tokens
POST/login/oauth/access_tokenPOST /login/oauth/access_tokenPOST /login/oauth/access_token
POST/login/oauth/callbackPOST /login/oauth/callbackPOST /login/oauth/callback
POST/orgs/{org}/hooks/{hook_id}/pingsPOST /orgs/:org/hooks/:hook_id/pingsPOST /orgs/:org/hooks/:hook_id/pings
POST/orgs/{org}/reposPOST /orgs/:org/reposPOST /orgs/:org/repos
POST/repos/{owner}/{repo}/actions/runs/{run_id}/cancelPOST /repos/:owner/:repo/actions/runs/:run_id/cancelPOST /repos/:owner/:repo/actions/runs/:run_id/cancel
POST/repos/{owner}/{repo}/actions/runs/{run_id}/rerunPOST /repos/:owner/:repo/actions/runs/:run_id/rerunPOST /repos/:owner/:repo/actions/runs/:run_id/rerun
POST/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatchesPOST /repos/:owner/:repo/actions/workflows/:workflow_id/dispatchesPOST /repos/:owner/:repo/actions/workflows/:workflow_id/dispatches
POST/repos/{owner}/{repo}/check-runsPOST /repos/:owner/:repo/check-runsPOST /repos/:owner/:repo/check-runs
POST/repos/{owner}/{repo}/check-runs/{check_run_id}/rerequestPOST /repos/:owner/:repo/check-runs/:check_run_id/rerequestPOST /repos/:owner/:repo/check-runs/:check_run_id/rerequest
POST/repos/{owner}/{repo}/check-suitesPOST /repos/:owner/:repo/check-suitesPOST /repos/:owner/:repo/check-suites
POST/repos/{owner}/{repo}/check-suites/{check_suite_id}/rerequestPOST /repos/:owner/:repo/check-suites/:check_suite_id/rerequestPOST /repos/:owner/:repo/check-suites/:check_suite_id/rerequest
POST/repos/{owner}/{repo}/git/blobsPOST /repos/:owner/:repo/git/blobsPOST /repos/:owner/:repo/git/blobs
POST/repos/{owner}/{repo}/git/commitsPOST /repos/:owner/:repo/git/commitsPOST /repos/:owner/:repo/git/commits
POST/repos/{owner}/{repo}/git/tagsPOST /repos/:owner/:repo/git/tagsPOST /repos/:owner/:repo/git/tags
POST/repos/{owner}/{repo}/git/treesPOST /repos/:owner/:repo/git/treesPOST /repos/:owner/:repo/git/trees
POST/repos/{owner}/{repo}/hooks/{hook_id}/pingsPOST /repos/:owner/:repo/hooks/:hook_id/pingsPOST /repos/:owner/:repo/hooks/:hook_id/pings
POST/repos/{owner}/{repo}/hooks/{hook_id}/testsPOST /repos/:owner/:repo/hooks/:hook_id/testsPOST /repos/:owner/:repo/hooks/:hook_id/tests
POST/repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/eventsPOST /repos/:owner/:repo/pulls/:pull_number/reviews/:review_id/eventsPOST /repos/:owner/:repo/pulls/:pull_number/reviews/:review_id/events
POST/repos/{owner}/{repo}/releases/generate-notesPOST /repos/:owner/:repo/releases/generate-notesPOST /repos/:owner/:repo/releases/generate-notes
POST/repos/{owner}/{repo}/transferPOST /repos/:owner/:repo/transferPOST /repos/:owner/:repo/transfer
POST/settings/connections/applications/{client_id}/revokePOST /settings/connections/applications/:client_id/revokePOST /settings/connections/applications/:client_id/revoke
PUT/repos/{owner}/{repo}/actions/workflows/{workflow_id}/disablePUT /repos/:owner/:repo/actions/workflows/:workflow_id/disablePUT /repos/:owner/:repo/actions/workflows/:workflow_id/disable
PUT/repos/{owner}/{repo}/actions/workflows/{workflow_id}/enablePUT /repos/:owner/:repo/actions/workflows/:workflow_id/enablePUT /repos/:owner/:repo/actions/workflows/:workflow_id/enable
PUT/repos/{owner}/{repo}/pulls/{pull_number}/mergePUT /repos/:owner/:repo/pulls/:pull_number/mergePUT /repos/:owner/:repo/pulls/:pull_number/merge
PUT/repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/dismissalsPUT /repos/:owner/:repo/pulls/:pull_number/reviews/:review_id/dismissalsPUT /repos/:owner/:repo/pulls/:pull_number/reviews/:review_id/dismissals
PUT/repos/{owner}/{repo}/pulls/{pull_number}/update-branchPUT /repos/:owner/:repo/pulls/:pull_number/update-branchPUT /repos/:owner/:repo/pulls/:pull_number/update-branch

Documentation

The package README, the same file that ships in the npm tarball.Edit on GitHub

Local emulators. Real API contracts. Part of Mockingbird.

WIP GitHub REST emulator targeting X-GitHub-Api-Version: 2026-03-10. Repository observations, commit-backed references, and same-repository pull-request create/get/list/update are implemented. Unsupported features return emulator-only 501. API_EVIDENCE.md pins the source and distinguishes research from runtime verification. SUPPORT.md is generated from the contract.

Install

Shell
bun add @crvouga/mockingbird-service-github

API

The portable entry exports GitHubAPI, createRuntime, prepareFixtures, GITHUB_NAMESPACE, GITHUB_API_VERSION, document, operationIds, and supportedOperationIds. Types include GitHubAPIOptions, GitHubRuntime, GitHubRuntimeOptions, GitHubFixtures, PreparedGitHubFixtures, Repository, Commit, PullRequest, OperationId, and SupportedOperationId. The Node-only /server entry exports createServer, DEFAULT_PORT, serveTarget, GitHubServerOptions, and GitHubServer. The executable is mockingbird-github.

prepareFixtures(input) generates omitted synthetic App private keys with WebCrypto. Pass its fixtures to createRuntime and use generatedPrivateKeys in your test's JWT signer; supplied keys are excluded from that list. Reusing prepared fixtures preserves identity across reset. The runtime's mount(prefix) embeds its Fetch and request-method handlers under an app origin. The CLI accepts --fixtures <json-file> and --base-url <url>; fleet entries also accept inline fixtures. See setup examples.

Usage

The example uses the official @octokit/rest SDK (22.0.1).

TypeScript
import { Octokit } from "@octokit/rest"
import { createRuntime } from "@crvouga/mockingbird-service-github"

// Test setup: the emulator, seeded with a synthetic repository and commit graph.
const emulator = createRuntime({ seed: 42 })
await emulator.fetch(new Request("http://github.mock/__admin/github/repositories", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    owner: "synthetic-org", name: "example", default_branch: "main",
    commits: [
      { sha: "a".repeat(40), parents: [] },
      { sha: "b".repeat(40), parents: ["a".repeat(40)] },
    ],
    branches: { main: "a".repeat(40) },
  }),
}))

// The app's own Octokit. Only its fetch changes: requests reach the emulator
// in this process instead of api.github.com.
const octokit = new Octokit({
  request: { fetch: (url: string, init: RequestInit) => emulator.fetch(new Request(url, init)) },
})
const repo = { owner: "synthetic-org", repo: "example" }
const { data: repository } = await octokit.rest.repos.get(repo)
console.log(repository.full_name) // "synthetic-org/example"

// Publish a synthetic branch and PR.
const branch = await octokit.rest.git.createRef({ ...repo, ref: "refs/heads/topic", sha: "b".repeat(40) })
console.log(branch.status) // 201
const { status, data: pr } = await octokit.rest.pulls.create({
  ...repo, title: "Synthetic change", head: "topic", base: "main", body: "Description",
})
console.log(status, pr.number) // 201, 1
const lookup = await octokit.rest.pulls.list({ ...repo, head: "synthetic-org:topic", base: "main", state: "open" })
console.log(lookup.data[0]?.number === pr.number) // true
const edited = await octokit.rest.pulls.update({ ...repo, pull_number: pr.number, body: "" })
console.log(edited.data.body === null) // true

All requests above stay inside the local Fetch runtime. The branch target must already exist in the synthetic commit graph; this is not a Git push.

createRuntime adds health, namespaces, clock, scoped faults, redacted request journal and shared Timeline. Select a namespace with x-mockingbird-namespace or /__admin/ns/<name>/... consistently on seed and provider requests. Reset removes repository, owner, ancestry and branch state in that namespace; diagnostic history and fault settings retain their shared runtime lifetimes. Checkpoints restore all stored provider state through the shared Timeline.

GitHubAPI is the portable provider-only Fetch/Hono entry; createServer from @crvouga/mockingbird-service-github/server exposes Node HTTP and returns a close function. Programmatic servers default to an ephemeral loopback port. The CLI is mockingbird-github serve --port 8828.

Synthetic setup and boundaries

POST /__admin/github/repositories accepts owner/name, optional private and default_branch, a commits array of lowercase 40-hex SHA/parents records and a branches map of branch names to seeded SHAs. Omitted commits/branches create an empty synthetic repository. Every parent and branch target must be seeded; cycles, duplicates and an absent default branch in a nonempty branch map are rejected. Validation completes before mutation; existing repositories cannot be overwritten by this control. Use a fresh namespace or reset for a different fixture. These are emulator fixture constraints, not GitHub REST request rules.

Seeds create organization owners only. Same-owner repositories share owner identity. Repository lookup is case-insensitive; branch names preserve case. Only identity, ownership, visibility, default branch, URLs and timestamps are returned; repository settings, statistics, permissions and full upstream response coverage are not claimed. Missing repositories return a provider-shaped 404. No credential-based namespace mapping or authorization policy is implemented.

The selected API version is returned in x-github-api-version-selected. Omitting the request header selects this emulator's 2026-03-10 contract, unlike GitHub's current 2022-11-28 default. Other versions return emulator-only 501, not a claimed provider error. This is an explicit single-version emulator.

No Git transport, real commit creation, token issuance, GitHub App identity, branch protection, repository rules, merge execution or outgoing notification is provided. Seeded ancestry does not prove a real repository's contents. No expected-old-SHA or universal pull-request idempotency guarantee is added. Use synthetic fixture names only; journal records omit bodies and credential/query values. Package tests require no GitHub account or network service.

References

GET /repos/{owner}/{repo}/git/ref/heads/topic/nested reads an exact ref; GET .../git/matching-refs/heads/topic returns prefix matches. Omit the suffix (with or without a trailing slash) to list all synthetic refs. Names are case-sensitive; owner/repository lookup remains case-insensitive. Matching refs are sorted by full name.

Create with POST .../git/refs and { "ref": "refs/heads/topic", "sha": "<seeded SHA>" }. Update with PATCH .../git/refs/heads/topic and { "sha": "<seeded SHA>" }. Default force: false requires ancestry from the head current at mutation time. force: true permits a synthetic non-fast-forward update; it never invokes Git or GitHub. Unknown fields do not confer an expected-old-SHA lease or idempotency.

Only commit-backed references are modeled; annotated tag objects and provider-managed pull refs are unsupported. The bounded oracle compared successful ref operations and one non-fast-forward422 envelope. Other error wording, condition/status mapping and validation precedence remain provisional; see API_EVIDENCE.md.

Pull requests

Create with POST /repos/{owner}/{repo}/pulls and a title, head branch and base branch: { "title": "Synthetic change", "head": "topic", "base": "main" }. Both branches must exist and the head must contain seeded ancestry absent from the base. An owner-qualified same-repository head is accepted; cross-repository heads and issue conversion are explicit501 limitations. No notifications are sent.

Read GET .../pulls/{number} or list GET .../pulls. List supports state (open default, closed, all), head=owner:branch, base, created/updated sorting, direction, page (default1) and per_page (default30, clamped100). Follow Link relations; filters and explicit namespace header/path selections survive pagination. Continue sending shared history/branch headers if you selected a Timeline branch. Popularity and long-running sorts return501 because comments/activity are not modeled.

Update title, body, base, state and maintainer_can_modify with PATCH .../pulls/{number}. An empty body string clears the description and returns null. Head is not an update field. Numbers are repository-scoped; id/node_id stay stable. The reduced response includes head/base names, repository identity and SHAs. Open PRs resolve current branch tips; closed PRs retain the last captured tips until explicitly updated. PR timestamps track create/update calls, not background provider events. Mergeability and merge commit SHA stay null; merged stays false. Author identity, comments, labels, reviews, merge execution and provider event propagation are not modeled.

An open PR for the same head/base yields422 with a PullRequest/custom validation error. Consumer-private operation IDs and Idempotency-Key never deduplicate creates. If the response is lost, list by head/base across pages, then retrieve the matching number; retrying create can yield the duplicate error. The bounded live oracle confirmed this same-repository duplicate envelope at API version2026-03-10. See API_EVIDENCE.md for untested cases and error-precedence gaps.

Publication fault scenarios

Activate a namespace-scoped preset through POST /__admin/faults with { "preset": "github_pr_create_accepted_drop" }. List presets with GET /__admin/faults/presets. Existing shared fault overrides can narrow an operation/path, change a response or retire a scenario after a chosen count.

  • github_pr_create_accepted_drop, github_pr_update_accepted_drop, github_ref_create_accepted_drop, github_ref_update_accepted_drop: the next matching request reaches normal validation. Only a successful mutation is accepted, checkpointed and followed by a dropped response. Invalid requests retain their ordinary errors and consume that one-shot rule. In-process Fetch rejects with TypeError; the Node HTTP adapter closes the connection. Recover through provider reads; retrying a create may return a duplicate error.
  • github_unavailable: one503 before mutation for each of the four write operations.
  • github_denied: one403 with a scripted integration-denied message per write operation. This does not inspect credentials or evaluate permissions/rules.
  • github_rate_limited: one secondary-limit429 per write operation, Retry-After60 and a synthetic remaining count1. Static headers exercise client backoff; there is no automatic quota engine, timer-driven expiry, or claim that every GitHub rate limit has this status/message/header combination.

Accepted write journal entries record accepted, checkpoint and ref/PR-number metadata even when response status is0. Bodies are omitted. Fault settings follow shared runtime lifetime and are not rewound by provider-state checkout.

To model a separate intervening actor, call POST /__admin/github/refs/move with { "owner": "synthetic-org", "repo": "example", "ref": "refs/heads/topic", "sha": "<seeded SHA>" } between a client's read and write. This emulator-only control requires an existing branch and a seeded fast-forward target, creates a separate checkpoint, and returns simulated: true. It cannot create refs, force rewrites or mutate another namespace. Invalid controls leave state/history unchanged. Subsequent writes use the current tip's ancestry; a stale observation alone is not grounds for rejection. This control models interleaving, not atomic publication or real branch enforcement.

Local contract verification

bun test includes seeded self-parity for all nine operations and successful per-operation response checks. A deliberately divergent, schema-valid repository observation must fail the parity comparator. These are two isolated emulator instances; self-parity is not evidence that GitHub matches this implementation.

test/node-consumer.mjs uses native Node HTTP and literal public contracts against the built local server. It recovers a PR after socket loss, observes duplicates without idempotency, follows pagination over31PRs, checks intervening ref movement without CAS, and schedules a retry from Retry-After using a logical test clock. It imports no provider handlers or state helpers. Run the package build before running this consumer directly. It creates only local fixture resources.

Parity metadata enables all nine operations; the four writes are marked unsafe and require explicit inclusion. Real-provider comparisons still require the separate bounded oracle and explicit disposable-resource authorization. WIP status remains until independent compatibility evidence supports a stronger claim.

Bounded live evidence and publication limits

The separately approved 2026-09-29 oracle verification matched12comparisons across all nine operations after repairing the empty-body update mismatch found in its first run. Each run closed its fixture PR and deleted its two unchanged fixture branches; closed PR history and Git objects remain. The runner is available only in the repository checkout; see the oracle guide. It requires a reviewed manifest and explicit writes/notifications/cleanup approval, plus MOCKINGBIRD_GITHUB_TOKEN or explicitly selected authenticated gh usage. Ordinary package tests never invoke it.

This evidence covers selected identity/state/ref/PR fields and two error cases. It does not establish complete upstream response schemas, all validation precedence, live multi-page behavior, real rate quotas or real network-loss guarantees. The package remains WIP. Scripted denied responses do not prove GitHub App identity, token permissions, branch protection or rulesets. There is no Git transport or token issuance, no expected-old-SHA compare-and-swap, and no atomic transaction covering ref movement and PR publication. Consumer reconciliation policy and cross-component Initiative acceptance remain outside this emulator.

↑↓ to navigate↵ to open