Turn GitHub sync off, or move a read-only mirror to neosource
POST/api/repos/{owner}/{repo}/github-detach
detachGithub
Detaches a GitHub-synced repository (`github_managed`) or a read-only mirror of GitHub (`neosource_readonly_mirror`, "Move to neosource", ADR 0166): its relationship becomes `neosource_managed` with `authority_handoff: detached`, the pull from GitHub stops (a pull already in flight may finish once, never over a newer push), and this deployment is authoritative from here on. **Nothing is written to GitHub**, and commits pushed there after the last pull stay only there. Any GitHub App binding, every branch and tag, every preserved ref and the repository's issues are kept; the response says what was kept. `expected_authority_epoch` must be the relationship's current epoch, else `409 relationship_conflict`. Refused with `409 relationship_conflict` while a push to GitHub has not settled — the message names the ref transaction to resolve or cancel first — while an attach holds the relationship (for a mirror: while any attach is in flight; cancel it first), or when the repository is neither synced nor mirrored. Requires repository **admin** from a **signed-in session** or an access token with no workspace or repository lock (ADR 0162); a locked token is refused with `403 forbidden`.
Requires authentication using a bearer token or a session cookie — see tokens and scopes.
curl
curl -X POST 'https://neosource.dev/api/repos/OWNER/REPO/github-detach' \
-H 'Authorization: Bearer $NEOSOURCE_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"expected_authority_epoch":0}'fetch
fetch("https://neosource.dev/api/repos/OWNER/REPO/github-detach", {
method: "POST",
headers: {
Authorization: "Bearer $NEOSOURCE_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({"expected_authority_epoch":0}),
});Path parameters
ownerrequiredRepository owner or organization slug.
reporequiredRepository name.
Request bodyrequired
application/json
Responses
200Detached
application/json
Standard errors
Bodies documented once for the whole API — see standard errors.
- 400Bad Request — one of: invalid_input
- 401Authentication required
- 403Forbidden — one of: forbidden
- 404Not Found — one of: not_found
- 409Conflict — one of: attach_publish_unconfirmed, conflict, relationship_conflict, relationship_unavailable
- 429Rate limited — retry after the `Retry-After` header
- 500Internal server error
- 502Bad Gateway — one of: upstream
- 503Service temporarily unavailable / at capacity — retry after the `Retry-After` header
- 504Gateway timeout — the request exceeded the server's handling budget
Schemas
Referenced above. Listed here rather than expanded inline, so the same definition is not repeated at every level.
GithubAttachSummary
The latest attach, as the relationship read summarises it.
created_atrequiredMillis since the Unix epoch.
finished_atWhen the run reached a terminal state; `null` while in flight.
last_errororiginrequiredrun_idrequiredstaterequired
GithubSyncBlockerRow
One thing that stops GitHub sync: the blocker itself — its `kind` plus whatever detail that kind carries (`outcome`, `health`, `refname`, `missing_events`) — the stable `fix` code the panel renders, and a link to where it is fixed when there is one.
fixrequiredfix_urlWhere the fix is made: the GitHub App's install page for the App-side fixes, the GitHub repository's settings for an archived repository. `null` otherwise, and when this deployment has no App.
GithubMetadataMode
Whether GitHub's issues and pull requests are pulled into this repository — the `github_repo_sync.metadata_mode` column (`migrations/20260930170000_github_attach_runs.sql`). [`Self::Pulled`] is every relationship that predates the column and every read-only mirror (a CHECK). [`Self::Off`] is a repository attached to a GitHub repository while keeping its native issues (ADR 0136): its metadata is never claimed by the poller or the backfill.
"pulled""off"
GithubRelationshipMode
Which forge owns an imported repository's refs and provider-shaped data. The ONLY relationship vocabulary, in the database (`github_repo_sync.mode`), in Rust, on the wire and in the SPA. The legacy `synced` / `mirror_readonly` pair it replaced was collapsed onto [`Self::NeosourceReadonlyMirror`] by `migrations/20260902120000_github_relationship_contract.sql`.
"github_managed""neosource_readonly_mirror""neosource_managed""github_backup"
GithubSyncState
Lifecycle of a repo's metadata sync (`github_repo_sync.state`).
"backfilling""live""error"
GithubWriteHealth
Whether a GitHub-authoritative repository is currently safe to write through — the `github_repo_sync.write_health` column. Closed vocabulary; the tokens are the CHECK list at `migrations/20260901120000_github_relationship_expand.sql`. The default for a fresh relationship is [`Self::LfsUnverified`], not `Healthy`: a repo whose LFS posture nobody has looked at yet is not known-good, and the fail-closed direction is the one that does not push objects at an upstream that will reject or mangle them.
"healthy""lfs_unverified""lfs_blocked""paused""error"